@oxyhq/services 22.0.0 → 22.1.0

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 (69) hide show
  1. package/lib/commonjs/index.js +12 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/OxyAccountDialog.js +27 -529
  4. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAuthChooser.js +881 -0
  6. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -0
  7. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +158 -0
  8. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -0
  9. package/lib/commonjs/ui/context/OxyContext.js +28 -3
  10. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  11. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -2
  12. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +2 -1
  14. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  15. package/lib/commonjs/ui/utils/commonsStoreLinks.js +42 -0
  16. package/lib/commonjs/ui/utils/commonsStoreLinks.js.map +1 -0
  17. package/lib/module/index.js +5 -0
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/ui/components/OxyAccountDialog.js +29 -533
  20. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
  21. package/lib/module/ui/components/OxyAuthChooser.js +878 -0
  22. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -0
  23. package/lib/module/ui/components/oxyAuthChooserStyles.js +155 -0
  24. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -0
  25. package/lib/module/ui/context/OxyContext.js +28 -3
  26. package/lib/module/ui/context/OxyContext.js.map +1 -1
  27. package/lib/module/ui/context/useOxyAccountGraph.js +6 -2
  28. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  29. package/lib/module/ui/hooks/useSwitchableAccounts.js +2 -1
  30. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  31. package/lib/module/ui/utils/commonsStoreLinks.js +37 -0
  32. package/lib/module/ui/utils/commonsStoreLinks.js.map +1 -0
  33. package/lib/typescript/commonjs/index.d.ts +2 -0
  34. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +10 -20
  36. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +60 -0
  38. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -0
  39. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  40. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -0
  43. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts +24 -0
  46. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  47. package/lib/typescript/module/index.d.ts +2 -0
  48. package/lib/typescript/module/index.d.ts.map +1 -1
  49. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +10 -20
  50. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +60 -0
  52. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -0
  53. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  54. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  55. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  56. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -0
  57. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  58. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  59. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts +24 -0
  60. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  61. package/package.json +4 -4
  62. package/src/index.ts +7 -0
  63. package/src/ui/components/OxyAccountDialog.tsx +33 -557
  64. package/src/ui/components/OxyAuthChooser.tsx +883 -0
  65. package/src/ui/components/oxyAuthChooserStyles.ts +152 -0
  66. package/src/ui/context/OxyContext.tsx +31 -3
  67. package/src/ui/context/useOxyAccountGraph.ts +4 -2
  68. package/src/ui/hooks/useSwitchableAccounts.ts +1 -0
  69. package/src/ui/utils/commonsStoreLinks.ts +38 -0
@@ -0,0 +1,152 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ /** High-contrast QR plate background — kept in sync with `OxyAuthChooser`'s QR colors. */
4
+ const QR_PLATE_BG = '#FFFFFF';
5
+
6
+ /**
7
+ * Shared styles for `OxyAuthChooser`'s views. Extracted from `OxyAccountDialog`
8
+ * so both the Dialog-wrapped host and any bare host (e.g. a future
9
+ * auth.oxy.so hub page) render identically without duplicating a StyleSheet.
10
+ */
11
+ export const authChooserStyles = StyleSheet.create({
12
+ rows: {
13
+ width: '100%',
14
+ gap: 8,
15
+ },
16
+ accountRow: {
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ gap: 12,
20
+ borderWidth: StyleSheet.hairlineWidth,
21
+ borderRadius: 16,
22
+ paddingVertical: 10,
23
+ paddingHorizontal: 12,
24
+ },
25
+ rowDisabled: {
26
+ opacity: 0.6,
27
+ },
28
+ avatarRing: {
29
+ borderRadius: 9999,
30
+ borderWidth: 2,
31
+ padding: 1,
32
+ },
33
+ rowMeta: {
34
+ flex: 1,
35
+ minWidth: 0,
36
+ },
37
+ rowName: {
38
+ fontSize: 15,
39
+ fontWeight: '600',
40
+ },
41
+ rowHandle: {
42
+ fontSize: 12.5,
43
+ marginTop: 1,
44
+ },
45
+ addRow: {
46
+ flexDirection: 'row',
47
+ alignItems: 'center',
48
+ gap: 12,
49
+ borderWidth: StyleSheet.hairlineWidth,
50
+ borderStyle: 'dashed',
51
+ borderRadius: 16,
52
+ paddingVertical: 10,
53
+ paddingHorizontal: 12,
54
+ marginTop: 8,
55
+ },
56
+ addBadge: {
57
+ width: 40,
58
+ height: 40,
59
+ borderRadius: 9999,
60
+ borderWidth: StyleSheet.hairlineWidth,
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ },
64
+ footerLinks: {
65
+ alignItems: 'center',
66
+ marginTop: 16,
67
+ },
68
+ footerLink: {
69
+ alignSelf: 'center',
70
+ paddingVertical: 10,
71
+ marginTop: 12,
72
+ },
73
+ linkText: {
74
+ fontSize: 14,
75
+ fontWeight: '600',
76
+ },
77
+ signInBlock: {
78
+ width: '100%',
79
+ },
80
+ primaryButton: {
81
+ width: '100%',
82
+ borderRadius: 14,
83
+ marginTop: 8,
84
+ },
85
+ secondaryButton: {
86
+ width: '100%',
87
+ borderRadius: 14,
88
+ marginTop: 10,
89
+ },
90
+ usernameInput: {
91
+ width: '100%',
92
+ borderWidth: StyleSheet.hairlineWidth,
93
+ borderRadius: 14,
94
+ paddingVertical: 12,
95
+ paddingHorizontal: 14,
96
+ fontSize: 16,
97
+ marginTop: 8,
98
+ },
99
+ dividerRow: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ width: '100%',
103
+ marginVertical: 14,
104
+ },
105
+ dividerLine: {
106
+ flex: 1,
107
+ height: StyleSheet.hairlineWidth,
108
+ },
109
+ dividerText: {
110
+ marginHorizontal: 12,
111
+ fontSize: 13,
112
+ },
113
+ centeredBlock: {
114
+ alignItems: 'center',
115
+ justifyContent: 'center',
116
+ gap: 12,
117
+ paddingVertical: 24,
118
+ },
119
+ mutedText: {
120
+ fontSize: 14,
121
+ textAlign: 'center',
122
+ },
123
+ qrHeadline: {
124
+ fontSize: 16,
125
+ fontWeight: '700',
126
+ textAlign: 'center',
127
+ },
128
+ errorText: {
129
+ fontSize: 14,
130
+ textAlign: 'center',
131
+ },
132
+ errorBanner: {
133
+ flexDirection: 'row',
134
+ alignItems: 'center',
135
+ gap: 8,
136
+ width: '100%',
137
+ borderRadius: 12,
138
+ paddingVertical: 10,
139
+ paddingHorizontal: 12,
140
+ marginBottom: 12,
141
+ },
142
+ errorBannerText: {
143
+ flex: 1,
144
+ fontSize: 13.5,
145
+ lineHeight: 18,
146
+ },
147
+ qrPlate: {
148
+ padding: 16,
149
+ borderRadius: 16,
150
+ backgroundColor: QR_PLATE_BG,
151
+ },
152
+ });
@@ -606,14 +606,41 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
606
606
  [commitSession],
607
607
  );
608
608
 
609
+ // Commit a minted graph-SWITCH session. Same funnel as `handleWebSession` but
610
+ // IN-PLACE: `hubSync: false` so switching accounts never triggers the
611
+ // cross-origin hub-sync full-page redirect. The switch already propagates
612
+ // across tabs/apps via the server's `session_state` socket broadcast.
613
+ const commitSwitchedSession = useCallback(
614
+ async (session: SessionLoginResponse): Promise<void> => {
615
+ if (!session?.user || !session?.sessionId || !session.accessToken) {
616
+ throw new Error('Session response did not include a usable session');
617
+ }
618
+ await commitSession(
619
+ {
620
+ sessionId: session.sessionId,
621
+ accessToken: session.accessToken,
622
+ deviceSecret: session.deviceSecret,
623
+ deviceId: session.deviceId,
624
+ expiresAt: session.expiresAt,
625
+ userId: session.user.id,
626
+ user: session.user,
627
+ },
628
+ { activate: true, hubSync: false },
629
+ );
630
+ },
631
+ [commitSession],
632
+ );
633
+
609
634
  // ── Unified account dialog ─────────────────────────────────────────────────
610
635
  // The single account-chooser + sign-in surface. Built ONCE per provider mount
611
636
  // and bound to the live `oxyServices` + `sessionClient` + this provider's
612
- // `handleWebSession` commit funnel. `handleWebSession` is threaded through a ref
613
- // so the controller keeps a STABLE `commitSession` (rebuilding the controller
614
- // on every commit-identity change would drop its subscription + state).
637
+ // commit funnels. Both funnels are threaded through refs so the controller
638
+ // keeps STABLE commit callbacks (rebuilding the controller on every
639
+ // commit-identity change would drop its subscription + state).
615
640
  const handleWebSessionRef = useRef(handleWebSession);
616
641
  handleWebSessionRef.current = handleWebSession;
642
+ const commitSwitchedSessionRef = useRef(commitSwitchedSession);
643
+ commitSwitchedSessionRef.current = commitSwitchedSession;
617
644
 
618
645
  const accountDialogControllerRef = useRef<AccountDialogController | null>(null);
619
646
  if (!accountDialogControllerRef.current) {
@@ -626,6 +653,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
626
653
  // instant `/auth-session` `auth_update` wake instead of a slow poll.
627
654
  socketFactory: io,
628
655
  commitSession: (session) => handleWebSessionRef.current(session),
656
+ commitSwitchedSession: (session) => commitSwitchedSessionRef.current(session),
629
657
  onSignedIn: () => setAccountDialogOpen(false),
630
658
  openUrl: (url) => {
631
659
  if (isWebBrowser()) {
@@ -13,7 +13,7 @@ interface UseOxyAccountGraphParams {
13
13
  oxyServices: OxyServices;
14
14
  sessionClient: SessionClient;
15
15
  syncFromClient: () => Promise<void>;
16
- commitSession: (input: CommitInput, options: { activate: boolean }) => Promise<void>;
16
+ commitSession: (input: CommitInput, options: { activate: boolean; hubSync?: boolean }) => Promise<void>;
17
17
  queryClient: QueryClient;
18
18
  accountDialogControllerRef: RefObject<AccountDialogController | null>;
19
19
  clearSessionStateRef: RefObject<() => Promise<void>>;
@@ -89,7 +89,9 @@ export function useOxyAccountGraph({
89
89
  userId: result.user.id,
90
90
  user: result.user,
91
91
  },
92
- { activate: true },
92
+ // A switch is IN-PLACE: never run the cross-origin hub-sync full-page
93
+ // redirect. Cross-tab/app propagation rides the `session_state` socket.
94
+ { activate: true, hubSync: false },
93
95
  );
94
96
  await runPostAccountSwitchSideEffects();
95
97
  },
@@ -23,6 +23,7 @@ const EMPTY_SNAPSHOT: AccountDialogSnapshot = {
23
23
  error: null,
24
24
  switchingAccountId: null,
25
25
  signIn: { phase: 'idle', authorizeCode: null, qrPayload: null, expiresAt: null, error: null },
26
+ commonsAvailability: 'unknown',
26
27
  };
27
28
 
28
29
  /**
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Where to send a user who doesn't have Commons installed yet.
3
+ *
4
+ * Commons is not published to either app store as of this writing (no submit
5
+ * profile in its `eas.json`, no store listing) — so these are ALWAYS
6
+ * env-overridable and NEVER hardcoded, and the landing-page fallback exists
7
+ * precisely so a "Get Commons" affordance is never a dead link in the
8
+ * meantime. Once Commons ships to TestFlight/Play, set the two store env vars
9
+ * and every consumer picks them up with no code change.
10
+ */
11
+
12
+ /** Real per-platform store URLs, or `undefined` where not yet configured. */
13
+ export interface CommonsStoreLinks {
14
+ ios: string | undefined;
15
+ android: string | undefined;
16
+ }
17
+
18
+ /** Per-platform Commons store URLs, from env — `undefined` when unset. */
19
+ export function getCommonsStoreLinks(): CommonsStoreLinks {
20
+ return {
21
+ ios: process.env.EXPO_PUBLIC_COMMONS_APP_STORE_URL || undefined,
22
+ android: process.env.EXPO_PUBLIC_COMMONS_PLAY_STORE_URL || undefined,
23
+ };
24
+ }
25
+
26
+ /** Placeholder landing page — the fallback while Commons has no store listing. */
27
+ const DEFAULT_COMMONS_LANDING_URL = 'https://oxy.so/commons';
28
+
29
+ /**
30
+ * Resolve the "Get Commons" href for the current platform. Never `undefined`
31
+ * — falls back to a landing page so the affordance is always a working link,
32
+ * store listing or not.
33
+ */
34
+ export function getCommonsAcquisitionUrl(platformOS: 'ios' | 'android' | string): string {
35
+ const links = getCommonsStoreLinks();
36
+ const storeUrl = platformOS === 'ios' ? links.ios : platformOS === 'android' ? links.android : undefined;
37
+ return storeUrl ?? process.env.EXPO_PUBLIC_COMMONS_LANDING_URL ?? DEFAULT_COMMONS_LANDING_URL;
38
+ }