@ankhorage/expo-runtime 2.2.1 → 2.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ankhorage/expo-runtime
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2e39b8: Classify Expo authentication browser results into neutral OAuth transport responses.
8
+
9
+ ## 2.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - de6bd74: Add canonical native scheme planning for enabled Expo application targets.
14
+
3
15
  ## 2.2.1
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,5 @@
1
+ import type { AppDeployTargets } from '@ankhorage/contracts/deploy';
2
+ export type ExpoRuntimeNativePlatform = 'android' | 'ios';
3
+ export type ExpoRuntimeNativeSchemeMap = Readonly<Partial<Record<ExpoRuntimeNativePlatform, string>>>;
4
+ export declare function resolveExpoRuntimeNativeSchemeMap(targets: AppDeployTargets): ExpoRuntimeNativeSchemeMap;
5
+ //# sourceMappingURL=nativeLinkingPlanning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeLinkingPlanning.d.ts","sourceRoot":"","sources":["../src/nativeLinkingPlanning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,KAAK,CAAC;AAE1D,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAC/C,OAAO,CAAC,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC,CACnD,CAAC;AAEF,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,gBAAgB,GACxB,0BAA0B,CAW5B"}
@@ -0,0 +1,11 @@
1
+ export function resolveExpoRuntimeNativeSchemeMap(targets) {
2
+ const schemes = {};
3
+ if (targets.android?.enabled && targets.android.scheme !== undefined) {
4
+ schemes.android = targets.android.scheme;
5
+ }
6
+ if (targets.ios?.enabled && targets.ios.scheme !== undefined) {
7
+ schemes.ios = targets.ios.scheme;
8
+ }
9
+ return schemes;
10
+ }
11
+ //# sourceMappingURL=nativeLinkingPlanning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeLinkingPlanning.js","sourceRoot":"","sources":["../src/nativeLinkingPlanning.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,iCAAiC,CAC/C,OAAyB;IAEzB,MAAM,OAAO,GAAuD,EAAE,CAAC;IAEvE,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACrE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;IAC3C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,EAAE,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7D,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IACnC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import type { AppDeployTargets } from '@ankhorage/contracts/deploy';\n\nexport type ExpoRuntimeNativePlatform = 'android' | 'ios';\n\nexport type ExpoRuntimeNativeSchemeMap = Readonly<\n Partial<Record<ExpoRuntimeNativePlatform, string>>\n>;\n\nexport function resolveExpoRuntimeNativeSchemeMap(\n targets: AppDeployTargets,\n): ExpoRuntimeNativeSchemeMap {\n const schemes: Partial<Record<ExpoRuntimeNativePlatform, string>> = {};\n\n if (targets.android?.enabled && targets.android.scheme !== undefined) {\n schemes.android = targets.android.scheme;\n }\n if (targets.ios?.enabled && targets.ios.scheme !== undefined) {\n schemes.ios = targets.ios.scheme;\n }\n\n return schemes;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { AuthOAuthAuthorizationResponse } from '@ankhorage/contracts/auth';
2
+ export declare function resolveExpoOAuthBrowserResult(result: unknown): AuthOAuthAuthorizationResponse;
3
+ export declare function resolveExpoOAuthBrowserException(): AuthOAuthAuthorizationResponse;
4
+ //# sourceMappingURL=oauthBrowserTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthBrowserTransport.d.ts","sourceRoot":"","sources":["../src/oauthBrowserTransport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAEhF,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,OAAO,GAAG,8BAA8B,CAkB7F;AAED,wBAAgB,gCAAgC,IAAI,8BAA8B,CAQjF"}
@@ -0,0 +1,37 @@
1
+ export function resolveExpoOAuthBrowserResult(result) {
2
+ if (!isRecord(result))
3
+ return transportFailed('The Expo authentication browser returned no result.');
4
+ const type = Reflect.get(result, 'type');
5
+ if (type === 'success') {
6
+ const url = Reflect.get(result, 'url');
7
+ return typeof url === 'string' && url.trim().length > 0
8
+ ? { type: 'callback', url }
9
+ : transportFailed('The Expo authentication browser returned no callback URL.');
10
+ }
11
+ if (type === 'cancel') {
12
+ return { type: 'cancelled', reason: 'user_cancelled' };
13
+ }
14
+ if (type === 'dismiss') {
15
+ return { type: 'cancelled', reason: 'browser_dismissed' };
16
+ }
17
+ return transportFailed('The Expo authentication browser did not complete the OAuth redirect.');
18
+ }
19
+ export function resolveExpoOAuthBrowserException() {
20
+ return {
21
+ type: 'error',
22
+ error: {
23
+ code: 'browser_unavailable',
24
+ message: 'The Expo authentication browser could not be opened.',
25
+ },
26
+ };
27
+ }
28
+ function transportFailed(message) {
29
+ return {
30
+ type: 'error',
31
+ error: { code: 'transport_failed', message },
32
+ };
33
+ }
34
+ function isRecord(value) {
35
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
36
+ }
37
+ //# sourceMappingURL=oauthBrowserTransport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthBrowserTransport.js","sourceRoot":"","sources":["../src/oauthBrowserTransport.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,6BAA6B,CAAC,MAAe;IAC3D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QACnB,OAAO,eAAe,CAAC,qDAAqD,CAAC,CAAC;IAEhF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YACrD,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE;YAC3B,CAAC,CAAC,eAAe,CAAC,2DAA2D,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACzD,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC5D,CAAC;IACD,OAAO,eAAe,CAAC,sEAAsE,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,gCAAgC;IAC9C,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE;YACL,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,sDAAsD;SAChE;KACF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE;KAC7C,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { AuthOAuthAuthorizationResponse } from '@ankhorage/contracts/auth';\n\nexport function resolveExpoOAuthBrowserResult(result: unknown): AuthOAuthAuthorizationResponse {\n if (!isRecord(result))\n return transportFailed('The Expo authentication browser returned no result.');\n\n const type = Reflect.get(result, 'type');\n if (type === 'success') {\n const url = Reflect.get(result, 'url');\n return typeof url === 'string' && url.trim().length > 0\n ? { type: 'callback', url }\n : transportFailed('The Expo authentication browser returned no callback URL.');\n }\n if (type === 'cancel') {\n return { type: 'cancelled', reason: 'user_cancelled' };\n }\n if (type === 'dismiss') {\n return { type: 'cancelled', reason: 'browser_dismissed' };\n }\n return transportFailed('The Expo authentication browser did not complete the OAuth redirect.');\n}\n\nexport function resolveExpoOAuthBrowserException(): AuthOAuthAuthorizationResponse {\n return {\n type: 'error',\n error: {\n code: 'browser_unavailable',\n message: 'The Expo authentication browser could not be opened.',\n },\n };\n}\n\nfunction transportFailed(message: string): AuthOAuthAuthorizationResponse {\n return {\n type: 'error',\n error: { code: 'transport_failed', message },\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  export { type ExpoRuntimeConfigPluginOutput, type ExpoRuntimeDependencyMap, type ExpoRuntimeGeneratedAppOutputPlan, type ExpoRuntimeNativeOutputPlan, resolveExpoRuntimeConfigPluginOutput, resolveExpoRuntimeDependencyMap, resolveExpoRuntimeGeneratedAppOutput, resolveExpoRuntimeNativeOutput, } from './generatedAppOutput';
2
2
  export { getExpoBarcodeScannerViewSource } from './generatedSources';
3
3
  export { type ExpoRuntimeLayoutIntegrationPlan, resolveExpoRuntimeLayoutIntegration, } from './layoutIntegrationPlanning';
4
+ export { type ExpoRuntimeNativePlatform, type ExpoRuntimeNativeSchemeMap, resolveExpoRuntimeNativeSchemeMap, } from './nativeLinkingPlanning';
4
5
  export type { ExpoRuntimeAdapterId, ExpoRuntimePlan, ExpoRuntimeProviderId, } from './resolveExpoRuntimePlan';
5
6
  export { resolveExpoRuntimePlan } from './resolveExpoRuntimePlan';
6
7
  //# sourceMappingURL=planning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../src/planning.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,iCAAiC,EACtC,KAAK,2BAA2B,EAChC,oCAAoC,EACpC,+BAA+B,EAC/B,oCAAoC,EACpC,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EACL,KAAK,gCAAgC,EACrC,mCAAmC,GACpC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../src/planning.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,iCAAiC,EACtC,KAAK,2BAA2B,EAChC,oCAAoC,EACpC,+BAA+B,EAC/B,oCAAoC,EACpC,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EACL,KAAK,gCAAgC,EACrC,mCAAmC,GACpC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAC/B,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/planning.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { resolveExpoRuntimeConfigPluginOutput, resolveExpoRuntimeDependencyMap, resolveExpoRuntimeGeneratedAppOutput, resolveExpoRuntimeNativeOutput, } from './generatedAppOutput';
2
2
  export { getExpoBarcodeScannerViewSource } from './generatedSources';
3
3
  export { resolveExpoRuntimeLayoutIntegration, } from './layoutIntegrationPlanning';
4
+ export { resolveExpoRuntimeNativeSchemeMap, } from './nativeLinkingPlanning';
4
5
  export { resolveExpoRuntimePlan } from './resolveExpoRuntimePlan';
5
6
  //# sourceMappingURL=planning.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"planning.js","sourceRoot":"","sources":["../src/planning.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,oCAAoC,EACpC,+BAA+B,EAC/B,oCAAoC,EACpC,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAEL,mCAAmC,GACpC,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["export {\n type ExpoRuntimeConfigPluginOutput,\n type ExpoRuntimeDependencyMap,\n type ExpoRuntimeGeneratedAppOutputPlan,\n type ExpoRuntimeNativeOutputPlan,\n resolveExpoRuntimeConfigPluginOutput,\n resolveExpoRuntimeDependencyMap,\n resolveExpoRuntimeGeneratedAppOutput,\n resolveExpoRuntimeNativeOutput,\n} from './generatedAppOutput';\nexport { getExpoBarcodeScannerViewSource } from './generatedSources';\nexport {\n type ExpoRuntimeLayoutIntegrationPlan,\n resolveExpoRuntimeLayoutIntegration,\n} from './layoutIntegrationPlanning';\nexport type {\n ExpoRuntimeAdapterId,\n ExpoRuntimePlan,\n ExpoRuntimeProviderId,\n} from './resolveExpoRuntimePlan';\nexport { resolveExpoRuntimePlan } from './resolveExpoRuntimePlan';\n"]}
1
+ {"version":3,"file":"planning.js","sourceRoot":"","sources":["../src/planning.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,oCAAoC,EACpC,+BAA+B,EAC/B,oCAAoC,EACpC,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAEL,mCAAmC,GACpC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AAMjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["export {\n type ExpoRuntimeConfigPluginOutput,\n type ExpoRuntimeDependencyMap,\n type ExpoRuntimeGeneratedAppOutputPlan,\n type ExpoRuntimeNativeOutputPlan,\n resolveExpoRuntimeConfigPluginOutput,\n resolveExpoRuntimeDependencyMap,\n resolveExpoRuntimeGeneratedAppOutput,\n resolveExpoRuntimeNativeOutput,\n} from './generatedAppOutput';\nexport { getExpoBarcodeScannerViewSource } from './generatedSources';\nexport {\n type ExpoRuntimeLayoutIntegrationPlan,\n resolveExpoRuntimeLayoutIntegration,\n} from './layoutIntegrationPlanning';\nexport {\n type ExpoRuntimeNativePlatform,\n type ExpoRuntimeNativeSchemeMap,\n resolveExpoRuntimeNativeSchemeMap,\n} from './nativeLinkingPlanning';\nexport type {\n ExpoRuntimeAdapterId,\n ExpoRuntimePlan,\n ExpoRuntimeProviderId,\n} from './resolveExpoRuntimePlan';\nexport { resolveExpoRuntimePlan } from './resolveExpoRuntimePlan';\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/expo-runtime",
3
3
  "type": "module",
4
- "version": "2.2.1",
4
+ "version": "2.4.0",
5
5
  "description": "Declarative runtime integration for Expo apps: maps app capabilities to permissions, packages, config plugins, providers, and adapters",
6
6
  "homepage": "https://github.com/ankhorage/expo-runtime#readme",
7
7
  "bugs": {
@@ -22,7 +22,7 @@
22
22
  "runtime"
23
23
  ],
24
24
  "dependencies": {
25
- "@ankhorage/contracts": "^7.6.0",
25
+ "@ankhorage/contracts": "^7.8.0",
26
26
  "@ankhorage/permissions": "^0.2.1"
27
27
  },
28
28
  "files": [
@@ -48,6 +48,13 @@
48
48
  "import": "./dist/planning.js",
49
49
  "default": "./dist/planning.js"
50
50
  },
51
+ "./oauth-browser": {
52
+ "react-native": "./src/oauthBrowserTransport.ts",
53
+ "browser": "./src/oauthBrowserTransport.ts",
54
+ "types": "./src/oauthBrowserTransport.ts",
55
+ "import": "./dist/oauthBrowserTransport.js",
56
+ "default": "./dist/oauthBrowserTransport.js"
57
+ },
51
58
  "./media-picker": {
52
59
  "react-native": "./src/mediaPicker/index.ts",
53
60
  "browser": "./src/mediaPicker/index.ts",
@@ -0,0 +1,77 @@
1
+ import type { AppDeployTargets } from '@ankhorage/contracts/deploy';
2
+ import { describe, expect, test } from 'bun:test';
3
+
4
+ import { resolveExpoRuntimeNativeSchemeMap } from './nativeLinkingPlanning';
5
+
6
+ const TARGET_CASES: readonly [string, AppDeployTargets, Readonly<Record<string, string>>][] = [
7
+ ['web', { web: { enabled: true } }, {}],
8
+ [
9
+ 'android',
10
+ { android: { enabled: true, package: 'com.ankh.android', scheme: 'ankh-android' } },
11
+ { android: 'ankh-android' },
12
+ ],
13
+ [
14
+ 'ios',
15
+ { ios: { enabled: true, bundleIdentifier: 'com.ankh.ios', scheme: 'ankh-ios' } },
16
+ { ios: 'ankh-ios' },
17
+ ],
18
+ [
19
+ 'web + android',
20
+ {
21
+ web: { enabled: true },
22
+ android: { enabled: true, package: 'com.ankh.android', scheme: 'ankh-android' },
23
+ },
24
+ { android: 'ankh-android' },
25
+ ],
26
+ [
27
+ 'web + ios',
28
+ {
29
+ web: { enabled: true },
30
+ ios: { enabled: true, bundleIdentifier: 'com.ankh.ios', scheme: 'ankh-ios' },
31
+ },
32
+ { ios: 'ankh-ios' },
33
+ ],
34
+ [
35
+ 'android + ios',
36
+ {
37
+ android: { enabled: true, package: 'com.ankh.android', scheme: 'ankh-android' },
38
+ ios: { enabled: true, bundleIdentifier: 'com.ankh.ios', scheme: 'ankh-ios' },
39
+ },
40
+ { android: 'ankh-android', ios: 'ankh-ios' },
41
+ ],
42
+ [
43
+ 'web + android + ios',
44
+ {
45
+ web: { enabled: true },
46
+ android: { enabled: true, package: 'com.ankh.android', scheme: 'ankh-android' },
47
+ ios: { enabled: true, bundleIdentifier: 'com.ankh.ios', scheme: 'ankh-ios' },
48
+ },
49
+ { android: 'ankh-android', ios: 'ankh-ios' },
50
+ ],
51
+ ];
52
+
53
+ describe('resolveExpoRuntimeNativeSchemeMap', () => {
54
+ for (const [name, targets, expected] of TARGET_CASES) {
55
+ test(`derives only enabled native schemes for ${name}`, () => {
56
+ expect(resolveExpoRuntimeNativeSchemeMap(targets)).toEqual(expected);
57
+ });
58
+ }
59
+
60
+ test('does not guess schemes from native application identifiers', () => {
61
+ expect(
62
+ resolveExpoRuntimeNativeSchemeMap({
63
+ android: { enabled: true, package: 'com.ankh.android' },
64
+ ios: { enabled: true, bundleIdentifier: 'com.ankh.ios' },
65
+ }),
66
+ ).toEqual({});
67
+ });
68
+
69
+ test('omits disabled native targets even when they retain stable schemes', () => {
70
+ expect(
71
+ resolveExpoRuntimeNativeSchemeMap({
72
+ android: { enabled: false, package: 'com.ankh.android', scheme: 'ankh-android' },
73
+ ios: { enabled: false, bundleIdentifier: 'com.ankh.ios', scheme: 'ankh-ios' },
74
+ }),
75
+ ).toEqual({});
76
+ });
77
+ });
@@ -0,0 +1,22 @@
1
+ import type { AppDeployTargets } from '@ankhorage/contracts/deploy';
2
+
3
+ export type ExpoRuntimeNativePlatform = 'android' | 'ios';
4
+
5
+ export type ExpoRuntimeNativeSchemeMap = Readonly<
6
+ Partial<Record<ExpoRuntimeNativePlatform, string>>
7
+ >;
8
+
9
+ export function resolveExpoRuntimeNativeSchemeMap(
10
+ targets: AppDeployTargets,
11
+ ): ExpoRuntimeNativeSchemeMap {
12
+ const schemes: Partial<Record<ExpoRuntimeNativePlatform, string>> = {};
13
+
14
+ if (targets.android?.enabled && targets.android.scheme !== undefined) {
15
+ schemes.android = targets.android.scheme;
16
+ }
17
+ if (targets.ios?.enabled && targets.ios.scheme !== undefined) {
18
+ schemes.ios = targets.ios.scheme;
19
+ }
20
+
21
+ return schemes;
22
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ resolveExpoOAuthBrowserException,
5
+ resolveExpoOAuthBrowserResult,
6
+ } from './oauthBrowserTransport';
7
+
8
+ describe('Expo OAuth browser transport', () => {
9
+ test('maps a successful browser redirect to a callback response', () => {
10
+ expect(
11
+ resolveExpoOAuthBrowserResult({ type: 'success', url: 'ankh://auth/callback?code=abc' }),
12
+ ).toEqual({ type: 'callback', url: 'ankh://auth/callback?code=abc' });
13
+ });
14
+
15
+ test('maps user cancellation separately from browser dismissal', () => {
16
+ expect(resolveExpoOAuthBrowserResult({ type: 'cancel' })).toEqual({
17
+ type: 'cancelled',
18
+ reason: 'user_cancelled',
19
+ });
20
+ expect(resolveExpoOAuthBrowserResult({ type: 'dismiss' })).toEqual({
21
+ type: 'cancelled',
22
+ reason: 'browser_dismissed',
23
+ });
24
+ });
25
+
26
+ test.each(['locked', 'opened'])(
27
+ 'maps %s to a transport failure instead of cancellation',
28
+ (type) => {
29
+ expect(resolveExpoOAuthBrowserResult({ type })).toEqual({
30
+ type: 'error',
31
+ error: {
32
+ code: 'transport_failed',
33
+ message: 'The Expo authentication browser did not complete the OAuth redirect.',
34
+ },
35
+ });
36
+ },
37
+ );
38
+
39
+ test('rejects malformed success and unknown results as transport failures', () => {
40
+ expect(resolveExpoOAuthBrowserResult({ type: 'success' })).toMatchObject({
41
+ type: 'error',
42
+ error: { code: 'transport_failed' },
43
+ });
44
+ expect(resolveExpoOAuthBrowserResult(null)).toMatchObject({
45
+ type: 'error',
46
+ error: { code: 'transport_failed' },
47
+ });
48
+ });
49
+
50
+ test('maps a rejected browser invocation to browser unavailable', () => {
51
+ expect(resolveExpoOAuthBrowserException()).toEqual({
52
+ type: 'error',
53
+ error: {
54
+ code: 'browser_unavailable',
55
+ message: 'The Expo authentication browser could not be opened.',
56
+ },
57
+ });
58
+ });
59
+ });
@@ -0,0 +1,42 @@
1
+ import type { AuthOAuthAuthorizationResponse } from '@ankhorage/contracts/auth';
2
+
3
+ export function resolveExpoOAuthBrowserResult(result: unknown): AuthOAuthAuthorizationResponse {
4
+ if (!isRecord(result))
5
+ return transportFailed('The Expo authentication browser returned no result.');
6
+
7
+ const type = Reflect.get(result, 'type');
8
+ if (type === 'success') {
9
+ const url = Reflect.get(result, 'url');
10
+ return typeof url === 'string' && url.trim().length > 0
11
+ ? { type: 'callback', url }
12
+ : transportFailed('The Expo authentication browser returned no callback URL.');
13
+ }
14
+ if (type === 'cancel') {
15
+ return { type: 'cancelled', reason: 'user_cancelled' };
16
+ }
17
+ if (type === 'dismiss') {
18
+ return { type: 'cancelled', reason: 'browser_dismissed' };
19
+ }
20
+ return transportFailed('The Expo authentication browser did not complete the OAuth redirect.');
21
+ }
22
+
23
+ export function resolveExpoOAuthBrowserException(): AuthOAuthAuthorizationResponse {
24
+ return {
25
+ type: 'error',
26
+ error: {
27
+ code: 'browser_unavailable',
28
+ message: 'The Expo authentication browser could not be opened.',
29
+ },
30
+ };
31
+ }
32
+
33
+ function transportFailed(message: string): AuthOAuthAuthorizationResponse {
34
+ return {
35
+ type: 'error',
36
+ error: { code: 'transport_failed', message },
37
+ };
38
+ }
39
+
40
+ function isRecord(value: unknown): value is Record<string, unknown> {
41
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
42
+ }
package/src/planning.ts CHANGED
@@ -13,6 +13,11 @@ export {
13
13
  type ExpoRuntimeLayoutIntegrationPlan,
14
14
  resolveExpoRuntimeLayoutIntegration,
15
15
  } from './layoutIntegrationPlanning';
16
+ export {
17
+ type ExpoRuntimeNativePlatform,
18
+ type ExpoRuntimeNativeSchemeMap,
19
+ resolveExpoRuntimeNativeSchemeMap,
20
+ } from './nativeLinkingPlanning';
16
21
  export type {
17
22
  ExpoRuntimeAdapterId,
18
23
  ExpoRuntimePlan,