@oxyhq/services 24.0.0 → 24.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/android/src/main/java/so/oxy/session/OxyBackgroundSessionModule.kt +42 -27
  2. package/android/src/main/java/so/oxy/session/OxyBackgroundSessionStore.kt +11 -2
  3. package/lib/commonjs/ui/screens/ProfileScreen.js +15 -4
  4. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  5. package/lib/commonjs/ui/session/backgroundSession.js +37 -12
  6. package/lib/commonjs/ui/session/backgroundSession.js.map +1 -1
  7. package/lib/commonjs/ui/session/useBackgroundSessionSync.js +4 -5
  8. package/lib/commonjs/ui/session/useBackgroundSessionSync.js.map +1 -1
  9. package/lib/commonjs/ui/utils/userUtils.js +11 -21
  10. package/lib/commonjs/ui/utils/userUtils.js.map +1 -1
  11. package/lib/module/ui/screens/ProfileScreen.js +15 -4
  12. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  13. package/lib/module/ui/session/backgroundSession.js +37 -12
  14. package/lib/module/ui/session/backgroundSession.js.map +1 -1
  15. package/lib/module/ui/session/useBackgroundSessionSync.js +4 -5
  16. package/lib/module/ui/session/useBackgroundSessionSync.js.map +1 -1
  17. package/lib/module/ui/utils/userUtils.js +12 -22
  18. package/lib/module/ui/utils/userUtils.js.map +1 -1
  19. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/ui/session/backgroundSession.d.ts.map +1 -1
  21. package/lib/typescript/commonjs/ui/session/useBackgroundSessionSync.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/ui/utils/userUtils.d.ts +5 -7
  23. package/lib/typescript/commonjs/ui/utils/userUtils.d.ts.map +1 -1
  24. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  25. package/lib/typescript/module/ui/session/backgroundSession.d.ts.map +1 -1
  26. package/lib/typescript/module/ui/session/useBackgroundSessionSync.d.ts.map +1 -1
  27. package/lib/typescript/module/ui/utils/userUtils.d.ts +5 -7
  28. package/lib/typescript/module/ui/utils/userUtils.d.ts.map +1 -1
  29. package/package.json +2 -2
  30. package/src/ui/screens/ProfileScreen.tsx +17 -3
  31. package/src/ui/session/__tests__/backgroundSession.test.ts +63 -8
  32. package/src/ui/session/backgroundSession.ts +65 -20
  33. package/src/ui/session/useBackgroundSessionSync.ts +4 -5
  34. package/src/ui/utils/userUtils.ts +10 -29
@@ -27,12 +27,16 @@
27
27
  * live in different stores with one writer each, so there is nothing to keep in
28
28
  * lockstep and no state that can disagree.
29
29
  *
30
- * Android only. `requireOptionalNativeModule` resolves `null` on web and on any
31
- * app that does not ship the native module, and every function here degrades to a
32
- * no-op in that case so an app can enable this and lose nothing on other
33
- * platforms.
30
+ * ## Android only, and web is refused rather than merely unsupported
31
+ *
32
+ * Web is gated off by an explicit platform check, not by the native module being
33
+ * absent — provisioning in a browser origin would be a security downgrade, so it
34
+ * must not depend on an incidental fact. See {@link loadNativeModule}. iOS has no
35
+ * implementation yet and resolves to no module, so every function here degrades to
36
+ * a no-op: an app can enable this and lose nothing on other platforms.
34
37
  */
35
38
  import { requireOptionalNativeModule } from 'expo-modules-core';
39
+ import { Platform } from 'react-native';
36
40
  import { logger } from '@oxyhq/core';
37
41
 
38
42
  /**
@@ -52,7 +56,30 @@ const RENEW_WHEN_REMAINING_MS = 7 * 24 * 60 * 60 * 1000;
52
56
  let nativeModule;
53
57
 
54
58
  /**
55
- * Resolve the native module once.
59
+ * Resolve the native module once — and refuse outright on web.
60
+ *
61
+ * ## Web is excluded on purpose, and not merely because the module is absent
62
+ *
63
+ * The credential is deliberately NON-ROTATING and long-lived, which is safe only
64
+ * because native background code is its sole consumer and nobody rotates it. A
65
+ * browser origin already holds the ROTATING `deviceSecret`, and it has no
66
+ * background worker to serve — so provisioning there would add a strictly weaker
67
+ * long-lived secret alongside a stronger short-lived one. That is a downgrade, not
68
+ * a harmless no-op, and the harm lands at the PROVISION CALL (which mints a live
69
+ * server-side credential) regardless of whether anything manages to store it.
70
+ *
71
+ * That property must not rest on `requireOptionalNativeModule` happening to return
72
+ * `null` on web. It does today — the module is registered for android only — but
73
+ * that is an incidental fact a future web shim or a web-build native-module mock
74
+ * would quietly reverse. So the platform is checked FIRST, before we even ask the
75
+ * registry, and every entry point in this module inherits the gate.
76
+ *
77
+ * (A pre-deploy server makes this visible too: oxy-api's `sessionDevice` router
78
+ * applies `requireSameSiteOrigin` path-agnostically, so an unmatched path answers
79
+ * `404` to a native caller but `403 BAD_ORIGIN` to a browser one. Core's degrade is
80
+ * deliberately narrow — `404 → null` only — because a real origin misconfiguration
81
+ * on a CURRENT server also returns 403, and swallowing that would hide a genuine
82
+ * bug. Platform gating belongs here, where the platform is actually known.)
56
83
  *
57
84
  * `requireOptionalNativeModule` (a static import Metro always resolves) rather
58
85
  * than a dynamic `import(moduleName)`: the latter compiles to `require(variable)`
@@ -62,6 +89,10 @@ let nativeModule;
62
89
  */
63
90
  function loadNativeModule() {
64
91
  if (nativeModule === undefined) {
92
+ if (Platform.OS === 'web') {
93
+ nativeModule = null;
94
+ return nativeModule;
95
+ }
65
96
  const native = requireOptionalNativeModule('OxyBackgroundSession');
66
97
  nativeModule = native && typeof native.put === 'function' && typeof native.clear === 'function' && typeof native.peek === 'function' ? {
67
98
  put: native.put.bind(native),
@@ -205,13 +236,7 @@ export async function syncBackgroundSession(input) {
205
236
  });
206
237
  return;
207
238
  }
208
- const persisted = await native.put({
209
- baseUrl: oxyServices.getBaseURL(),
210
- deviceId: provisioned.deviceId,
211
- secret: provisioned.secret,
212
- accountId: provisioned.accountId,
213
- expiresAt
214
- });
239
+ const persisted = await native.put(oxyServices.getBaseURL(), provisioned.deviceId, provisioned.secret, provisioned.accountId, expiresAt);
215
240
  if (!persisted) {
216
241
  logger.warn('[backgroundSession] the native store did not retain the background credential; background refreshes stay signed out until the next attempt', {
217
242
  component: 'backgroundSession'
@@ -1 +1 @@
1
- {"version":3,"names":["requireOptionalNativeModule","logger","RENEW_WHEN_REMAINING_MS","nativeModule","loadNativeModule","undefined","native","put","clear","peek","bind","isBackgroundSessionSupported","clearBackgroundSession","error","warn","component","assertProvisioningAvailable","oxyServices","provisionBackgroundCredential","message","Error","syncBackgroundSession","input","userId","canUsePrivateApi","isCurrent","stored","accountId","isForCurrentAccount","hasLifeLeft","expiresAt","Date","now","provisioned","parse","Number","isFinite","persisted","baseUrl","getBaseURL","deviceId","secret"],"sourceRoot":"../../../../src","sources":["ui/session/backgroundSession.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["requireOptionalNativeModule","Platform","logger","RENEW_WHEN_REMAINING_MS","nativeModule","loadNativeModule","undefined","OS","native","put","clear","peek","bind","isBackgroundSessionSupported","clearBackgroundSession","error","warn","component","assertProvisioningAvailable","oxyServices","provisionBackgroundCredential","message","Error","syncBackgroundSession","input","userId","canUsePrivateApi","isCurrent","stored","accountId","isForCurrentAccount","hasLifeLeft","expiresAt","Date","now","provisioned","parse","Number","isFinite","persisted","getBaseURL","deviceId","secret"],"sourceRoot":"../../../../src","sources":["ui/session/backgroundSession.ts"],"mappings":"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","ignoreList":[]}
@@ -33,11 +33,10 @@ export function useBackgroundSessionSync({
33
33
  return;
34
34
  }
35
35
  if (!enabled) {
36
- // An app that turns the feature OFF must not leave a live credential at
37
- // rest for the rest of its 30-day lifetime. The cost is one local store
38
- // open per launch on Android apps that never use the feature, which is
39
- // accepted deliberately: correct revocation is worth more than skipping a
40
- // memoized keystore open.
36
+ // An app that leaves the feature off must not keep a credential THIS app
37
+ // provisioned earlier. The store is package-scoped (see
38
+ // OxyBackgroundSessionStore), so this cannot revoke a sibling Oxy app's
39
+ // credential under the shared UID.
41
40
  void clearBackgroundSession();
42
41
  return;
43
42
  }
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","AppState","clearBackgroundSession","isBackgroundSessionSupported","syncBackgroundSession","useBackgroundSessionSync","enabled","oxyServices","userId","canUsePrivateApi","current","run","isCurrent","subscription","addEventListener","state","remove"],"sourceRoot":"../../../../src","sources":["ui/session/useBackgroundSessionSync.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,SAAS,QAAQ,OAAO;AACjC,SAASC,QAAQ,QAAQ,cAAc;AAEvC,SACEC,sBAAsB,EACtBC,4BAA4B,EAC5BC,qBAAqB,QAChB,wBAAqB;AAY5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CAAC;EACvCC,OAAO;EACPC,WAAW;EACXC,MAAM;EACNC;AAC6B,CAAC,EAAQ;EACtCT,SAAS,CAAC,MAAM;IACd,IAAI,CAACG,4BAA4B,CAAC,CAAC,EAAE;MACnC;IACF;IAEA,IAAI,CAACG,OAAO,EAAE;MACZ;MACA;MACA;MACA;MACA;MACA,KAAKJ,sBAAsB,CAAC,CAAC;MAC7B;IACF;;IAEA;IACA;IACA,IAAIQ,OAAO,GAAG,IAAI;IAClB;IACA;IACA;IACA;IACA;IACA,MAAMC,GAAG,GAAGA,CAAA,KAAM;MAChB,KAAKP,qBAAqB,CAAC;QACzBG,WAAW;QACXC,MAAM;QACNC,gBAAgB;QAChBG,SAAS,EAAEA,CAAA,KAAMF;MACnB,CAAC,CAAC;IACJ,CAAC;IAEDC,GAAG,CAAC,CAAC;IACL,MAAME,YAAY,GAAGZ,QAAQ,CAACa,gBAAgB,CAAC,QAAQ,EAAGC,KAAK,IAAK;MAClE,IAAIA,KAAK,KAAK,QAAQ,EAAE;QACtBJ,GAAG,CAAC,CAAC;MACP;IACF,CAAC,CAAC;IAEF,OAAO,MAAM;MACXD,OAAO,GAAG,KAAK;MACfG,YAAY,CAACG,MAAM,CAAC,CAAC;MACrB;MACA;MACA;MACA;IACF,CAAC;EACH,CAAC,EAAE,CAACV,OAAO,EAAEC,WAAW,EAAEC,MAAM,EAAEC,gBAAgB,CAAC,CAAC;AACtD","ignoreList":[]}
1
+ {"version":3,"names":["useEffect","AppState","clearBackgroundSession","isBackgroundSessionSupported","syncBackgroundSession","useBackgroundSessionSync","enabled","oxyServices","userId","canUsePrivateApi","current","run","isCurrent","subscription","addEventListener","state","remove"],"sourceRoot":"../../../../src","sources":["ui/session/useBackgroundSessionSync.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,SAAS,QAAQ,OAAO;AACjC,SAASC,QAAQ,QAAQ,cAAc;AAEvC,SACEC,sBAAsB,EACtBC,4BAA4B,EAC5BC,qBAAqB,QAChB,wBAAqB;AAY5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CAAC;EACvCC,OAAO;EACPC,WAAW;EACXC,MAAM;EACNC;AAC6B,CAAC,EAAQ;EACtCT,SAAS,CAAC,MAAM;IACd,IAAI,CAACG,4BAA4B,CAAC,CAAC,EAAE;MACnC;IACF;IAEA,IAAI,CAACG,OAAO,EAAE;MACZ;MACA;MACA;MACA;MACA,KAAKJ,sBAAsB,CAAC,CAAC;MAC7B;IACF;;IAEA;IACA;IACA,IAAIQ,OAAO,GAAG,IAAI;IAClB;IACA;IACA;IACA;IACA;IACA,MAAMC,GAAG,GAAGA,CAAA,KAAM;MAChB,KAAKP,qBAAqB,CAAC;QACzBG,WAAW;QACXC,MAAM;QACNC,gBAAgB;QAChBG,SAAS,EAAEA,CAAA,KAAMF;MACnB,CAAC,CAAC;IACJ,CAAC;IAEDC,GAAG,CAAC,CAAC;IACL,MAAME,YAAY,GAAGZ,QAAQ,CAACa,gBAAgB,CAAC,QAAQ,EAAGC,KAAK,IAAK;MAClE,IAAIA,KAAK,KAAK,QAAQ,EAAE;QACtBJ,GAAG,CAAC,CAAC;MACP;IACF,CAAC,CAAC;IAEF,OAAO,MAAM;MACXD,OAAO,GAAG,KAAK;MACfG,YAAY,CAACG,MAAM,CAAC,CAAC;MACrB;MACA;MACA;MACA;IACF,CAAC;EACH,CAAC,EAAE,CAACV,OAAO,EAAEC,WAAW,EAAEC,MAAM,EAAEC,gBAAgB,CAAC,CAAC;AACtD","ignoreList":[]}
@@ -4,12 +4,11 @@
4
4
  * User display name and date formatting utilities.
5
5
  *
6
6
  * Display-name helpers follow the API contract: prefer `name.displayName` when
7
- * present, otherwise fall back to the normalized handle. The legacy
8
- * `getAccountDisplayName` chain is only used as a last resort for local
9
- * account-switcher surfaces without a handle.
7
+ * present, otherwise fall back to the normalized handle via
8
+ * `getNormalizedUserHandle`.
10
9
  */
11
10
 
12
- import { getAccountDisplayName as coreGetAccountDisplayName, getNormalizedUserHandle } from '@oxyhq/core';
11
+ import { getNormalizedUserHandle } from '@oxyhq/core';
13
12
 
14
13
  /**
15
14
  * Formats a date string to a readable format (e.g., "Feb 21, 2025")
@@ -37,37 +36,28 @@ function readDisplayName(user) {
37
36
  /**
38
37
  * Gets a display name from user data.
39
38
  *
40
- * Prefers API `name.displayName`, then the normalized handle, then the local
41
- * account-switcher fallback chain.
39
+ * Prefers API `name.displayName`, then the normalized handle.
42
40
  */
43
- export const getDisplayName = (user, locale) => {
41
+ export const getDisplayName = user => {
44
42
  const displayName = readDisplayName(user);
45
43
  if (displayName) return displayName;
46
- const handle = getNormalizedUserHandle(user);
47
- if (handle) return handle;
48
- return coreGetAccountDisplayName(user, locale);
44
+ return getNormalizedUserHandle(user) ?? '';
49
45
  };
50
46
 
51
47
  /**
52
48
  * Gets a short display name (first token) for compact UI.
53
49
  */
54
- export const getShortDisplayName = (user, locale) => {
50
+ export const getShortDisplayName = user => {
55
51
  const displayName = readDisplayName(user);
56
52
  if (displayName) {
57
53
  const firstToken = displayName.split(/\s+/).find(Boolean);
58
54
  if (firstToken) return firstToken;
59
55
  }
60
- const name = user?.name;
61
- if (name && typeof name === 'object') {
62
- const first = typeof name.first === 'string' ? name.first.trim() : '';
63
- if (first) return first.split(/\s+/)[0] ?? first;
64
- const full = typeof name.full === 'string' ? name.full.trim() : '';
65
- if (full) return full.split(/\s+/)[0] ?? full;
66
- } else if (typeof name === 'string' && name.trim()) {
67
- return name.trim().split(/\s+/)[0] ?? name.trim();
68
- }
69
56
  const handle = getNormalizedUserHandle(user);
70
- if (handle) return handle;
71
- return coreGetAccountDisplayName(user, locale);
57
+ if (handle) {
58
+ const firstToken = handle.split(/\s+/).find(Boolean);
59
+ return firstToken ?? handle;
60
+ }
61
+ return '';
72
62
  };
73
63
  //# sourceMappingURL=userUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["getAccountDisplayName","coreGetAccountDisplayName","getNormalizedUserHandle","formatDate","dateString","date","Date","Number","isNaN","getTime","toLocaleDateString","month","day","year","readDisplayName","user","name","displayName","trim","getDisplayName","locale","handle","getShortDisplayName","firstToken","split","find","Boolean","first","full"],"sourceRoot":"../../../../src","sources":["ui/utils/userUtils.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["getNormalizedUserHandle","formatDate","dateString","date","Date","Number","isNaN","getTime","toLocaleDateString","month","day","year","readDisplayName","user","name","displayName","trim","getDisplayName","getShortDisplayName","firstToken","split","find","Boolean","handle"],"sourceRoot":"../../../../src","sources":["ui/utils/userUtils.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,uBAAuB,QAAmC,aAAa;;AAEhF;AACA;AACA;AACA,OAAO,MAAMC,UAAU,GAAIC,UAA4C,IAAa;EAClF,IAAI,CAACA,UAAU,EAAE,OAAO,EAAE;EAE1B,IAAI;IACF,MAAMC,IAAI,GAAGD,UAAU,YAAYE,IAAI,GAAGF,UAAU,GAAG,IAAIE,IAAI,CAACF,UAAU,CAAC;IAC3E,IAAIG,MAAM,CAACC,KAAK,CAACH,IAAI,CAACI,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE;IAE3C,OAAOJ,IAAI,CAACK,kBAAkB,CAAC,OAAO,EAAE;MACtCC,KAAK,EAAE,OAAO;MACdC,GAAG,EAAE,SAAS;MACdC,IAAI,EAAE;IACR,CAAC,CAAC;EACJ,CAAC,CAAC,MAAM;IACN,OAAO,EAAE;EACX;AACF,CAAC;AAED,SAASC,eAAeA,CAACC,IAA6C,EAAU;EAC9E,MAAMC,IAAI,GAAGD,IAAI,EAAEC,IAAI;EACvB,IAAI,CAACA,IAAI,IAAI,OAAOA,IAAI,KAAK,QAAQ,EAAE,OAAO,EAAE;EAChD,OAAO,OAAOA,IAAI,CAACC,WAAW,KAAK,QAAQ,GAAGD,IAAI,CAACC,WAAW,CAACC,IAAI,CAAC,CAAC,GAAG,EAAE;AAC5E;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,cAAc,GACzBJ,IAA6C,IAClC;EACX,MAAME,WAAW,GAAGH,eAAe,CAACC,IAAI,CAAC;EACzC,IAAIE,WAAW,EAAE,OAAOA,WAAW;EACnC,OAAOf,uBAAuB,CAACa,IAAI,CAAC,IAAI,EAAE;AAC5C,CAAC;;AAED;AACA;AACA;AACA,OAAO,MAAMK,mBAAmB,GAC9BL,IAA6C,IAClC;EACX,MAAME,WAAW,GAAGH,eAAe,CAACC,IAAI,CAAC;EACzC,IAAIE,WAAW,EAAE;IACf,MAAMI,UAAU,GAAGJ,WAAW,CAACK,KAAK,CAAC,KAAK,CAAC,CAACC,IAAI,CAACC,OAAO,CAAC;IACzD,IAAIH,UAAU,EAAE,OAAOA,UAAU;EACnC;EAEA,MAAMI,MAAM,GAAGvB,uBAAuB,CAACa,IAAI,CAAC;EAC5C,IAAIU,MAAM,EAAE;IACV,MAAMJ,UAAU,GAAGI,MAAM,CAACH,KAAK,CAAC,KAAK,CAAC,CAACC,IAAI,CAACC,OAAO,CAAC;IACpD,OAAOH,UAAU,IAAII,MAAM;EAC7B;EAEA,OAAO,EAAE;AACX,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ProfileScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAe3D,UAAU,kBAAmB,SAAQ,eAAe;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoS/C,CAAC;AAuCF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ProfileScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ProfileScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAe3D,UAAU,kBAAmB,SAAQ,eAAe;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkT/C,CAAC;AAuCF,eAAe,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/backgroundSession.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgE/C,yEAAyE;AACzE,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqF5F"}
1
+ {"version":3,"file":"backgroundSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/backgroundSession.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyG/C,yEAAyE;AACzE,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqF5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBackgroundSessionSync.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/useBackgroundSessionSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,6BAA6B;IAC5C,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,EAAE,6BAA6B,GAAG,IAAI,CAiDtC"}
1
+ {"version":3,"file":"useBackgroundSessionSync.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/useBackgroundSessionSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,6BAA6B;IAC5C,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,EAAE,6BAA6B,GAAG,IAAI,CAgDtC"}
@@ -2,9 +2,8 @@
2
2
  * User display name and date formatting utilities.
3
3
  *
4
4
  * Display-name helpers follow the API contract: prefer `name.displayName` when
5
- * present, otherwise fall back to the normalized handle. The legacy
6
- * `getAccountDisplayName` chain is only used as a last resort for local
7
- * account-switcher surfaces without a handle.
5
+ * present, otherwise fall back to the normalized handle via
6
+ * `getNormalizedUserHandle`.
8
7
  */
9
8
  import { type DisplayNameUserShape } from '@oxyhq/core';
10
9
  /**
@@ -14,12 +13,11 @@ export declare const formatDate: (dateString: string | undefined | null | Date)
14
13
  /**
15
14
  * Gets a display name from user data.
16
15
  *
17
- * Prefers API `name.displayName`, then the normalized handle, then the local
18
- * account-switcher fallback chain.
16
+ * Prefers API `name.displayName`, then the normalized handle.
19
17
  */
20
- export declare const getDisplayName: (user: DisplayNameUserShape | null | undefined, locale?: string) => string;
18
+ export declare const getDisplayName: (user: DisplayNameUserShape | null | undefined) => string;
21
19
  /**
22
20
  * Gets a short display name (first token) for compact UI.
23
21
  */
24
- export declare const getShortDisplayName: (user: DisplayNameUserShape | null | undefined, locale?: string) => string;
22
+ export declare const getShortDisplayName: (user: DisplayNameUserShape | null | undefined) => string;
25
23
  //# sourceMappingURL=userUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"userUtils.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/userUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,KAAG,MAezE,CAAC;AAQF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC7C,SAAS,MAAM,KACd,MAQF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC7C,SAAS,MAAM,KACd,MAqBF,CAAC"}
1
+ {"version":3,"file":"userUtils.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/userUtils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEjF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,KAAG,MAezE,CAAC;AAQF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,KAC5C,MAIF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,KAC5C,MAcF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ProfileScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAe3D,UAAU,kBAAmB,SAAQ,eAAe;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoS/C,CAAC;AAuCF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ProfileScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ProfileScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAe3D,UAAU,kBAAmB,SAAQ,eAAe;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkT/C,CAAC;AAuCF,eAAe,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/backgroundSession.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgE/C,yEAAyE;AACzE,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqF5F"}
1
+ {"version":3,"file":"backgroundSession.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/backgroundSession.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyG/C,yEAAyE;AACzE,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqF5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBackgroundSessionSync.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/useBackgroundSessionSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,6BAA6B;IAC5C,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,EAAE,6BAA6B,GAAG,IAAI,CAiDtC"}
1
+ {"version":3,"file":"useBackgroundSessionSync.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/useBackgroundSessionSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,6BAA6B;IAC5C,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,wDAAwD;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,EAAE,6BAA6B,GAAG,IAAI,CAgDtC"}
@@ -2,9 +2,8 @@
2
2
  * User display name and date formatting utilities.
3
3
  *
4
4
  * Display-name helpers follow the API contract: prefer `name.displayName` when
5
- * present, otherwise fall back to the normalized handle. The legacy
6
- * `getAccountDisplayName` chain is only used as a last resort for local
7
- * account-switcher surfaces without a handle.
5
+ * present, otherwise fall back to the normalized handle via
6
+ * `getNormalizedUserHandle`.
8
7
  */
9
8
  import { type DisplayNameUserShape } from '@oxyhq/core';
10
9
  /**
@@ -14,12 +13,11 @@ export declare const formatDate: (dateString: string | undefined | null | Date)
14
13
  /**
15
14
  * Gets a display name from user data.
16
15
  *
17
- * Prefers API `name.displayName`, then the normalized handle, then the local
18
- * account-switcher fallback chain.
16
+ * Prefers API `name.displayName`, then the normalized handle.
19
17
  */
20
- export declare const getDisplayName: (user: DisplayNameUserShape | null | undefined, locale?: string) => string;
18
+ export declare const getDisplayName: (user: DisplayNameUserShape | null | undefined) => string;
21
19
  /**
22
20
  * Gets a short display name (first token) for compact UI.
23
21
  */
24
- export declare const getShortDisplayName: (user: DisplayNameUserShape | null | undefined, locale?: string) => string;
22
+ export declare const getShortDisplayName: (user: DisplayNameUserShape | null | undefined) => string;
25
23
  //# sourceMappingURL=userUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"userUtils.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/userUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,KAAG,MAezE,CAAC;AAQF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC7C,SAAS,MAAM,KACd,MAQF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC7C,SAAS,MAAM,KACd,MAqBF,CAAC"}
1
+ {"version":3,"file":"userUtils.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/userUtils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEjF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,KAAG,MAezE,CAAC;AAQF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,KAC5C,MAIF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,oBAAoB,GAAG,IAAI,GAAG,SAAS,KAC5C,MAcF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "24.0.0",
3
+ "version": "24.0.2",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -148,7 +148,7 @@
148
148
  "@commitlint/cli": "^21.2.1",
149
149
  "@commitlint/config-conventional": "^21.2.0",
150
150
  "@expo/vector-icons": "^15.0.3",
151
- "@oxyhq/core": "15.0.0",
151
+ "@oxyhq/core": "15.0.1",
152
152
  "@react-native-async-storage/async-storage": "^2.0.0",
153
153
  "@react-native-community/netinfo": "^11.4.1",
154
154
  "@react-native/babel-preset": "^0.86.0",
@@ -135,8 +135,13 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
135
135
  // title (a stable "Profile" fallback before it resolves), so the banner +
136
136
  // overlapping avatar scroll as content UNDER the shared gradient nav bar.
137
137
  // `onImage` tone keeps the title + close legible over the colored banner.
138
+ // Ends in a string, deliberately: `getNormalizedUserHandle` answers `null` for a
139
+ // user it cannot normalise, and letting that null travel makes this `string | null`
140
+ // — which does not satisfy `Avatar.name` and does not typecheck. Empty is the honest
141
+ // bottom of the chain and every reader below already treats it as falsy
142
+ // (`displayName || t('profile.title')`), so nothing renders a literal "null".
138
143
  const displayName = profile
139
- ? (profile.name?.displayName ?? getNormalizedUserHandle(profile))
144
+ ? (profile.name?.displayName ?? getNormalizedUserHandle(profile) ?? '')
140
145
  : username || '';
141
146
  useSurfaceHeader({
142
147
  title: displayName || t('profile.title'),
@@ -191,8 +196,17 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
191
196
  <View style={styles.avatarRow} className="px-screen-margin">
192
197
  <View style={styles.avatarWrapper} className="border-bg bg-bg rounded-radius-max">
193
198
  <Avatar
194
- source={profile?.avatar ? oxyServices.getFileDownloadUrl(profile.avatar, 'thumb') : undefined}
195
- name={displayName || username}
199
+ // `getFileDownloadUrl` answers `null` for a reference it cannot
200
+ // resolve, and `Avatar.source` takes `undefined` for "no picture" —
201
+ // the two spellings of absent do not meet, so the coalesce is the
202
+ // whole fix. Without it this file does not typecheck, which blocks
203
+ // `bun run build` and therefore every publish of this package.
204
+ source={
205
+ profile?.avatar
206
+ ? (oxyServices.getFileDownloadUrl(profile.avatar, 'thumb') ?? undefined)
207
+ : undefined
208
+ }
209
+ name={displayName}
196
210
  size={AVATAR_SIZE}
197
211
  />
198
212
  </View>
@@ -56,12 +56,20 @@ function fakeOxy(
56
56
  * Re-import the module per test: it memoises the resolved native module, which is
57
57
  * correct in production (one resolution per process) and must not leak between
58
58
  * cases here.
59
+ *
60
+ * `platform` is explicit because the react-native stub defaults `Platform.OS` to
61
+ * `'web'`, where this feature is gated OFF by design. Every case therefore has to
62
+ * say which platform it is describing — defaulting to `'android'` here, since that
63
+ * is the only platform the feature ships on.
59
64
  */
60
- async function loadModule(native: NativeCalls | null) {
65
+ async function loadModule(native: NativeCalls | null, platform: 'android' | 'web' = 'android') {
61
66
  jest.resetModules();
67
+ requireOptionalNativeModule.mockClear();
62
68
  requireOptionalNativeModule.mockReturnValue(
63
69
  native ? { put: native.put, clear: native.clear, peek: native.peek } : null,
64
70
  );
71
+ const { Platform } = await import('react-native');
72
+ Platform.OS = platform;
65
73
  return import('../backgroundSession');
66
74
  }
67
75
 
@@ -145,13 +153,18 @@ describe('syncBackgroundSession', () => {
145
153
  });
146
154
 
147
155
  expect(provision).toHaveBeenCalledTimes(1);
148
- expect(native.put).toHaveBeenCalledWith({
149
- baseUrl: 'https://api.oxy.so',
150
- deviceId: 'device-1',
151
- secret: 'bg-secret',
152
- accountId: 'user-1',
153
- expiresAt: Date.parse(expiresAt),
154
- });
156
+ // Positional scalars, and the ORDER is the contract: the native signature is
157
+ // five parameters, not one object, because an Expo Modules `Record` does not
158
+ // convert in a consumer build of this package and every call was rejected. A
159
+ // silently reordered argument would store a credential that no mint can use, so
160
+ // this assertion is what pins the order — see `OxyBackgroundSessionModule.kt`.
161
+ expect(native.put).toHaveBeenCalledWith(
162
+ 'https://api.oxy.so',
163
+ 'device-1',
164
+ 'bg-secret',
165
+ 'user-1',
166
+ Date.parse(expiresAt),
167
+ );
155
168
  // Same account, so nothing was revoked on the way.
156
169
  expect(native.clear).not.toHaveBeenCalled();
157
170
  });
@@ -316,6 +329,48 @@ describe('a @oxyhq/core too old to provision', () => {
316
329
  });
317
330
  });
318
331
 
332
+ describe('on web', () => {
333
+ /**
334
+ * Web must never provision. The credential is non-rotating and long-lived, which
335
+ * is only safe because native background code is its sole consumer; a browser
336
+ * origin already holds the ROTATING deviceSecret and has no background worker, so
337
+ * a successful provision there is a security DOWNGRADE. The harm lands at the
338
+ * network call, which mints a live server-side credential, so "storage failed
339
+ * anyway" is not a defence.
340
+ *
341
+ * These tests present a native module that IS available, which is the whole
342
+ * point: they fail if the gate ever relies on `requireOptionalNativeModule`
343
+ * returning null on web rather than on the platform check.
344
+ */
345
+ test('does not provision even when a native module IS present', async () => {
346
+ const native = fakeNative(null);
347
+ const { syncBackgroundSession, isBackgroundSessionSupported } = await loadModule(native, 'web');
348
+ const provision = jest.fn(async () => provisioned());
349
+
350
+ expect(isBackgroundSessionSupported()).toBe(false);
351
+ await syncBackgroundSession({
352
+ oxyServices: fakeOxy(provision),
353
+ userId: 'user-1',
354
+ canUsePrivateApi: true,
355
+ isCurrent: () => true,
356
+ });
357
+
358
+ expect(provision).not.toHaveBeenCalled();
359
+ expect(native.put).not.toHaveBeenCalled();
360
+ // Not even a read, so nothing in a browser origin is touched at all.
361
+ expect(native.peek).not.toHaveBeenCalled();
362
+ expect(native.clear).not.toHaveBeenCalled();
363
+ });
364
+
365
+ test('never asks the native registry on web', async () => {
366
+ const { isBackgroundSessionSupported } = await loadModule(fakeNative(null), 'web');
367
+ expect(isBackgroundSessionSupported()).toBe(false);
368
+ // The platform decides BEFORE the registry is consulted, so a web-build
369
+ // native-module mock or a future web shim cannot reopen the gate.
370
+ expect(requireOptionalNativeModule).not.toHaveBeenCalled();
371
+ });
372
+ });
373
+
319
374
  describe('without the native module', () => {
320
375
  test('every entry point is an inert no-op', async () => {
321
376
  const { syncBackgroundSession, clearBackgroundSession, isBackgroundSessionSupported } =
@@ -25,12 +25,16 @@
25
25
  * live in different stores with one writer each, so there is nothing to keep in
26
26
  * lockstep and no state that can disagree.
27
27
  *
28
- * Android only. `requireOptionalNativeModule` resolves `null` on web and on any
29
- * app that does not ship the native module, and every function here degrades to a
30
- * no-op in that case so an app can enable this and lose nothing on other
31
- * platforms.
28
+ * ## Android only, and web is refused rather than merely unsupported
29
+ *
30
+ * Web is gated off by an explicit platform check, not by the native module being
31
+ * absent — provisioning in a browser origin would be a security downgrade, so it
32
+ * must not depend on an incidental fact. See {@link loadNativeModule}. iOS has no
33
+ * implementation yet and resolves to no module, so every function here degrades to
34
+ * a no-op: an app can enable this and lose nothing on other platforms.
32
35
  */
33
36
  import { requireOptionalNativeModule } from 'expo-modules-core';
37
+ import { Platform } from 'react-native';
34
38
  import type { OxyServices } from '@oxyhq/core';
35
39
  import { logger } from '@oxyhq/core';
36
40
 
@@ -40,15 +44,29 @@ import { logger } from '@oxyhq/core';
40
44
  * secret's only reader is one less place it can leak from.
41
45
  */
42
46
  interface OxyBackgroundSessionNativeModule {
43
- /** Store (replacing) the credential. Resolves `false` when it was rejected or storage failed. */
44
- put(credential: {
45
- baseUrl: string;
46
- deviceId: string;
47
- secret: string;
48
- accountId: string;
49
- /** Epoch millis parsed from the server's ISO-8601 string here, not natively. */
50
- expiresAt: number;
51
- }): Promise<boolean>;
47
+ /**
48
+ * Store (replacing) the credential. Resolves `false` when it was rejected or
49
+ * storage failed.
50
+ *
51
+ * Positional scalars rather than one object, matching the native signature — and
52
+ * that shape is load-bearing, not a preference. Passing an object made this an
53
+ * Expo Modules `Record`, and a `Record` does not convert in a consumer build of
54
+ * this package: every call was rejected with `The 1st argument cannot be cast to
55
+ * type CredentialInput (received class ReadableNativeMap)`, so the credential was
56
+ * never stored and background refreshes stayed signed out. See the comment on
57
+ * `AsyncFunction("put")` in `OxyBackgroundSessionModule.kt` for why, and do not
58
+ * restore the object form without proving on a device that a `Record` converts.
59
+ *
60
+ * `expiresAtMs` is epoch millis, parsed from the server's ISO-8601 string here
61
+ * rather than natively.
62
+ */
63
+ put(
64
+ baseUrl: string,
65
+ deviceId: string,
66
+ secret: string,
67
+ accountId: string,
68
+ expiresAtMs: number,
69
+ ): Promise<boolean>;
52
70
  /** Drop the credential and any cached access token. */
53
71
  clear(): Promise<boolean>;
54
72
  /** What is stored: the owning account and its expiry (epoch millis), or `null`. */
@@ -67,7 +85,30 @@ const RENEW_WHEN_REMAINING_MS = 7 * 24 * 60 * 60 * 1000;
67
85
  let nativeModule: OxyBackgroundSessionNativeModule | null | undefined;
68
86
 
69
87
  /**
70
- * Resolve the native module once.
88
+ * Resolve the native module once — and refuse outright on web.
89
+ *
90
+ * ## Web is excluded on purpose, and not merely because the module is absent
91
+ *
92
+ * The credential is deliberately NON-ROTATING and long-lived, which is safe only
93
+ * because native background code is its sole consumer and nobody rotates it. A
94
+ * browser origin already holds the ROTATING `deviceSecret`, and it has no
95
+ * background worker to serve — so provisioning there would add a strictly weaker
96
+ * long-lived secret alongside a stronger short-lived one. That is a downgrade, not
97
+ * a harmless no-op, and the harm lands at the PROVISION CALL (which mints a live
98
+ * server-side credential) regardless of whether anything manages to store it.
99
+ *
100
+ * That property must not rest on `requireOptionalNativeModule` happening to return
101
+ * `null` on web. It does today — the module is registered for android only — but
102
+ * that is an incidental fact a future web shim or a web-build native-module mock
103
+ * would quietly reverse. So the platform is checked FIRST, before we even ask the
104
+ * registry, and every entry point in this module inherits the gate.
105
+ *
106
+ * (A pre-deploy server makes this visible too: oxy-api's `sessionDevice` router
107
+ * applies `requireSameSiteOrigin` path-agnostically, so an unmatched path answers
108
+ * `404` to a native caller but `403 BAD_ORIGIN` to a browser one. Core's degrade is
109
+ * deliberately narrow — `404 → null` only — because a real origin misconfiguration
110
+ * on a CURRENT server also returns 403, and swallowing that would hide a genuine
111
+ * bug. Platform gating belongs here, where the platform is actually known.)
71
112
  *
72
113
  * `requireOptionalNativeModule` (a static import Metro always resolves) rather
73
114
  * than a dynamic `import(moduleName)`: the latter compiles to `require(variable)`
@@ -77,6 +118,10 @@ let nativeModule: OxyBackgroundSessionNativeModule | null | undefined;
77
118
  */
78
119
  function loadNativeModule(): OxyBackgroundSessionNativeModule | null {
79
120
  if (nativeModule === undefined) {
121
+ if (Platform.OS === 'web') {
122
+ nativeModule = null;
123
+ return nativeModule;
124
+ }
80
125
  const native = requireOptionalNativeModule<Partial<OxyBackgroundSessionNativeModule>>(
81
126
  'OxyBackgroundSession',
82
127
  );
@@ -246,13 +291,13 @@ export async function syncBackgroundSession(input: BackgroundSessionSyncInput):
246
291
  return;
247
292
  }
248
293
 
249
- const persisted = await native.put({
250
- baseUrl: oxyServices.getBaseURL(),
251
- deviceId: provisioned.deviceId,
252
- secret: provisioned.secret,
253
- accountId: provisioned.accountId,
294
+ const persisted = await native.put(
295
+ oxyServices.getBaseURL(),
296
+ provisioned.deviceId,
297
+ provisioned.secret,
298
+ provisioned.accountId,
254
299
  expiresAt,
255
- });
300
+ );
256
301
  if (!persisted) {
257
302
  logger.warn(
258
303
  '[backgroundSession] the native store did not retain the background credential; background refreshes stay signed out until the next attempt',
@@ -48,11 +48,10 @@ export function useBackgroundSessionSync({
48
48
  }
49
49
 
50
50
  if (!enabled) {
51
- // An app that turns the feature OFF must not leave a live credential at
52
- // rest for the rest of its 30-day lifetime. The cost is one local store
53
- // open per launch on Android apps that never use the feature, which is
54
- // accepted deliberately: correct revocation is worth more than skipping a
55
- // memoized keystore open.
51
+ // An app that leaves the feature off must not keep a credential THIS app
52
+ // provisioned earlier. The store is package-scoped (see
53
+ // OxyBackgroundSessionStore), so this cannot revoke a sibling Oxy app's
54
+ // credential under the shared UID.
56
55
  void clearBackgroundSession();
57
56
  return;
58
57
  }