Skip to content

P-1925 P-1926 Enhance analytics API with optional parameters and referral config#2

Merged
yosriady merged 14 commits into
mainfrom
claude/review-react-native-sdk-xbqM5
Mar 27, 2026
Merged

P-1925 P-1926 Enhance analytics API with optional parameters and referral config#2
yosriady merged 14 commits into
mainfrom
claude/review-react-native-sdk-xbqM5

Conversation

@yosriady
Copy link
Copy Markdown
Contributor

@yosriady yosriady commented Mar 26, 2026

Summary

This PR enhances the Formo Analytics SDK with improved flexibility for tracking events and managing referral sources. It makes several previously required parameters optional, adds support for custom referral query parameter configuration, and improves error handling.

Key Changes

  • Screen Event Enhancement: Added optional category parameter to the screen() method for better event categorization
  • Signature Event Flexibility: Made chainId parameter optional in signature() method, removing the validation that rejected null/undefined/0 values
  • Transaction Event Enrichment: Added optional function_name and function_args parameters to transaction() method for better smart contract interaction tracking
  • Referral Configuration: Introduced ReferralOptions interface allowing custom query parameter names for referral code extraction via parseTrafficSource()
  • Error Handling: Added global errorHandler callback in SDK options for centralized error management
  • Provider Props Consolidation: Moved asyncStorage, onReady, and onError props from separate interface into main FormoAnalyticsProviderProps for cleaner API
  • Transaction Parameters: Made data, to, and value optional in transaction tracking (previously required)

Implementation Details

  • The parseTrafficSource() utility now accepts custom referral parameter names and merges them with default parameters (ref, referral, refcode, referrer_code)
  • Optional parameters are conditionally included in event payloads using spread operators to avoid sending undefined values
  • Removed chainId validation logic that prevented tracking signatures without a valid chain ID
  • Updated EventFactory to handle all new optional parameters consistently across event generation
  • Updated tests to reflect that signatures can now be tracked with chainId 0 or without chainId entirely

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N


Open with Devin

claude added 2 commits March 26, 2026 10:31
… parity

- Make signature() chainId optional to match web SDK (chain-agnostic signatures)
- Add function_name and function_args to transaction() for contract call tracking
- Add optional category parameter to screen() matching web page() API
- Consolidate FormoAnalyticsProviderProps into single complete type
- Replace require() with static import in setTrafficSourceFromUrl
- Add errorHandler option to Options for global error handling
- Add ReferralOptions for configurable referral query parameter parsing
- Update tests for new optional chainId behavior

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This PR enhances the Formo Analytics SDK with improved flexibility for tracking events and managing referral sources. It makes several previously required parameters optional, adds support for custom referral query parameter configuration, and improves error handling.

Highlights

  • Screen Event Enhancement: Added optional category parameter to the screen() method for better event categorization
  • Signature Event Flexibility: Made chainId parameter optional in signature() method, removing the validation that rejected null/undefined/0 values
  • Transaction Event Enrichment: Added optional function_name and function_args parameters to transaction() method for better smart contract interaction tracking
  • Referral Configuration: Introduced ReferralOptions interface allowing custom query parameter names for referral code extraction via parseTrafficSource()
  • Error Handling: Added global errorHandler callback in SDK options for centralized error management
  • Provider Props Consolidation: Moved asyncStorage, onReady, and onError props from separate interface into main FormoAnalyticsProviderProps for cleaner API
  • Transaction Parameters: Made data, to, and value optional in transaction tracking (previously required)
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

The screen() signature changed to screen(name, category?, properties?, ...),
so the test needs to pass undefined for category before the properties object.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several enhancements to the analytics SDK. Key changes include making chainId optional for signature events, adding an optional category parameter to screen events, and introducing function_name and function_args for transaction events. An errorHandler option has been added for global error handling, and the traffic source parsing now supports custom referral query parameters. The review suggests an improvement in parseTrafficSource to use a Set for merging default and custom referral parameters, which would enhance clarity and prevent redundant checks.

Comment on lines +45 to +47
const refParams = customRefParams
? [...defaultRefParams, ...customRefParams]
: defaultRefParams;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve clarity and prevent iterating over duplicate referral parameter names, you can use a Set to merge the default and custom parameters. This ensures each parameter is unique and avoids redundant checks in the loop that follows.

Suggested change
const refParams = customRefParams
? [...defaultRefParams, ...customRefParams]
: defaultRefParams;
const refParams = customRefParams
? [...new Set([...defaultRefParams, ...customRefParams])]
: defaultRefParams;

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff8c13a7dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/FormoAnalytics.ts
Comment on lines 140 to 143
public async screen(
name: string,
category?: string,
properties?: IFormoEventProperties,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep screen() backward-compatible with existing argument order

Changing screen to screen(name, category?, properties?, context?, callback?) shifts every existing positional call by one slot. Calls like screen('Home', { section: 'featured' }) now pass the properties object as category, so custom properties/context are dropped or misaligned and a non-string category can be sent in the payload. This is a silent analytics regression for existing JS consumers and a breaking API change for TS users that previously used screen(name, properties, ...).

Useful? React with 👍 / 👎.

Comment thread src/FormoAnalytics.ts Outdated
public setTrafficSourceFromUrl(url: string): void {
const { parseTrafficSource, storeTrafficSource } = require("./utils/trafficSource");
const trafficSource = parseTrafficSource(url);
const trafficSource = parseTrafficSource(url, this.options.referral?.queryParams);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor referral pathPattern when parsing traffic source

The new ReferralOptions type advertises pathPattern, but URL parsing only receives referral.queryParams here. As a result, configuring { referral: { pathPattern: ... } } has no effect and referral codes encoded in path segments are never extracted, which makes the public option misleading and breaks expected attribution behavior for path-based referral links.

Useful? React with 👍 / 👎.

Comment thread src/FormoAnalytics.ts Outdated
Comment on lines +179 to +180
public setTrafficSourceFromUrl(url: string): void {
const { parseTrafficSource, storeTrafficSource } = require("./utils/trafficSource");
const trafficSource = parseTrafficSource(url);
const trafficSource = parseTrafficSource(url, this.options.referral?.queryParams);

This comment was marked as outdated.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

…ern, dedup refParams

- Revert screen() to original (name, properties?, context?, callback?) signature
  to avoid breaking existing callers. Category is passed via properties.category.
- Implement pathPattern support in parseTrafficSource so ReferralOptions.pathPattern
  actually extracts referral codes from URL paths via regex capture group.
- Deduplicate referral query params using Set to avoid redundant lookups.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
@yosriady
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de6f846ea1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/FormoAnalytics.ts Outdated
} catch (error) {
logger.error("Error tracking event:", error);
if (this.options.errorHandler) {
this.options.errorHandler(error instanceof Error ? error : new Error(String(error)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard errorHandler exceptions inside trackEvent

The new global errorHandler is invoked from within the catch block, but it is not wrapped in its own try/catch. If a consumer-provided handler throws (for example due to a bug in their reporter), that exception escapes trackEvent and causes public calls like track()/signature() to reject even though tracking errors are otherwise handled internally. This turns telemetry failures into app-level failures; wrap the handler call and log secondary handler errors instead of letting them bubble.

Useful? React with 👍 / 👎.

claude added 2 commits March 26, 2026 13:55
… from bubbling

A throwing errorHandler would cause trackEvent to reject, turning telemetry
failures into app-level failures. Now secondary handler errors are logged
instead of propagated.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Since this is v1.0.0 with no existing consumers, use the clean signature
screen(name, category?, properties?, context?, callback?) instead of the
properties.category workaround. Matches web SDK's page(category?, name?).

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
@yosriady
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New referral and errorHandler options missing from optionsKey, preventing SDK re-initialization on change

The PR adds two new options to Options: referral (serializable object with queryParams and pathPattern) and errorHandler (function). Neither is included in the optionsKey computation in FormoAnalyticsProvider.tsx. The existing pattern extracts each option individually (lines 126-137) and includes serializable ones in serializableOptions (lines 141-152) and useMemo deps (line 160). For example, app (a similar serializable config object) is included. For functions, ready is tracked as hasReady: !!options?.ready. However, referral is not extracted or serialized, and errorHandler has no hasErrorHandler equivalent. If a consumer changes referral config or toggles errorHandler, the optionsKey won't change, the useEffect (line 162) won't re-run, and the SDK instance will retain stale options—meaning setTrafficSourceFromUrl() at src/FormoAnalytics.ts:180-184 would use outdated referral config.

(Refers to lines 140-160)

Prompt for agents
In src/FormoAnalyticsProvider.tsx, around line 125-160:

1. Extract the new options from the options object (after line 137):
   const referral = options?.referral;
   const hasErrorHandler = !!options?.errorHandler;

2. Add them to the serializableOptions object (around line 141-152):
   Add referral and hasErrorHandler to the object.

3. Add them to the useMemo dependency array (line 160):
   Add referral and hasErrorHandler to the dependency list.

This follows the existing pattern where serializable options (like app) are included directly, and function options (like ready) are tracked as boolean flags (hasReady).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdf7205c64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/FormoAnalytics.ts
Comment on lines 140 to 143
public async screen(
name: string,
category?: string,
properties?: IFormoEventProperties,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep screen properties as second argument

Adding category before properties changes runtime behavior for existing integrations that call screen(name, properties) (including JS users without type checks). Calls like formo.screen('Profile', { userId: '123' }) now treat the object as category, drop properties, and emit malformed screen metadata, so upgrades silently lose custom screen properties. Please preserve backward compatibility with an overload/runtime argument check (or by appending category after existing optional args).

Useful? React with 👍 / 👎.

@yosriady yosriady changed the title Enhance analytics API with optional parameters and referral config P-1925 P-1926 Enhance analytics API with optional parameters and referral config Mar 27, 2026
@linear
Copy link
Copy Markdown

linear Bot commented Mar 27, 2026

… backgrounded)

Implements the Segment/RudderStack mobile lifecycle spec using a JS-side
approach (no native modules required, works in Expo Go):

- Application Installed: first launch (no stored version in AsyncStorage)
- Application Updated: version or build changed from stored values
- Application Opened: every cold start (from_background: false) and
  foreground return (from_background: true), includes deep link URL
- Application Backgrounded: app goes to background

Detection compares stored app version/build in AsyncStorage against
current values from react-native-device-info, expo-application, or
options.app config.

Controlled via autocapture.lifecycle option (default: true).
Adds AppLifecycleManager with its own AppState listener, separate from
EventQueue's flush-on-background listener.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Comment on lines +130 to +133
if (previousVersion === null && previousBuild === null) {
// No stored version — first install
logger.info("AppLifecycleManager: Application Installed");
await this.analytics.track("Application Installed", {

This comment was marked as outdated.

When AsyncStorage is not provided, the SDK falls back to MemoryStorage
which is empty on every cold start. Without this guard, detectInstallOrUpdate
would false-positive as "Application Installed" on every launch.

Now checks if AsyncStorage adapter is actually available before comparing
stored version/build. Skips install/update events (with warning) if only
MemoryStorage is available. Application Opened/Backgrounded still fire.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
devin-ai-integration[bot]

This comment was marked as resolved.

StorageManager.getStorage("asyncStorage") silently falls back to
MemoryStorage when AsyncStorage hasn't been initialized, and
MemoryStorage.isAvailable() always returns true. This made the
lifecycle guard ineffective — install events would still fire on
every cold start without AsyncStorage.

Fix: Add StorageManager.hasPersistentStorage() that checks if an
initialized AsyncStorage adapter exists in the storages map, bypassing
the fallback logic in getStorage().

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Comment thread src/lib/lifecycle/index.ts Outdated
Comment on lines +162 to +163
storage().set(LOCAL_APP_VERSION_KEY, version);
storage().set(LOCAL_APP_BUILD_KEY, build);

This comment was marked as outdated.

…install events

storage().set() writes to the in-memory cache synchronously but persists
to AsyncStorage asynchronously without being awaited. If the app crashes
or is force-quit before the async write completes, the version data is
lost and the next launch would fire a duplicate "Application Installed"
event. Switch to awaiting setAsync() to ensure persistence completes.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
devin-ai-integration[bot]

This comment was marked as resolved.

A transient AsyncStorage write failure in lifecycle tracking (e.g.,
storage full, permission error) would reject the init() promise and
leave the SDK uninitialized with no-op methods for the entire session.
Lifecycle tracking is non-critical — it should not block the core
analytics SDK from functioning.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
@yosriady
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67b07281b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/FormoAnalytics.ts
Comment on lines 154 to 156
name: string,
category?: string,
properties?: IFormoEventProperties,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve backward-compatible screen() parameter parsing

Changing screen to accept category as the second positional argument breaks existing integrations that call screen(name, properties, ...): the properties object is now treated as category, so the intended screen properties are lost and an object-valued category is emitted instead. This is a silent analytics regression for JavaScript users (and TypeScript users compiled against the old signature), so the method should keep legacy argument handling or add new parameters without shifting existing positions.

Useful? React with 👍 / 👎.

Comment thread src/lib/storage/StorageManager.ts Outdated
Comment on lines +84 to +85
const stored = this.storages.get("asyncStorage");
return stored !== undefined && stored.isAvailable();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detect real AsyncStorage before lifecycle install/update checks

hasPersistentStorage() currently returns true whenever something is stored at the asyncStorage slot, but this slot can contain the memory fallback after getStorage("asyncStorage") fails availability checks. In that common path (no AsyncStorage provided), lifecycle code will think persistence exists and run install/update detection every launch, producing repeated false Application Installed/Application Updated analytics instead of skipping detection.

Useful? React with 👍 / 👎.

getStorage("asyncStorage") caches a MemoryStorage fallback at the
asyncStorage slot when AsyncStorage hasn't been initialized. The
previous check (stored !== undefined && stored.isAvailable()) would
pass for this cached MemoryStorage since MemoryStorage.isAvailable()
always returns true.

Use instanceof AsyncStorageAdapter to ensure we're checking the real
persistent adapter, not a MemoryStorage fallback cached at that slot.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Comment on lines 520 to 527
to,
value,
...(transactionHash && { transactionHash }),
...(function_name && { function_name }),
...(function_args && { function_args }),
...properties,
},
address,

This comment was marked as outdated.

claude added 2 commits March 27, 2026 05:15
The toSnakeCase utility recursively converts all object keys, which
corrupts function_args values that represent smart contract ABI
parameter names (e.g., "tokenId" becomes "token_id"). Extract
function_args before conversion and re-attach after to preserve
the original key casing.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
Upgrade pnpm from 9.0.0 to 10.27.0 and add security settings
per https://pnpm.io/supply-chain-security:

1. onlyBuiltDependencies: [] — block lifecycle scripts by default,
   only explicitly approved packages can run postinstall scripts
2. blockExoticSubdeps: true — prevent transitive deps from using
   git repos or direct tarball URLs
3. minimumReleaseAge: 1440 — wait 24h before installing newly
   published versions to avoid the malware exposure window
4. trustPolicy: no-downgrade — prevent installation if a package's
   trust level has decreased from previous releases

Also removes package-lock.json (npm artifact) in favor of
pnpm-lock.yaml.

https://claude.ai/code/session_0117WwRELH1nzVbxRj7Kmm5N
@yosriady yosriady merged commit 5222b1c into main Mar 27, 2026
5 checks passed
@yosriady yosriady deleted the claude/review-react-native-sdk-xbqM5 branch March 27, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants