@ankhorage/studio 0.11.8 → 0.11.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/callback.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,UA+G7F"}
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/callback.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,UAkI7F"}
@@ -5,38 +5,57 @@ export function getAuthOAuthCallbackTsx(args) {
5
5
  const postSignInTarget = escapeStringLiteral(routeNameToGroupedHref(args.postSignInRoute, 'app'));
6
6
  return `import * as Linking from 'expo-linking';
7
7
  import { Stack, useRouter } from 'expo-router';
8
- import * as WebBrowser from 'expo-web-browser';
9
8
  import { useEffect, useRef, useState } from 'react';
10
- import { ActivityIndicator, Platform, Pressable, StyleSheet, View } from 'react-native';
9
+ import { ActivityIndicator, Pressable, StyleSheet, View } from 'react-native';
11
10
  import { Text, useZoraTheme } from '@ankhorage/zora';
12
11
 
13
12
  import { completeOAuthCallback } from '@/auth/oauth';
14
13
 
15
14
  const SIGN_IN_ROUTE = '${signInTarget}';
16
15
  const POST_SIGN_IN_ROUTE = '${postSignInTarget}';
17
- const webBrowserCompletion =
18
- Platform.OS === 'web' ? WebBrowser.maybeCompleteAuthSession() : null;
19
16
  const callbackScreenOptions = { title: 'Completing sign in' };
20
17
 
18
+ interface ActiveCallbackCompletion {
19
+ callbackUrl: string;
20
+ promise: ReturnType<typeof completeOAuthCallback>;
21
+ }
22
+
23
+ let activeCallbackCompletion: ActiveCallbackCompletion | null = null;
24
+
25
+ function completeOAuthCallbackOnce(callbackUrl: string) {
26
+ if (!activeCallbackCompletion || activeCallbackCompletion.callbackUrl !== callbackUrl) {
27
+ activeCallbackCompletion = {
28
+ callbackUrl,
29
+ promise: completeOAuthCallback(callbackUrl),
30
+ };
31
+ }
32
+ return activeCallbackCompletion.promise;
33
+ }
34
+
21
35
  export default function OAuthCallbackScreen() {
22
36
  const router = useRouter();
23
37
  const callbackUrl = Linking.useURL();
24
38
  const { theme } = useZoraTheme();
25
- const handledRef = useRef(false);
39
+ const handledOutcomeRef = useRef(false);
26
40
  const [message, setMessage] = useState('Completing secure sign in…');
27
41
  const [failed, setFailed] = useState(false);
28
42
 
29
43
  useEffect(() => {
30
- if (webBrowserCompletion?.type === 'success' || handledRef.current) return;
31
-
32
44
  let active = true;
33
45
  void (async () => {
34
46
  const deliveredUrl = callbackUrl ?? (await Linking.getInitialURL());
35
- if (!deliveredUrl || handledRef.current) return;
36
- handledRef.current = true;
47
+ if (!deliveredUrl) {
48
+ if (active && !handledOutcomeRef.current) {
49
+ handledOutcomeRef.current = true;
50
+ setFailed(true);
51
+ setMessage('The OAuth callback URL is unavailable.');
52
+ }
53
+ return;
54
+ }
37
55
 
38
- const outcome = await completeOAuthCallback(deliveredUrl);
39
- if (!active) return;
56
+ const outcome = await completeOAuthCallbackOnce(deliveredUrl);
57
+ if (!active || handledOutcomeRef.current) return;
58
+ handledOutcomeRef.current = true;
40
59
 
41
60
  if (outcome.status === 'authenticated') {
42
61
  router.replace(POST_SIGN_IN_ROUTE);
@@ -1 +1 @@
1
- {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,UAAU,uBAAuB,CAAC,IAAsD;IAC5F,MAAM,YAAY,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC;IAElG,OAAO;;;;;;;;;yBASgB,YAAY;8BACP,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgG7C,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,UAAU,uBAAuB,CAAC,IAAsD;IAC5F,MAAM,YAAY,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC;IAElG,OAAO;;;;;;;;yBAQgB,YAAY;8BACP,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoH7C,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAG5E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,UA6Q9D"}
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAG5E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,UAoU9D"}
@@ -70,7 +70,17 @@ async function runOAuthAuthorization(
70
70
  };
71
71
  }
72
72
 
73
- const redirectUri = resolveOAuthRedirectUri();
73
+ let redirectUri: string;
74
+ try {
75
+ redirectUri = resolveOAuthRedirectUri();
76
+ } catch {
77
+ return {
78
+ status: 'error',
79
+ message: 'The OAuth redirect URI could not be resolved in this environment.',
80
+ recoverable: true,
81
+ };
82
+ }
83
+
74
84
  const started = await oauth.startAuthorization({
75
85
  provider: provider.id,
76
86
  redirectUri,
@@ -101,6 +111,13 @@ async function runOAuthAuthorization(
101
111
  };
102
112
  }
103
113
 
114
+ if (Platform.OS === 'web') {
115
+ return redirectWebAuthorization({
116
+ attemptId: started.data.attemptId,
117
+ authorizationUrl: started.data.authorizationUrl,
118
+ });
119
+ }
120
+
104
121
  let browserResult: WebBrowser.WebBrowserAuthSessionResult;
105
122
  try {
106
123
  browserResult = await WebBrowser.openAuthSessionAsync(
@@ -131,6 +148,42 @@ async function runOAuthAuthorization(
131
148
  return toTransportOutcome(completed);
132
149
  }
133
150
 
151
+ async function redirectWebAuthorization(args: {
152
+ attemptId: string;
153
+ authorizationUrl: string;
154
+ }): Promise<GeneratedOAuthTransportOutcome> {
155
+ const location = getBrowserLocation();
156
+ const assign = location ? Reflect.get(location, 'assign') : undefined;
157
+ if (typeof assign !== 'function') {
158
+ await cancelOAuthAttempt(args.attemptId, 'browser_dismissed');
159
+ await clearTransportAttempt();
160
+ return {
161
+ status: 'error',
162
+ message: 'Web OAuth requires full-page browser navigation.',
163
+ recoverable: true,
164
+ };
165
+ }
166
+
167
+ try {
168
+ Reflect.apply(assign, location, [args.authorizationUrl]);
169
+ return waitForFullPageNavigation();
170
+ } catch {
171
+ await cancelOAuthAttempt(args.attemptId, 'browser_dismissed');
172
+ await clearTransportAttempt();
173
+ return {
174
+ status: 'error',
175
+ message: 'The browser could not navigate to the OAuth provider.',
176
+ recoverable: true,
177
+ };
178
+ }
179
+ }
180
+
181
+ function waitForFullPageNavigation(): Promise<never> {
182
+ return new Promise<never>(() => {
183
+ // Full-page navigation replaces this document before the promise settles.
184
+ });
185
+ }
186
+
134
187
  export async function completeOAuthCallback(
135
188
  callbackUrl: string,
136
189
  ): Promise<GeneratedOAuthTransportOutcome> {
@@ -176,13 +229,10 @@ function resolveOAuthRedirectUri(): string {
176
229
  callbackPath = callbackPath.slice(1);
177
230
  }
178
231
  if (Platform.OS === 'web') {
179
- const location = Reflect.get(globalThis, 'location');
180
- if (
181
- typeof location === 'object' &&
182
- location !== null &&
183
- typeof Reflect.get(location, 'origin') === 'string'
184
- ) {
185
- return new URL(\`/\${callbackPath}\`, Reflect.get(location, 'origin')).toString();
232
+ const location = getBrowserLocation();
233
+ const origin = location ? Reflect.get(location, 'origin') : undefined;
234
+ if (typeof origin === 'string' && origin.length > 0) {
235
+ return new URL(\`/\${callbackPath}\`, origin).toString();
186
236
  }
187
237
  throw new Error('Web OAuth requires a canonical browser origin.');
188
238
  }
@@ -190,6 +240,11 @@ function resolveOAuthRedirectUri(): string {
190
240
  return Linking.createURL(callbackPath);
191
241
  }
192
242
 
243
+ function getBrowserLocation(): object | null {
244
+ const location = Reflect.get(globalThis, 'location');
245
+ return typeof location === 'object' && location !== null ? location : null;
246
+ }
247
+
193
248
  function toTransportOutcome(
194
249
  result: AuthOAuthCompletionResult,
195
250
  ): GeneratedOAuthTransportOutcome {
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/oauth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,UAAU,qBAAqB,CAAC,IAAyB;IAC7D,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO;;;;;;;;;;;;;;;gCAeuB,aAAa;;oCAET,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuP5C,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/oauth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,UAAU,qBAAqB,CAAC,IAAyB;IAC7D,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO;;;;;;;;;;;;;;;gCAeuB,aAAa;;oCAET,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8S5C,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/studio",
3
- "version": "0.11.8",
3
+ "version": "0.11.9",
4
4
  "description": "Studio authoring package for Ankhorage apps",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/studio#readme",