Skip to content

feat(formulus): support settings deep links - #1009

Open
allennakalema06-web wants to merge 3 commits into
OpenDataEnsemble:devfrom
allennakalema06-web:feat/formulus-settings-deep-link
Open

allennakalema06-web wants to merge 3 commits into
OpenDataEnsemble:devfrom
allennakalema06-web:feat/formulus-settings-deep-link

Conversation

@allennakalema06-web

Copy link
Copy Markdown
Contributor

Summary
Implements #915 by wrapping Formulus settings QR payloads in a formulus://settings?payload=... deep link while preserving support for existing bare FRMLS: payloads.
Changes

  • Registers the formulus://settings deep link on Android.
  • Adds the Formulus custom URL scheme on iOS.
  • Routes settings deep links to the Profiles flow.
  • Reuses the existing QR settings parser and profile creation flow so settings are presented for confirmation before a profile is created.
  • Keeps the in-app QR scanner backward compatible with bare FRMLS: payloads.
  • Updates the Formulus QR generator, Synkronus Portal, and synk qr to generate the new URI-wrapped payload.
  • Adds tests covering bare FRMLS and wrapped deep-link parsing.
    Validation
  • QRSettingsService tests: 4 passed.
  • Android manifest processing: passed.
  • TypeScript checks for the changed Formulus application files: passed.
  • git diff --check: passed.
  • Formulus QR generator manually verified to emit formulus://settings?payload=....
  • Physical Android test confirmed that Android recognizes the formulus://settings intent and launches Formulus.
  • Full end-to-end profile-prefill verification on the physical device was blocked by a local Metro dependency-resolution issue (invariant) unrelated to these changes.
    iOS
    The custom formulus URL scheme is registered through CFBundleURLTypes. Physical iOS Camera/Code Scanner handling of the custom scheme has not been verified and may require follow-up testing on an iOS device.

@Mishael-2584 Mishael-2584 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @allennakalema06-web, the approach is good: it reuses the existing confirmation flow and keeps bare FRMLS: scanning working.

Two things to resolve before merge: iOS delivery of the link, and how older app versions handle the new QR format. I've also left a few smaller suggestions inline.

@Mishael-2584
Mishael-2584 self-requested a review October 6, 2026 11:35
Comment on lines +13 to +23
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org.opendataensemble.formulus</string>
<key>CFBundleURLSchemes</key>
<array>
<string>formulus</string>
</array>
</dict>
</array>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The scheme is registered here, but I don't think iOS will hand the link to React Native yet. This app uses the scene lifecycle (UIApplicationSceneManifest + SceneDelegate), and SceneDelegate.swift only implements willConnectTo. With scenes, incoming URLs arrive through scene(_:openURLContexts:) (app already running) and connectionOptions.urlContexts (cold start), not application(_:open:options:). Neither is forwarded to RCTLinkingManager today.

Could we add that and test on an iOS device, or limit this PR to Android and say so in the description? Right now it reads as if iOS is supported, but it's untested.

u: username,
p: password,
});
return `formulus://settings?payload=${encodeURIComponent(frmls)}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes the portal emit only the new formulus:// link. Any Formulus build without this PR will reject these QR codes ("Not FRMLS format"), and field devices often update slowly.

Could we keep a way to generate the old format for a release or two (a toggle in the portal and a --legacy flag in synk qr), or at least note a minimum app version in the release notes? The same applies to qr.go and generateQR.ts.

Comment on lines +76 to +78
const settings = await QRSettingsService.processQRCode(
`formulus://settings?payload=${encodeURIComponent(payload)}`,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Navigation has already decoded payload here. We re-encode it, wrap it back into formulus://settings?payload=..., and then unwrapQRCode strips and decodes it again. It works, but it's a roundabout path.

Suggestion: add a method like QRSettingsService.parsePayload(payload) that takes the FRMLS string directly, and call that here. parseQRCode then only handles full scanned strings.

if (!mountedRef.current) return;

setDropdownOpen(false);
setEditor({ label: '', initialSettings: settings });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If the user already has the new-profile form open, a link replaces it and discards what they typed. Maybe skip the link, or ask first, when editor is already set.

Comment on lines +6 to +12
function unwrapQRCode(qrString: string): string {
if (qrString.startsWith('FRMLS:')) return qrString;

const prefix = `${FORMULUS_SETTINGS_URL}?payload=`;
if (!qrString.startsWith(prefix)) {
throw new Error('Not a Formulus settings link');
}

@Mishael-2584 Mishael-2584 Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strict startsWith(prefix) check fails on formulus://settings/?payload=..., and on links with extra params after the payload (...&foo=bar). In that second case the extra text becomes part of the payload and decoding returns wrong output.

Could we extract the value more flexibly (e.g. /[?&]payload=([^&#]*)/, or new URL() since react-native-url-polyfill is already a dependency)? Also, decodeURIComponent throws on a malformed % sequence, so a small test for that and for a trailing param would be good.

This branch has not been deployed

No deployments
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.

3 participants