@atlasauth/expo 0.3.0 → 0.5.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 +18 -0
- package/dist/AtlasProvider.d.ts +3 -0
- package/dist/AtlasProvider.js +2 -1
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/app-approval.d.ts +22 -0
- package/dist/app-approval.js +73 -0
- package/dist/app-approval.js.map +1 -0
- package/dist/bridge.d.ts +34 -0
- package/dist/bridge.js +48 -0
- package/dist/bridge.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/social.d.ts +42 -0
- package/dist/social.js +60 -0
- package/dist/social.js.map +1 -0
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @atlasauth/expo changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-11-12
|
|
4
|
+
|
|
5
|
+
- `useAppApproval`, `exchangeLegacyToken` (upgrade bridge), `signInWithApple` / `signInWithGoogle` / `signInWithIdToken`.
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `useAppApproval(id)`: approve-in-your-app for the signed-in app. Reads the pending request, polls while it is pending, and offers `approve(number)` and `deny()` with the shuffled number choices.
|
|
12
|
+
- `exchangeLegacyToken(client, { source, token, clear, storage })`: the upgrade bridge. Trades the legacy session token an existing app still holds for an Atlas session, with no sign-in screen.
|
|
13
|
+
- `signInWithApple`, `signInWithGoogle` and `signInWithIdToken`: native system-sheet sign-in over the provider's client.
|
|
14
|
+
|
|
15
|
+
## 0.4.0 — 2026-10-26
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `<AtlasProvider>` provides the bearer client it builds over the token cache to the policy hooks.
|
|
20
|
+
|
|
3
21
|
## 0.3.0 — 2026-10-24
|
|
4
22
|
Requires `@atlasauth/react-native` with policy acceptance (new dependency).
|
|
5
23
|
- **Policy acceptance** (terms of service, privacy, any policy the app defines). `<PolicyConsent>`, `<PolicyClickwrap>`,
|
package/dist/AtlasProvider.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
|
+
import { type AttestationProvider } from '@atlasauth/js';
|
|
2
3
|
import { type AtlasProviderProps as ReactAtlasProviderProps } from '@atlasauth/react';
|
|
3
4
|
import { type TokenCache } from './token-cache';
|
|
4
5
|
/**
|
|
@@ -39,5 +40,7 @@ export interface AtlasProviderProps extends Omit<ReactAtlasProviderProps, 'fetch
|
|
|
39
40
|
tokenKey?: string;
|
|
40
41
|
/** Injectable for tests; defaults to the platform `fetch`. */
|
|
41
42
|
fetchImpl?: typeof fetch;
|
|
43
|
+
/** App Attest / Play Integrity provider, once the instance requires app attestation. Read once. */
|
|
44
|
+
attestation?: AttestationProvider;
|
|
42
45
|
}
|
|
43
46
|
export declare function AtlasProvider(props: AtlasProviderProps): ReactElement;
|
package/dist/AtlasProvider.js
CHANGED
|
@@ -8,7 +8,7 @@ const cache_fetch_1 = require("./cache-fetch");
|
|
|
8
8
|
const fapi_context_1 = require("./fapi-context");
|
|
9
9
|
const token_cache_1 = require("./token-cache");
|
|
10
10
|
function AtlasProvider(props) {
|
|
11
|
-
const { tokenCache, tokenKey = token_cache_1.DEFAULT_SESSION_KEY, fetchImpl, ...rest } = props;
|
|
11
|
+
const { tokenCache, tokenKey = token_cache_1.DEFAULT_SESSION_KEY, fetchImpl, attestation, ...rest } = props;
|
|
12
12
|
// Built exactly once — the initializer runs on first render only, so the cache
|
|
13
13
|
// (and the default in-memory one, when no adapter is passed) survives
|
|
14
14
|
// re-renders rather than being re-created and losing the session every render.
|
|
@@ -24,6 +24,7 @@ function AtlasProvider(props) {
|
|
|
24
24
|
baseUrl: (0, js_1.resolveFrontendApi)(rest.frontendApi, { component: '<AtlasProvider> (expo)', allowSameOrigin: false }),
|
|
25
25
|
fetchImpl: cacheFetch,
|
|
26
26
|
locale: rest.locale,
|
|
27
|
+
attestation,
|
|
27
28
|
}),
|
|
28
29
|
// Sign-out tombstones persist beside the token (there is no localStorage).
|
|
29
30
|
tombstoneStore: (0, token_cache_1.tombstoneStoreFor)(cache),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AtlasProvider.js","sourceRoot":"","sources":["../src/AtlasProvider.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"AtlasProvider.js","sourceRoot":"","sources":["../src/AtlasProvider.tsx"],"names":[],"mappings":";;AA0DA,sCA+BC;AAzFD,iCAAmE;AACnE,sCAAyF;AACzF,4CAG0B;AAC1B,+CAAiD;AACjD,iDAAiD;AACjD,+CAKuB;AA6CvB,SAAgB,aAAa,CAAC,KAAyB;IACrD,MAAM,EAAE,UAAU,EAAE,QAAQ,GAAG,iCAAmB,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAE9F,+EAA+E;IAC/E,sEAAsE;IACtE,+EAA+E;IAC/E,MAAM,CAAC,MAAM,CAAC,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE;QAC7B,MAAM,KAAK,GAAG,UAAU,IAAI,IAAA,sCAAwB,GAAE,CAAC;QACvD,MAAM,UAAU,GAAG,IAAA,8BAAgB,EAAC,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,IAAI,KAAK,EAAE,CAAC,CAAC;QAC7F,OAAO;YACL,UAAU;YACV,qFAAqF;YACrF,0EAA0E;YAC1E,IAAI,EAAE,IAAI,eAAU,CAAC;gBACnB,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,OAAO,EAAE,IAAA,uBAAkB,EAAC,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,wBAAwB,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;gBAC9G,SAAS,EAAE,UAAU;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,WAAW;aACZ,CAAC;YACF,2EAA2E;YAC3E,cAAc,EAAE,IAAA,+BAAiB,EAAC,KAAK,CAAC;SACzC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,IAAA,qBAAa,EAAC,qBAAkB,EAAE;QACvC,GAAG,IAAI;QACP,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,MAAM,CAAC,cAAc;QAC5D,SAAS,EAAE,MAAM,CAAC,UAAU;QAC5B,QAAQ,EAAE,IAAA,qBAAa,EAAC,8BAAe,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC;KACzF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AppApprovalDetails, type FapiClient } from '@atlasauth/js';
|
|
2
|
+
export interface AppApprovalController {
|
|
3
|
+
/** The request, or null while loading / when it does not exist. */
|
|
4
|
+
details: AppApprovalDetails | null;
|
|
5
|
+
loading: boolean;
|
|
6
|
+
/** Approve with the number the person tapped. Resolves true when the answer was accepted. */
|
|
7
|
+
approve(number: string): Promise<boolean>;
|
|
8
|
+
/** "This was not me". */
|
|
9
|
+
deny(): Promise<boolean>;
|
|
10
|
+
error: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** Controller logic, separated from React so it is testable with a fake client. */
|
|
13
|
+
export declare function createAppApprovalActions(client: FapiClient, id: string): {
|
|
14
|
+
load: () => Promise<AppApprovalDetails | null>;
|
|
15
|
+
approve: (number: string) => Promise<boolean>;
|
|
16
|
+
deny: () => Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* @param id the approval id from your push or webhook; null does nothing.
|
|
20
|
+
* @param pollMs while the request is pending, re-read it this often (default 3000; 0 disables).
|
|
21
|
+
*/
|
|
22
|
+
export declare function useAppApproval(id: string | null, pollMs?: number): AppApprovalController;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAppApprovalActions = createAppApprovalActions;
|
|
4
|
+
exports.useAppApproval = useAppApproval;
|
|
5
|
+
/**
|
|
6
|
+
* Approve-in-your-app for the signed-in app: read the pending request (its id
|
|
7
|
+
* arrives in your push notification, or by polling), show the person the
|
|
8
|
+
* shuffled number choices, and answer. A thin hook over the `@atlasauth/js`
|
|
9
|
+
* calls, bound to the provider's bearer-authenticated client.
|
|
10
|
+
*/
|
|
11
|
+
const react_1 = require("react");
|
|
12
|
+
const js_1 = require("@atlasauth/js");
|
|
13
|
+
const policies_1 = require("./policies");
|
|
14
|
+
/** Controller logic, separated from React so it is testable with a fake client. */
|
|
15
|
+
function createAppApprovalActions(client, id) {
|
|
16
|
+
return {
|
|
17
|
+
load: () => (0, js_1.getAppApproval)(client, id),
|
|
18
|
+
approve: async (number) => (await (0, js_1.approveSignIn)(client, { id, number })).ok,
|
|
19
|
+
deny: async () => (await (0, js_1.denySignIn)(client, id)).ok,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param id the approval id from your push or webhook; null does nothing.
|
|
24
|
+
* @param pollMs while the request is pending, re-read it this often (default 3000; 0 disables).
|
|
25
|
+
*/
|
|
26
|
+
function useAppApproval(id, pollMs = 3000) {
|
|
27
|
+
const client = (0, policies_1.useExpoFapiClient)();
|
|
28
|
+
const [details, setDetails] = (0, react_1.useState)(null);
|
|
29
|
+
const [loading, setLoading] = (0, react_1.useState)(id !== null);
|
|
30
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
31
|
+
const actions = (0, react_1.useRef)(null);
|
|
32
|
+
actions.current = id ? createAppApprovalActions(client, id) : null;
|
|
33
|
+
(0, react_1.useEffect)(() => {
|
|
34
|
+
if (!id)
|
|
35
|
+
return;
|
|
36
|
+
let live = true;
|
|
37
|
+
let timer;
|
|
38
|
+
const tick = async () => {
|
|
39
|
+
const next = await actions.current?.load();
|
|
40
|
+
if (!live)
|
|
41
|
+
return;
|
|
42
|
+
setDetails(next ?? null);
|
|
43
|
+
setLoading(false);
|
|
44
|
+
if (pollMs > 0 && next?.status === 'pending')
|
|
45
|
+
timer = setTimeout(tick, pollMs);
|
|
46
|
+
};
|
|
47
|
+
setLoading(true);
|
|
48
|
+
void tick();
|
|
49
|
+
return () => { live = false; if (timer)
|
|
50
|
+
clearTimeout(timer); };
|
|
51
|
+
}, [id, pollMs]);
|
|
52
|
+
const answer = (0, react_1.useCallback)(async (run) => {
|
|
53
|
+
const ok = (await run()) ?? false;
|
|
54
|
+
setError(ok ? null : 'The answer was not accepted.');
|
|
55
|
+
if (ok)
|
|
56
|
+
setDetails((d) => (d ? { ...d, status: 'approved' } : d));
|
|
57
|
+
return ok;
|
|
58
|
+
}, []);
|
|
59
|
+
return {
|
|
60
|
+
details,
|
|
61
|
+
loading,
|
|
62
|
+
error,
|
|
63
|
+
approve: (n) => answer(() => actions.current?.approve(n)),
|
|
64
|
+
deny: async () => {
|
|
65
|
+
const ok = (await actions.current?.deny()) ?? false;
|
|
66
|
+
setError(ok ? null : 'The answer was not accepted.');
|
|
67
|
+
if (ok)
|
|
68
|
+
setDetails((d) => (d ? { ...d, status: 'denied' } : d));
|
|
69
|
+
return ok;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=app-approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-approval.js","sourceRoot":"","sources":["../src/app-approval.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade bridge for apps that already have signed-in users: trade the legacy
|
|
3
|
+
* session token the app still holds for an Atlas session, with no sign-in screen.
|
|
4
|
+
*
|
|
5
|
+
* The exchange asks for the tokens in the response body (a native app has no
|
|
6
|
+
* cookies). Pass the provider's client (`useExpoFapiClient()`) and the session
|
|
7
|
+
* JWT is stored by the token cache like any other; pass `storage` as well to keep
|
|
8
|
+
* the refresh token for `createNativeSessionManager`.
|
|
9
|
+
*/
|
|
10
|
+
import { type FapiClient, type NativeSession } from '@atlasauth/js';
|
|
11
|
+
import type { TokenStorage } from './token-cache';
|
|
12
|
+
export type LegacyTokenExchangeOutcome = {
|
|
13
|
+
status: 'none';
|
|
14
|
+
} | {
|
|
15
|
+
status: 'complete';
|
|
16
|
+
session: NativeSession;
|
|
17
|
+
userId: string;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'needs_second_factor' | 'needs_mfa_enrollment';
|
|
20
|
+
attemptId: string;
|
|
21
|
+
} | {
|
|
22
|
+
status: 'refused';
|
|
23
|
+
outcome: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function exchangeLegacyToken(client: FapiClient, input: {
|
|
26
|
+
/** The legacy provider key, e.g. `better_auth`, `firebase`, `supabase`. */
|
|
27
|
+
source: string;
|
|
28
|
+
/** The token the app holds; absent means nothing to do. */
|
|
29
|
+
token?: string | null;
|
|
30
|
+
/** Called after a success or a final refusal: delete the legacy token here. */
|
|
31
|
+
clear?: () => void | Promise<void>;
|
|
32
|
+
/** Where to keep the refresh token (a slot dedicated to the native session). */
|
|
33
|
+
storage?: TokenStorage;
|
|
34
|
+
}): Promise<LegacyTokenExchangeOutcome>;
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exchangeLegacyToken = exchangeLegacyToken;
|
|
4
|
+
/**
|
|
5
|
+
* Upgrade bridge for apps that already have signed-in users: trade the legacy
|
|
6
|
+
* session token the app still holds for an Atlas session, with no sign-in screen.
|
|
7
|
+
*
|
|
8
|
+
* The exchange asks for the tokens in the response body (a native app has no
|
|
9
|
+
* cookies). Pass the provider's client (`useExpoFapiClient()`) and the session
|
|
10
|
+
* JWT is stored by the token cache like any other; pass `storage` as well to keep
|
|
11
|
+
* the refresh token for `createNativeSessionManager`.
|
|
12
|
+
*/
|
|
13
|
+
const js_1 = require("@atlasauth/js");
|
|
14
|
+
/** Refusals after which the legacy token will never exchange, so it is safe to delete. */
|
|
15
|
+
const FINAL = new Set(['invalid', 'signature_invalid', 'expired', 'reused', 'user_unavailable', 'user_unknown']);
|
|
16
|
+
async function exchangeLegacyToken(client, input) {
|
|
17
|
+
if (!input.token)
|
|
18
|
+
return { status: 'none' };
|
|
19
|
+
const clear = async () => { try {
|
|
20
|
+
await input.clear?.();
|
|
21
|
+
}
|
|
22
|
+
catch { /* best effort */ } };
|
|
23
|
+
let response;
|
|
24
|
+
try {
|
|
25
|
+
response = await client.post((0, js_1.migrationBrowserExchangePath)(input.source), { token: input.token, delivery: 'body' });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { status: 'refused', outcome: 'error' };
|
|
29
|
+
}
|
|
30
|
+
const d = response.data;
|
|
31
|
+
if (response.ok && d?.jwt && d.session_id) {
|
|
32
|
+
const session = { sessionToken: d.jwt, refreshToken: d.refresh_token ?? '', sessionId: d.session_id, expiresInSeconds: d.expires_in ?? 0 };
|
|
33
|
+
if (input.storage && session.refreshToken)
|
|
34
|
+
await input.storage.setToken((0, js_1.serializeNativeSession)(session));
|
|
35
|
+
await clear();
|
|
36
|
+
return { status: 'complete', session, userId: d.user_id ?? '' };
|
|
37
|
+
}
|
|
38
|
+
if (response.ok && d?.id && (d.status === 'needs_second_factor' || d.status === 'needs_mfa_enrollment')) {
|
|
39
|
+
await clear();
|
|
40
|
+
return { status: d.status, attemptId: d.id };
|
|
41
|
+
}
|
|
42
|
+
const reason = response.errors[0]?.meta?.bridge_outcome;
|
|
43
|
+
const outcome = typeof reason === 'string' ? reason : 'error';
|
|
44
|
+
if (FINAL.has(outcome))
|
|
45
|
+
await clear();
|
|
46
|
+
return { status: 'refused', outcome };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":";;AAqBA,kDAuCC;AA5DD;;;;;;;;GAQG;AACH,sCAA0H;AAS1H,0FAA0F;AAC1F,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,CAAC,CAAC,CAAC;AAE1G,KAAK,UAAU,mBAAmB,CACvC,MAAkB,EAClB,KASC;IAED,IAAI,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,IAAI,CAAC;QAAC,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAC1B,IAAA,iCAA4B,EAAC,KAAK,CAAC,MAAM,CAAC,EAC1C,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CACzC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC;IACxB,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAkB,EAAE,YAAY,EAAE,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC,aAAa,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QAC1J,IAAI,KAAK,CAAC,OAAO,IAAI,OAAO,CAAC,YAAY;YAAE,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAA,2BAAsB,EAAC,OAAO,CAAC,CAAC,CAAC;QACzG,MAAM,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,qBAAqB,IAAI,CAAC,CAAC,MAAM,KAAK,sBAAsB,CAAC,EAAE,CAAC;QACxG,MAAM,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;IAC/C,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC9D,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,MAAM,KAAK,EAAE,CAAC;IACtC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,6 @@ export * from './AtlasProvider';
|
|
|
4
4
|
export * from './native-session';
|
|
5
5
|
export { useAuth, useUser, useSession, useSessionList, useOrganization, SignedIn, SignedOut, Protect, AtlasLoading, AtlasLoaded, } from '@atlasauth/react';
|
|
6
6
|
export * from './policies';
|
|
7
|
+
export * from './social';
|
|
8
|
+
export * from './bridge';
|
|
9
|
+
export * from './app-approval';
|
package/dist/index.js
CHANGED
|
@@ -46,4 +46,8 @@ Object.defineProperty(exports, "AtlasLoading", { enumerable: true, get: function
|
|
|
46
46
|
Object.defineProperty(exports, "AtlasLoaded", { enumerable: true, get: function () { return react_1.AtlasLoaded; } });
|
|
47
47
|
// Policy acceptance (terms, privacy, any policy): native components + hooks bound to this provider.
|
|
48
48
|
__exportStar(require("./policies"), exports);
|
|
49
|
+
// Native social sign-in, the upgrade bridge for apps with existing users, and approve-in-your-app.
|
|
50
|
+
__exportStar(require("./social"), exports);
|
|
51
|
+
__exportStar(require("./bridge"), exports);
|
|
52
|
+
__exportStar(require("./app-approval"), exports);
|
|
49
53
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2EAA2E;AAC3E,kEAAkE;AAClE,gDAA8B;AAC9B,gDAA8B;AAC9B,kDAAgC;AAEhC,8EAA8E;AAC9E,mDAAiC;AAEjC;;;;;;;;;;GAUG;AACH,0CAW0B;AAVxB,gGAAA,OAAO,OAAA;AACP,gGAAA,OAAO,OAAA;AACP,mGAAA,UAAU,OAAA;AACV,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,iGAAA,QAAQ,OAAA;AACR,kGAAA,SAAS,OAAA;AACT,gGAAA,OAAO,OAAA;AACP,qGAAA,YAAY,OAAA;AACZ,oGAAA,WAAW,OAAA;AAGb,oGAAoG;AACpG,6CAA2B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2EAA2E;AAC3E,kEAAkE;AAClE,gDAA8B;AAC9B,gDAA8B;AAC9B,kDAAgC;AAEhC,8EAA8E;AAC9E,mDAAiC;AAEjC;;;;;;;;;;GAUG;AACH,0CAW0B;AAVxB,gGAAA,OAAO,OAAA;AACP,gGAAA,OAAO,OAAA;AACP,mGAAA,UAAU,OAAA;AACV,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,iGAAA,QAAQ,OAAA;AACR,kGAAA,SAAS,OAAA;AACT,gGAAA,OAAO,OAAA;AACP,qGAAA,YAAY,OAAA;AACZ,oGAAA,WAAW,OAAA;AAGb,oGAAoG;AACpG,6CAA2B;AAE3B,mGAAmG;AACnG,2CAAyB;AACzB,2CAAyB;AACzB,iDAA+B"}
|
package/dist/social.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Google / Apple sign-in for the provider's client: the system sheet
|
|
3
|
+
* yields a provider ID token, which is sent to Atlas (`POST
|
|
4
|
+
* /v1/client/sign_ins/id_token`) and traded for a session. The provider's token
|
|
5
|
+
* cache stores the returned session JWT.
|
|
6
|
+
*/
|
|
7
|
+
import { type FapiClient, type FieldError } from '@atlasauth/js';
|
|
8
|
+
export interface NativeSocialResult {
|
|
9
|
+
status: 'complete' | 'needs_more' | 'error' | 'cancelled';
|
|
10
|
+
jwt?: string;
|
|
11
|
+
attemptId?: string;
|
|
12
|
+
errors: FieldError[];
|
|
13
|
+
}
|
|
14
|
+
/** Send a provider ID token (with the nonce it was minted with) and finish the sign-in. */
|
|
15
|
+
export declare function signInWithIdToken(client: FapiClient, input: {
|
|
16
|
+
provider: string;
|
|
17
|
+
idToken: string;
|
|
18
|
+
nonce?: string;
|
|
19
|
+
}): Promise<NativeSocialResult>;
|
|
20
|
+
/** The slice of `expo-apple-authentication` used here. */
|
|
21
|
+
export interface AppleAuthenticationLike {
|
|
22
|
+
signInAsync(options: {
|
|
23
|
+
requestedScopes?: unknown[];
|
|
24
|
+
nonce?: string;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
identityToken: string | null;
|
|
27
|
+
}>;
|
|
28
|
+
AppleAuthenticationScope: {
|
|
29
|
+
EMAIL: unknown;
|
|
30
|
+
FULL_NAME: unknown;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Sign in with the Apple system sheet. A dismissed sheet resolves as `cancelled`. */
|
|
34
|
+
export declare function signInWithApple(client: FapiClient, apple: AppleAuthenticationLike): Promise<NativeSocialResult>;
|
|
35
|
+
/** The slice of `@react-native-google-signin/google-signin` used here. */
|
|
36
|
+
export interface GoogleSigninLike {
|
|
37
|
+
signIn(options?: {
|
|
38
|
+
nonce?: string;
|
|
39
|
+
}): Promise<unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** Sign in with the Google system sheet (use `@atlasauth/expo-google-signin` for Play Services checks). */
|
|
42
|
+
export declare function signInWithGoogle(client: FapiClient, google: GoogleSigninLike): Promise<NativeSocialResult>;
|
package/dist/social.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signInWithIdToken = signInWithIdToken;
|
|
4
|
+
exports.signInWithApple = signInWithApple;
|
|
5
|
+
exports.signInWithGoogle = signInWithGoogle;
|
|
6
|
+
/**
|
|
7
|
+
* Native Google / Apple sign-in for the provider's client: the system sheet
|
|
8
|
+
* yields a provider ID token, which is sent to Atlas (`POST
|
|
9
|
+
* /v1/client/sign_ins/id_token`) and traded for a session. The provider's token
|
|
10
|
+
* cache stores the returned session JWT.
|
|
11
|
+
*/
|
|
12
|
+
const js_1 = require("@atlasauth/js");
|
|
13
|
+
/** Send a provider ID token (with the nonce it was minted with) and finish the sign-in. */
|
|
14
|
+
async function signInWithIdToken(client, input) {
|
|
15
|
+
const flow = await (0, js_1.nativeSignIn)(client, input);
|
|
16
|
+
if (flow.errors.length)
|
|
17
|
+
return { status: 'error', errors: flow.errors };
|
|
18
|
+
const attempt = flow.attempt;
|
|
19
|
+
if (!flow.ticket)
|
|
20
|
+
return { status: 'needs_more', ...(attempt?.id ? { attemptId: attempt.id } : {}), errors: [] };
|
|
21
|
+
const ex = await client.post('/v1/client/tickets/exchange', { attempt_id: attempt?.id ?? '', ticket: flow.ticket });
|
|
22
|
+
if (!ex.ok || !ex.data?.jwt)
|
|
23
|
+
return { status: 'error', errors: ex.errors.length ? ex.errors : [{ code: 'EXCHANGE_FAILED', message: 'Could not complete sign-in.' }] };
|
|
24
|
+
return { status: 'complete', jwt: ex.data.jwt, ...(attempt?.id ? { attemptId: attempt.id } : {}), errors: [] };
|
|
25
|
+
}
|
|
26
|
+
/** Sign in with the Apple system sheet. A dismissed sheet resolves as `cancelled`. */
|
|
27
|
+
async function signInWithApple(client, apple) {
|
|
28
|
+
const nonce = (await (0, js_1.mintNativeNonce)(client, 'apple')) ?? undefined;
|
|
29
|
+
if (!nonce)
|
|
30
|
+
return { status: 'error', errors: [{ code: 'NONCE', message: 'Could not start Apple sign-in.' }] };
|
|
31
|
+
let idToken;
|
|
32
|
+
try {
|
|
33
|
+
({ identityToken: idToken } = await apple.signInAsync({ requestedScopes: [apple.AppleAuthenticationScope.EMAIL, apple.AppleAuthenticationScope.FULL_NAME], nonce }));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { status: 'cancelled', errors: [] };
|
|
37
|
+
}
|
|
38
|
+
if (!idToken)
|
|
39
|
+
return { status: 'cancelled', errors: [] };
|
|
40
|
+
return signInWithIdToken(client, { provider: 'apple', idToken, nonce });
|
|
41
|
+
}
|
|
42
|
+
/** Sign in with the Google system sheet (use `@atlasauth/expo-google-signin` for Play Services checks). */
|
|
43
|
+
async function signInWithGoogle(client, google) {
|
|
44
|
+
const nonce = (await (0, js_1.mintNativeNonce)(client, 'google')) ?? undefined;
|
|
45
|
+
if (!nonce)
|
|
46
|
+
return { status: 'error', errors: [{ code: 'NONCE', message: 'Could not start Google sign-in.' }] };
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await google.signIn({ nonce });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { status: 'cancelled', errors: [] };
|
|
53
|
+
}
|
|
54
|
+
const r = response;
|
|
55
|
+
const idToken = r?.data?.idToken ?? r?.idToken;
|
|
56
|
+
if (!idToken)
|
|
57
|
+
return { status: 'cancelled', errors: [] };
|
|
58
|
+
return signInWithIdToken(client, { provider: 'google', idToken, nonce });
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=social.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social.js","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlasauth/expo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@atlasauth/
|
|
8
|
-
"@atlasauth/react": "^0.
|
|
9
|
-
"@atlasauth/
|
|
7
|
+
"@atlasauth/js": "0.38.0",
|
|
8
|
+
"@atlasauth/react": "^0.34.0",
|
|
9
|
+
"@atlasauth/react-native": "^0.7.0"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"react": ">=18",
|
|
@@ -43,12 +43,13 @@
|
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
45
|
"dist",
|
|
46
|
+
"!dist/*.tsbuildinfo",
|
|
46
47
|
"README.md",
|
|
47
48
|
"CHANGELOG.md"
|
|
48
49
|
],
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsc -p tsconfig.build.json",
|
|
51
|
-
"typecheck": "
|
|
52
|
+
"typecheck": "tsgo -p tsconfig.json --noEmit --module preserve --moduleResolution bundler --incremental --tsBuildInfoFile node_modules/.cache/typecheck.tsbuildinfo",
|
|
52
53
|
"test": "vitest run"
|
|
53
54
|
}
|
|
54
55
|
}
|