Skip to content

Track the 1.x SDK, and stop hijacking npm installs from Metro - #43

Merged
yosriady merged 1 commit into
mainfrom
chore/rn-example-sdk-1x
Aug 2, 2026
Merged

Track the 1.x SDK, and stop hijacking npm installs from Metro#43
yosriady merged 1 commit into
mainfrom
chore/rn-example-sdk-1x

Conversation

@yosriady

@yosriady yosriady commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #42. Found while checking whether this example needs updating for the mobile analytics work — and it turned up a bug I introduced in #42.

The Metro bug (the important one)

The resolver redirect added in #42 fires unconditionally:

if (moduleName === SDK_PACKAGE_NAME) {
  return { filePath: path.resolve(sdkPath, "src/index.ts"), type: "sourceFile" };
}

So the package resolves to ../../sdk-react-native/src for anyone with a sibling checkout, silently running unreleased local code instead of the pinned npm version — and resolves to a non-existent path for anyone without one. CI didn't catch it because the with-react-native job lints and tests but never bundles.

I hit it directly: after pinning the dependency back to the published 1.0.0, the app kept emitting Deep Link Opened and the new app://<bundle>/<screen> URL — neither of which exists in 1.0.0. I nearly reported that as "verified against the published SDK".

Now gated on the node_modules entry actually being a symlink, which is precisely what distinguishes pnpm add link: from an install. Verified both directions:

Install mode page_url emitted
pnpm install (npm 1.0.0) app://Wallet
pnpm add link:../../sdk-react-native app://com.formo.analytics.demo/Wallet

Dependency: ^0.1.6^1.0.0

The caret on a 0.x version can never resolve to 1.x, so the example was pinned two majors behind and none of the 1.0 behaviour was being demonstrated.

App identity comes from app.json now

config/formo.ts hardcoded version: "1.1.0" while app.json said 1.0.0. These reach Formo as app_version and as the mobile origin, so the drift meant every event reported a version the app never had — indefinitely, and invisibly.

Now read from app.json through expo-constants (already a dependency). The existing test asserted the literal "1.1.0", which is exactly how the drift went unnoticed; it now asserts the two agree, plus that the iOS bundle identifier and Android package match (the SDK sends one bundleId for both, so a mismatch would make one app report two origins).

The explicit app config is deliberately kept rather than relying on auto-detection: in Expo Go the native modules report Expo Go's own bundle id and version, and on React Native Web nothing resolves a bundle id at all.

README

screen() takes (name, category?, properties?). The example passed an object where the category string goes, which would have sent [object Object] as the category.

Verification

14 tests pass (was 13), typecheck clean, and expo start --web bundles and runs in both install modes.

Not included

Demos of the new lifecycle APIs (pushNotification*, crash tracking, Application Foregrounded) — they aren't in a published release yet. Worth a follow-up once the SDK ships them.

Also still open: expo prebuild fails outright because tar@7 is hoisted where Expo 52 expects tar v6's API, so no one can build a dev client from this example. Separate issue.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Metro resolver: the redirect added in #42 fired unconditionally, so the
package resolved to ../../sdk-react-native/src for ANYONE with a sibling
checkout — silently running unreleased local code instead of the pinned
version — and resolved to a non-existent path for anyone without one. Caught
while verifying the example against the published SDK: it kept reporting
unreleased behaviour. Now gated on the node_modules entry actually being a
symlink, which is exactly what distinguishes `pnpm add link:` from an install.
Verified both ways round: npm install emits app://Wallet (1.0.0), a link emits
app://com.formo.analytics.demo/Wallet.

Dependency: ^0.1.6 -> ^1.0.0. The caret meant 0.1.6 could never pick up 1.x,
so the example was two majors behind.

App identity now comes from app.json via expo-constants instead of a hardcoded
copy. config/formo.ts claimed version 1.1.0 while app.json said 1.0.0 — and
these reach Formo as `app_version` and as the mobile `origin`, so the drift
meant every event reported a version the app never had. The test asserted the
literal 1.1.0, which is how it went unnoticed; it now asserts the two agree.

Kept the explicit app config rather than relying on auto-detection: in Expo Go
the native modules report EXPO GO's bundle id and version, and on React Native
Web nothing resolves a bundle id at all.

README: screen() takes (name, category?, properties?) — the example passed an
object where the category string goes, which would have sent [object Object].

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yosriady
yosriady merged commit 4c2ba32 into main Aug 2, 2026
7 checks passed
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.

1 participant