@octane-xplat/auth 0.8.0 → 0.10.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/README.md +73 -0
- package/dist/native/octane-client-build.json +1 -1
- package/dist/web/octane-client-build.json +1 -1
- package/package.json +19 -15
- package/src/AppleSignInButton.macos.tsrx +28 -30
- package/src/GoogleSignInButton.macos.tsrx +18 -4
- package/src/apple-button.macos.ts +75 -0
- package/src/apple.macos.ts +133 -53
- package/src/apple.web.ts +7 -4
- package/src/google.macos.ts +81 -14
- package/src/google.web.ts +5 -1
- package/src/index.macos.ts +1 -0
- package/src/index.ts +6 -5
- package/src/index.web.ts +6 -5
- package/src/types.ts +19 -4
- package/types/generated/index.d.ts +1 -1
- package/types/generated/index.web.d.ts +1 -1
- package/types/generated/types.d.ts +18 -4
- package/types/index.macos.d.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# `@octane-xplat/auth`
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
pnpm add @octane-xplat/auth
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Sign in with Apple and Google Sign-In for Octane xplat apps — the native
|
|
8
|
+
SDKs (`@nativescript/apple-sign-in`, `@nativescript/google-signin`) on
|
|
9
|
+
iOS/Android, the providers' web SDKs (Apple JS / Google Identity Services)
|
|
10
|
+
in the browser, and AuthenticationServices through the ObjC bridge plus a
|
|
11
|
+
app-configured hosted Google flow on the macOS AppKit host. One `AuthUser`/`AuthCredential`
|
|
12
|
+
contract everywhere.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { appleAuth } from '@octane-xplat/auth'
|
|
16
|
+
|
|
17
|
+
appleAuth.configure({ clientId: 'com.example.app.web' })
|
|
18
|
+
if (appleAuth.supported) {
|
|
19
|
+
const result = await appleAuth.signIn({ scopes: ['email', 'name'] })
|
|
20
|
+
if (result.status === 'success') {
|
|
21
|
+
// Send this credential to your backend for verification.
|
|
22
|
+
const { idToken, user } = result.credential
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each provider object reports `supported` — Apple is `false` on Android —
|
|
28
|
+
and exposes `configure`, `signIn`, and provider extras
|
|
29
|
+
(`appleAuth.getCredentialState`, `googleAuth.signOut`). `SignInResult`
|
|
30
|
+
distinguishes `success` / `cancelled` / `error`; a dismissed sheet is
|
|
31
|
+
`cancelled`, not a throw. Apple uses the official ASAuthorizationAppleIDButton on AppKit; Google
|
|
32
|
+
uses a text trigger for the hosted flow there. The other sign-in buttons are
|
|
33
|
+
platform-authentic components, so render them from platform-suffixed files or accept each
|
|
34
|
+
target's native styling.
|
|
35
|
+
|
|
36
|
+
The following component belongs in an `.ios.tsx` file.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { AppleSignInButton, appleAuth, googleAuth } from '@octane-xplat/auth'
|
|
40
|
+
|
|
41
|
+
export function SignIn() {
|
|
42
|
+
return (
|
|
43
|
+
<AppleSignInButton
|
|
44
|
+
onResult={(result) => {
|
|
45
|
+
if (result.status === 'success') {
|
|
46
|
+
void appleAuth.getCredentialState(result.credential.user.id)
|
|
47
|
+
}
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
// When ending a Google session:
|
|
53
|
+
await googleAuth.signOut()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Web needs registered client IDs; iOS needs the `applesignin` entitlement
|
|
57
|
+
and Google's `GoogleService-Info.plist`/reversed-client-id URL scheme;
|
|
58
|
+
Android needs `google-services.json` and a `serverClientId` for
|
|
59
|
+
`serverAuthCode`. Guide: [Using device
|
|
60
|
+
features](../../docs/platform/platform-services.md). Exercised by
|
|
61
|
+
[`AuthDemo`](../demos/src/AuthDemo.tsrx).
|
|
62
|
+
|
|
63
|
+
On macOS, `supported` reports AuthenticationServices availability, not completed
|
|
64
|
+
provider registration. Apple needs a signed app ID with Sign in with Apple
|
|
65
|
+
enabled and a matching provisioning profile and entitlement. Google needs
|
|
66
|
+
`googleAuth.configure({ hostedFlow })`, where `createRequest` starts a fresh
|
|
67
|
+
backend attempt and `complete` validates/redeems the captured callback on that
|
|
68
|
+
backend, returning `AuthCredential`. A client ID alone is insufficient on
|
|
69
|
+
AppKit. The adapter receives the configured client IDs, scopes, hosted domain,
|
|
70
|
+
and per-attempt nonce; the backend must apply and verify them.
|
|
71
|
+
|
|
72
|
+
See [macOS provider setup](../../docs/platform/platform-services.md#macos-provider-sign-in)
|
|
73
|
+
and the [maintained hosted adapter](../../examples/auth/google-hosted.macos.ts).
|
package/package.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Sign in with Apple + Google Sign-In for Octane xplat — native SDKs on iOS/Android, platform web SDKs in the browser",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"xplat",
|
|
8
|
-
"cross-platform",
|
|
9
|
-
"auth",
|
|
10
|
-
"sign-in",
|
|
6
|
+
"android",
|
|
11
7
|
"apple",
|
|
8
|
+
"auth",
|
|
9
|
+
"cross-platform",
|
|
12
10
|
"google",
|
|
13
|
-
"oauth",
|
|
14
|
-
"nativescript",
|
|
15
11
|
"ios",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
12
|
+
"nativescript",
|
|
13
|
+
"oauth",
|
|
14
|
+
"octane",
|
|
15
|
+
"sign-in",
|
|
16
|
+
"web",
|
|
17
|
+
"xplat"
|
|
18
18
|
],
|
|
19
|
-
"license": "MIT",
|
|
20
19
|
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/auth",
|
|
20
|
+
"license": "MIT",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "
|
|
23
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
24
24
|
"directory": "packages/auth"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
@@ -52,15 +52,17 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@nativescript/apple-sign-in": "2.0.0",
|
|
55
|
-
"@nativescript/google-signin": "2.1.1"
|
|
55
|
+
"@nativescript/google-signin": "2.1.1",
|
|
56
|
+
"@octane-xplat/platform": "0.10.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@nativescript-community/octane": "0.2.4",
|
|
59
60
|
"@nativescript/core": "9.1.2",
|
|
61
|
+
"@octane-xplat/macos-renderer": "0.10.0",
|
|
60
62
|
"@octanejs/vite-plugin": "0.1.61",
|
|
61
63
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
62
64
|
"octane": "0.6.3",
|
|
63
|
-
"tsrx-typegen": "0.2.
|
|
65
|
+
"tsrx-typegen": "0.2.2",
|
|
64
66
|
"typescript": "5.9.3",
|
|
65
67
|
"vite": "8.3.0"
|
|
66
68
|
},
|
|
@@ -81,6 +83,8 @@
|
|
|
81
83
|
"build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
|
|
82
84
|
"test:packed": "node tests/packed-consumer.mjs",
|
|
83
85
|
"typegen": "tsrx-typegen --target octane",
|
|
84
|
-
"pack:check": "tsrx-typegen --pack-check"
|
|
86
|
+
"pack:check": "tsrx-typegen --pack-check",
|
|
87
|
+
"test": "node --test tests/*.test.mjs",
|
|
88
|
+
"typecheck:macos": "tsrx-tsc --noEmit -p tsconfig.macos.json"
|
|
85
89
|
}
|
|
86
90
|
}
|
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
/** @jsxImportSource @xplat/macos
|
|
1
|
+
/** @jsxImportSource @octane-xplat/macos-renderer */
|
|
2
|
+
import { useEffect, useRef } from 'octane'
|
|
2
3
|
import { appleAuth } from './apple.macos'
|
|
3
|
-
import
|
|
4
|
+
import { attachAppleButton } from './apple-button.macos'
|
|
5
|
+
import type { AppleSignInButtonProps } from './types'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
default: 'Sign in with Apple',
|
|
7
|
-
signUp: 'Sign up with Apple',
|
|
8
|
-
continue: 'Continue with Apple',
|
|
9
|
-
} as const
|
|
10
|
-
|
|
11
|
-
/** Sign in with Apple button — macOS leaf. The AppKit renderer has no
|
|
12
|
-
* ASAuthorizationAppleIDButton in its vocabulary, so this is a flexbox row
|
|
13
|
-
* with a click-gesture tap (the same shape as Pressable/controls.macos)
|
|
14
|
-
* that runs the AuthenticationServices flow and reports through `onResult`.
|
|
15
|
-
* onPress on the intrinsic <button> is not used — the universal compile
|
|
16
|
-
* emits DOM-only delegateEvents for it. */
|
|
7
|
+
/** Official ASAuthorizationAppleIDButton, embedded in the leaf's host view. */
|
|
17
8
|
export function AppleSignInButton(props: AppleSignInButtonProps) @{
|
|
9
|
+
const anchor = useRef<any>(null)
|
|
10
|
+
const handle = useRef<ReturnType<typeof attachAppleButton> | null>(null)
|
|
11
|
+
const current = useRef(props)
|
|
12
|
+
current.current = props
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!anchor.current) return
|
|
16
|
+
handle.current = attachAppleButton(anchor.current, current.current, () => {
|
|
17
|
+
const latest = current.current
|
|
18
|
+
if (latest.disabled) return
|
|
19
|
+
latest.onPress?.()
|
|
20
|
+
void appleAuth.signIn().then((result) => current.current.onResult?.(result))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return () => { handle.current?.detach(); handle.current = null }
|
|
24
|
+
}, [props.type, props.theme])
|
|
25
|
+
|
|
26
|
+
useEffect(() => { handle.current?.update(!!props.disabled) })
|
|
27
|
+
|
|
18
28
|
<flexboxlayout
|
|
29
|
+
ref={(view: any) => { anchor.current = view }}
|
|
19
30
|
id={props.id}
|
|
20
|
-
className={
|
|
21
|
-
style={props.style}
|
|
22
|
-
|
|
23
|
-
onTap={
|
|
24
|
-
props.disabled === true
|
|
25
|
-
? undefined
|
|
26
|
-
: () => {
|
|
27
|
-
props.onPress?.()
|
|
28
|
-
if (props.onResult) {
|
|
29
|
-
void appleAuth.signIn().then((result: SignInResult) => props.onResult?.(result))
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
>
|
|
34
|
-
<label text={LABELS[props.type ?? 'default']} />
|
|
35
|
-
</flexboxlayout>
|
|
31
|
+
className={props.className}
|
|
32
|
+
style={{ width: 240, height: 44, ...props.style }}
|
|
33
|
+
/>;
|
|
36
34
|
}
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
/** @jsxImportSource @xplat/macos
|
|
1
|
+
/** @jsxImportSource @octane-xplat/macos-renderer */
|
|
2
|
+
import { googleAuth } from './google.macos'
|
|
2
3
|
import type { GoogleSignInButtonProps } from './types'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
export function GoogleSignInButton(
|
|
6
|
-
<
|
|
5
|
+
/** Hosted Google sign-in trigger; Google does not provide an AppKit button. */
|
|
6
|
+
export function GoogleSignInButton(props: GoogleSignInButtonProps) @{
|
|
7
|
+
<flexboxlayout
|
|
8
|
+
id={props.id}
|
|
9
|
+
className={['vx-button', props.className]}
|
|
10
|
+
style={props.style}
|
|
11
|
+
accessibilityRole="button"
|
|
12
|
+
accessibilityLabel="Sign in with Google"
|
|
13
|
+
accessibilityState={{ disabled: !!props.disabled }}
|
|
14
|
+
onTap={props.disabled ? undefined : () => {
|
|
15
|
+
props.onPress?.()
|
|
16
|
+
void googleAuth.signIn().then((result) => props.onResult?.(result))
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
<label text="Sign in with Google" />
|
|
20
|
+
</flexboxlayout>;
|
|
7
21
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Leaf-owned attachment: no renderer vocabulary or shared host change needed.
|
|
2
|
+
import type { AppleSignInButtonProps } from './types'
|
|
3
|
+
|
|
4
|
+
declare const objc: any
|
|
5
|
+
declare const NSObject: any
|
|
6
|
+
declare const interop: any
|
|
7
|
+
declare const ASAuthorizationAppleIDButton: any
|
|
8
|
+
|
|
9
|
+
let ActionTarget: any
|
|
10
|
+
|
|
11
|
+
export function attachAppleButton(view: any, props: AppleSignInButtonProps, press: () => void) {
|
|
12
|
+
try {
|
|
13
|
+
objc.import('AuthenticationServices')
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
16
|
+
if (typeof ASAuthorizationAppleIDButton === 'undefined') {
|
|
17
|
+
throw new Error('The official Sign in with Apple button is unavailable on this host')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!ActionTarget) {
|
|
21
|
+
ActionTarget = NSObject.extend(
|
|
22
|
+
{
|
|
23
|
+
press(this: any, _sender: any) {
|
|
24
|
+
this.callback?.()
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{ exposedMethods: { 'press:': { params: [interop.types.id], returns: interop.types.void } } },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const target = ActionTarget.new()
|
|
32
|
+
target.callback = press
|
|
33
|
+
// Values from ASAuthorizationAppleIDButton.h (macOS 10.15+).
|
|
34
|
+
const type = props.type === 'continue' ? 1 : props.type === 'signUp' ? 2 : 0
|
|
35
|
+
const theme = props.theme === 'white' ? 0 : props.theme === 'whiteOutline' ? 1 : 2
|
|
36
|
+
const button =
|
|
37
|
+
ASAuthorizationAppleIDButton.alloc().initWithAuthorizationButtonTypeAuthorizationButtonStyle(
|
|
38
|
+
type,
|
|
39
|
+
theme,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
button.target = target
|
|
43
|
+
button.action = 'press:'
|
|
44
|
+
button.enabled = !props.disabled
|
|
45
|
+
button.translatesAutoresizingMaskIntoConstraints = false
|
|
46
|
+
view.addSubview(button)
|
|
47
|
+
const constraints = [
|
|
48
|
+
button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
|
|
49
|
+
button.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
|
|
50
|
+
button.topAnchor.constraintEqualToAnchor(view.topAnchor),
|
|
51
|
+
button.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for (const constraint of constraints) {
|
|
55
|
+
constraint.active = true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
// Target is weak in AppKit: retain its wrapper with the attachment handle.
|
|
60
|
+
target,
|
|
61
|
+
button,
|
|
62
|
+
update(disabled: boolean) {
|
|
63
|
+
button.enabled = !disabled
|
|
64
|
+
},
|
|
65
|
+
detach() {
|
|
66
|
+
target.callback = null
|
|
67
|
+
button.target = null
|
|
68
|
+
for (const constraint of constraints) {
|
|
69
|
+
constraint.active = false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
button.removeFromSuperview()
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/apple.macos.ts
CHANGED
|
@@ -40,7 +40,10 @@ const SCOPE_MAP: Record<string, () => any> = {
|
|
|
40
40
|
|
|
41
41
|
function isCancel(error: unknown): boolean {
|
|
42
42
|
// ASAuthorizationError.canceled = 1001 (NSError.code).
|
|
43
|
-
return (
|
|
43
|
+
return (
|
|
44
|
+
(error as any)?.domain === 'com.apple.AuthenticationServices.AuthorizationError' &&
|
|
45
|
+
Number((error as any)?.code) === 1001
|
|
46
|
+
)
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
function utf8Bytes(input: string): Uint8Array {
|
|
@@ -63,7 +66,10 @@ function sha256Hex(input: string): string {
|
|
|
63
66
|
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
64
67
|
])
|
|
65
68
|
|
|
66
|
-
const H = new Uint32Array([
|
|
69
|
+
const H = new Uint32Array([
|
|
70
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
71
|
+
])
|
|
72
|
+
|
|
67
73
|
const bytes = utf8Bytes(input)
|
|
68
74
|
const bitLen = bytes.length * 8
|
|
69
75
|
const padded = new Uint8Array((bytes.length + 9 + 63) & ~63)
|
|
@@ -74,25 +80,48 @@ function sha256Hex(input: string): string {
|
|
|
74
80
|
dv.setUint32(padded.length - 4, bitLen >>> 0)
|
|
75
81
|
const w = new Uint32Array(64)
|
|
76
82
|
for (let block = 0; block < padded.length; block += 64) {
|
|
77
|
-
for (let t = 0; t < 16; t++) {
|
|
83
|
+
for (let t = 0; t < 16; t++) {
|
|
84
|
+
w[t] = dv.getUint32(block + t * 4)
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
for (let t = 16; t < 64; t++) {
|
|
79
|
-
const s0 =
|
|
80
|
-
|
|
88
|
+
const s0 =
|
|
89
|
+
((w[t - 15] >>> 7) | (w[t - 15] << 25)) ^
|
|
90
|
+
((w[t - 15] >>> 18) | (w[t - 15] << 14)) ^
|
|
91
|
+
(w[t - 15] >>> 3)
|
|
92
|
+
|
|
93
|
+
const s1 =
|
|
94
|
+
((w[t - 2] >>> 17) | (w[t - 2] << 15)) ^
|
|
95
|
+
((w[t - 2] >>> 19) | (w[t - 2] << 13)) ^
|
|
96
|
+
(w[t - 2] >>> 10)
|
|
97
|
+
|
|
81
98
|
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
let [a, b, c, d, e, f, g, h] = H
|
|
85
102
|
for (let t = 0; t < 64; t++) {
|
|
86
|
-
const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7)
|
|
103
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7))
|
|
87
104
|
const t1 = (h + S1 + ((e & f) ^ (~e & g)) + K[t] + w[t]) >>> 0
|
|
88
|
-
const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10)
|
|
105
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10))
|
|
89
106
|
const t2 = (S0 + ((a & b) ^ (a & c) ^ (b & c))) >>> 0
|
|
90
|
-
h = g
|
|
91
|
-
|
|
107
|
+
h = g
|
|
108
|
+
g = f
|
|
109
|
+
f = e
|
|
110
|
+
e = (d + t1) >>> 0
|
|
111
|
+
d = c
|
|
112
|
+
c = b
|
|
113
|
+
b = a
|
|
114
|
+
a = (t1 + t2) >>> 0
|
|
92
115
|
}
|
|
93
116
|
|
|
94
|
-
H[0] = (H[0] + a) >>> 0
|
|
95
|
-
H[
|
|
117
|
+
H[0] = (H[0] + a) >>> 0
|
|
118
|
+
H[1] = (H[1] + b) >>> 0
|
|
119
|
+
H[2] = (H[2] + c) >>> 0
|
|
120
|
+
H[3] = (H[3] + d) >>> 0
|
|
121
|
+
H[4] = (H[4] + e) >>> 0
|
|
122
|
+
H[5] = (H[5] + f) >>> 0
|
|
123
|
+
H[6] = (H[6] + g) >>> 0
|
|
124
|
+
H[7] = (H[7] + h) >>> 0
|
|
96
125
|
}
|
|
97
126
|
|
|
98
127
|
return [...H].map((x) => x.toString(16).padStart(8, '0')).join('')
|
|
@@ -102,15 +131,21 @@ function toCredential(credential: any): AuthCredential {
|
|
|
102
131
|
const tokenData = credential.valueForKey('identityToken')
|
|
103
132
|
const codeData = credential.valueForKey('authorizationCode')
|
|
104
133
|
const fullName = credential.fullName
|
|
105
|
-
const name =
|
|
106
|
-
[fullName?.givenName, fullName?.familyName].filter(Boolean).join(' ') || undefined
|
|
134
|
+
const name = [fullName?.givenName, fullName?.familyName].filter(Boolean).join(' ') || undefined
|
|
107
135
|
|
|
108
136
|
return {
|
|
109
137
|
provider: 'apple',
|
|
110
138
|
idToken: tokenData ? String(NSString.alloc().initWithDataEncoding(tokenData, 4)) : undefined,
|
|
111
|
-
authorizationCode: codeData
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
authorizationCode: codeData
|
|
140
|
+
? String(NSString.alloc().initWithDataEncoding(codeData, 4))
|
|
141
|
+
: undefined,
|
|
142
|
+
scopes: (credential.authorizedScopes ? Array.from(credential.authorizedScopes as any) : []).map(
|
|
143
|
+
(s: any) =>
|
|
144
|
+
s === ASAuthorizationScopeEmail
|
|
145
|
+
? 'email'
|
|
146
|
+
: s === ASAuthorizationScopeFullName
|
|
147
|
+
? 'name'
|
|
148
|
+
: String(s),
|
|
114
149
|
),
|
|
115
150
|
user: {
|
|
116
151
|
id: String(credential.user ?? ''),
|
|
@@ -154,15 +189,24 @@ function ensureClasses() {
|
|
|
154
189
|
|
|
155
190
|
AppleAuthDelegate = NSObject.extend(
|
|
156
191
|
{
|
|
157
|
-
authorizationControllerDidCompleteWithAuthorization(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
192
|
+
authorizationControllerDidCompleteWithAuthorization(
|
|
193
|
+
this: any,
|
|
194
|
+
_controller: any,
|
|
195
|
+
authorization: any,
|
|
196
|
+
) {
|
|
197
|
+
try {
|
|
198
|
+
const credential = toCredential(authorization.credential)
|
|
199
|
+
if (!credential.user.id || !credential.idToken) {
|
|
200
|
+
throw new Error('Sign in with Apple returned an incomplete credential')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.finish?.({ status: 'success', credential })
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.finish?.({ status: 'error', message: String((error as any)?.message ?? error) })
|
|
206
|
+
}
|
|
161
207
|
},
|
|
162
|
-
authorizationControllerDidCompleteWithError(_controller: any, error: any) {
|
|
163
|
-
|
|
164
|
-
activeFinish = null
|
|
165
|
-
finish?.(
|
|
208
|
+
authorizationControllerDidCompleteWithError(this: any, _controller: any, error: any) {
|
|
209
|
+
this.finish?.(
|
|
166
210
|
isCancel(error)
|
|
167
211
|
? { status: 'cancelled' }
|
|
168
212
|
: { status: 'error', message: String((error as any)?.localizedDescription ?? error) },
|
|
@@ -208,7 +252,7 @@ function ensureClasses() {
|
|
|
208
252
|
let activeController: any = null
|
|
209
253
|
let activeDelegate: any = null
|
|
210
254
|
let activeAnchor: any = null
|
|
211
|
-
let
|
|
255
|
+
let active = false
|
|
212
256
|
|
|
213
257
|
export const appleAuth: AppleAuth = {
|
|
214
258
|
get supported() {
|
|
@@ -222,37 +266,59 @@ export const appleAuth: AppleAuth = {
|
|
|
222
266
|
return { status: 'error', message: 'AuthenticationServices is unavailable on this host' }
|
|
223
267
|
}
|
|
224
268
|
|
|
225
|
-
if (
|
|
269
|
+
if (active) {
|
|
226
270
|
return { status: 'error', message: 'a Sign in with Apple flow is already active' }
|
|
227
271
|
}
|
|
228
272
|
|
|
273
|
+
active = true
|
|
229
274
|
return new Promise<SignInResult>((resolve) => {
|
|
275
|
+
let settled = false
|
|
230
276
|
const finish = (result: SignInResult) => {
|
|
277
|
+
if (settled) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
settled = true
|
|
282
|
+
active = false
|
|
231
283
|
activeController = null
|
|
232
284
|
activeDelegate = null
|
|
233
285
|
activeAnchor = null
|
|
234
|
-
activeFinish = null
|
|
235
286
|
resolve(result)
|
|
236
287
|
}
|
|
237
288
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (options?.scopes?.length) {
|
|
244
|
-
request.requestedScopes = options.scopes.map((s) => SCOPE_MAP[s]?.()).filter(Boolean)
|
|
245
|
-
}
|
|
289
|
+
try {
|
|
290
|
+
const app = NSApplication.sharedApplication
|
|
291
|
+
if (!(app.keyWindow ?? app.mainWindow ?? app.windows?.firstObject)) {
|
|
292
|
+
throw new Error('Sign in with Apple requires an AppKit presentation window')
|
|
293
|
+
}
|
|
246
294
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
ensureClasses()
|
|
296
|
+
activeDelegate = AppleAuthDelegate.new()
|
|
297
|
+
activeDelegate.finish = finish
|
|
298
|
+
const provider = ASAuthorizationAppleIDProvider.new()
|
|
299
|
+
const request = provider.createRequest()
|
|
300
|
+
if (options?.scopes?.length) {
|
|
301
|
+
request.requestedScopes = options.scopes.map((s) => SCOPE_MAP[s]?.()).filter(Boolean)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (options?.nonce !== undefined) {
|
|
305
|
+
request.nonce = sha256Hex(options.nonce)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
activeAnchor = AppleAuthAnchor.new()
|
|
309
|
+
activeController = ASAuthorizationController.alloc().initWithAuthorizationRequests([
|
|
310
|
+
request,
|
|
311
|
+
])
|
|
250
312
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
313
|
+
activeController.delegate = activeDelegate
|
|
314
|
+
activeController.presentationContextProvider = activeAnchor
|
|
315
|
+
activeController.performRequests()
|
|
316
|
+
} catch (error) {
|
|
317
|
+
finish({
|
|
318
|
+
status: 'error',
|
|
319
|
+
message: String((error as any)?.localizedDescription ?? (error as any)?.message ?? error),
|
|
320
|
+
})
|
|
321
|
+
}
|
|
256
322
|
})
|
|
257
323
|
},
|
|
258
324
|
async getCredentialState(userId: string): Promise<AppleCredentialState> {
|
|
@@ -261,17 +327,31 @@ export const appleAuth: AppleAuth = {
|
|
|
261
327
|
}
|
|
262
328
|
|
|
263
329
|
return new Promise<AppleCredentialState>((resolve) => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
330
|
+
try {
|
|
331
|
+
ASAuthorizationAppleIDProvider.new().getCredentialStateForUserIDCompletion(
|
|
332
|
+
userId,
|
|
333
|
+
(state: number, error: any) => {
|
|
334
|
+
if (error) {
|
|
335
|
+
resolve('unknown')
|
|
336
|
+
return
|
|
337
|
+
}
|
|
271
338
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
339
|
+
resolve(
|
|
340
|
+
state === 1
|
|
341
|
+
? 'authorized'
|
|
342
|
+
: state === 0
|
|
343
|
+
? 'revoked'
|
|
344
|
+
: state === 3
|
|
345
|
+
? 'transferred'
|
|
346
|
+
: state === 2
|
|
347
|
+
? 'notFound'
|
|
348
|
+
: 'unknown',
|
|
349
|
+
)
|
|
350
|
+
},
|
|
351
|
+
)
|
|
352
|
+
} catch {
|
|
353
|
+
resolve('unknown')
|
|
354
|
+
}
|
|
275
355
|
})
|
|
276
356
|
},
|
|
277
357
|
}
|
package/src/apple.web.ts
CHANGED
|
@@ -47,9 +47,9 @@ function decodeJwt(token: string): Record<string, any> {
|
|
|
47
47
|
function toCredential(response: any): AuthCredential {
|
|
48
48
|
const auth = response?.authorization ?? {}
|
|
49
49
|
const claims = auth.id_token ? decodeJwt(auth.id_token) : {}
|
|
50
|
-
const name =
|
|
51
|
-
.filter(Boolean)
|
|
52
|
-
|
|
50
|
+
const name =
|
|
51
|
+
[response?.user?.name?.firstName, response?.user?.name?.lastName].filter(Boolean).join(' ') ||
|
|
52
|
+
undefined
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
55
|
provider: 'apple',
|
|
@@ -101,7 +101,10 @@ export const appleAuth: AppleAuth = {
|
|
|
101
101
|
} catch (error) {
|
|
102
102
|
return isCancel(error)
|
|
103
103
|
? { status: 'cancelled' }
|
|
104
|
-
: {
|
|
104
|
+
: {
|
|
105
|
+
status: 'error',
|
|
106
|
+
message: String((error as any)?.message ?? (error as any)?.error ?? error),
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
},
|
|
107
110
|
async getCredentialState(): Promise<AppleCredentialState> {
|
package/src/google.macos.ts
CHANGED
|
@@ -1,18 +1,85 @@
|
|
|
1
|
-
// Google
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import type { GoogleAuth, SignInResult } from './types'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
message: 'Google Sign-In is unsupported by the AppKit host — use a hosted authSession flow',
|
|
9
|
-
}
|
|
1
|
+
// Google credentials on AppKit come from the app's hosted OAuth backend.
|
|
2
|
+
// The shared methods/result stay identical to the SDK leaves.
|
|
3
|
+
import { authSession } from '@octane-xplat/platform'
|
|
4
|
+
import type { GoogleAuth, GoogleAuthConfig, SignInResult } from './types'
|
|
5
|
+
|
|
6
|
+
let configuration: GoogleAuthConfig | undefined
|
|
7
|
+
let active = false
|
|
10
8
|
|
|
11
9
|
export const googleAuth: GoogleAuth = {
|
|
12
|
-
supported
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
get supported() {
|
|
11
|
+
return authSession.supported
|
|
12
|
+
},
|
|
13
|
+
async configure(config) {
|
|
14
|
+
configuration = config ? { ...config, scopes: config.scopes?.slice() } : undefined
|
|
15
|
+
},
|
|
16
|
+
async signIn(options = {}): Promise<SignInResult> {
|
|
17
|
+
const config = configuration
|
|
18
|
+
const flow = config?.hostedFlow
|
|
19
|
+
const session = authSession.impl
|
|
20
|
+
if (!session) {
|
|
21
|
+
return { status: 'error', message: 'AuthenticationServices is unavailable on this host' }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!flow) {
|
|
25
|
+
return {
|
|
26
|
+
status: 'error',
|
|
27
|
+
message: 'Google Sign-In on macOS requires configure({ hostedFlow })',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (active) {
|
|
32
|
+
return { status: 'error', message: 'a Google Sign-In flow is already active' }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
active = true
|
|
36
|
+
try {
|
|
37
|
+
const request = await flow.createRequest({
|
|
38
|
+
...options,
|
|
39
|
+
clientId: config?.clientId,
|
|
40
|
+
serverClientId: config?.serverClientId,
|
|
41
|
+
scopes: config?.scopes?.slice(),
|
|
42
|
+
hostedDomain: config?.hostedDomain,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Backend URLs must use TLS; the backend owns Google registration,
|
|
46
|
+
// state/nonce binding and single-use callback-code redemption.
|
|
47
|
+
if (!/^https:\/\//i.test(request.url)) {
|
|
48
|
+
throw new Error('Google hosted sign-in requires an HTTPS URL')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await session.open(request.url, {
|
|
52
|
+
callbackScheme: request.callbackScheme,
|
|
53
|
+
prefersEphemeralSession: true,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if (result.type === 'cancel') {
|
|
57
|
+
return { status: 'cancelled' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.type === 'error') {
|
|
61
|
+
return { status: 'error', message: result.message }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const credential = await flow.complete(result.url)
|
|
65
|
+
if (
|
|
66
|
+
credential?.provider !== 'google' ||
|
|
67
|
+
typeof credential.user?.id !== 'string' ||
|
|
68
|
+
!credential.user.id ||
|
|
69
|
+
!Array.isArray(credential.scopes) ||
|
|
70
|
+
credential.scopes.some((scope) => typeof scope !== 'string')
|
|
71
|
+
) {
|
|
72
|
+
throw new Error('Google hosted sign-in returned an invalid credential')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { status: 'success', credential }
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return { status: 'error', message: String((error as any)?.message ?? error) }
|
|
78
|
+
} finally {
|
|
79
|
+
active = false
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async signOut() {
|
|
83
|
+
await configuration?.hostedFlow?.signOut?.()
|
|
16
84
|
},
|
|
17
|
-
async signOut() {},
|
|
18
85
|
}
|
package/src/google.web.ts
CHANGED
|
@@ -78,7 +78,11 @@ function initialize(google: any) {
|
|
|
78
78
|
/** Internal — the button leaf renders the official GIS button into `host`. */
|
|
79
79
|
export async function renderGoogleButton(
|
|
80
80
|
host: HTMLElement,
|
|
81
|
-
options: {
|
|
81
|
+
options: {
|
|
82
|
+
theme?: 'dark' | 'light' | 'auto'
|
|
83
|
+
variant?: 'standard' | 'wide' | 'icon'
|
|
84
|
+
width?: number
|
|
85
|
+
},
|
|
82
86
|
onResult: (result: SignInResult) => void,
|
|
83
87
|
): Promise<void> {
|
|
84
88
|
if (!config.clientId) {
|
package/src/index.macos.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
|
|
2
2
|
// reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
|
|
3
|
-
export { appleAuth } from './apple'
|
|
4
|
-
export { googleAuth } from './google'
|
|
5
|
-
export { AppleSignInButton } from './AppleSignInButton.tsrx'
|
|
6
|
-
export { GoogleSignInButton } from './GoogleSignInButton.tsrx'
|
|
3
|
+
export { appleAuth } from './apple'
|
|
4
|
+
export { googleAuth } from './google'
|
|
5
|
+
export { AppleSignInButton } from './AppleSignInButton.tsrx'
|
|
6
|
+
export { GoogleSignInButton } from './GoogleSignInButton.tsrx'
|
|
7
7
|
export type {
|
|
8
8
|
AppleAuth,
|
|
9
9
|
AppleAuthConfig,
|
|
@@ -15,7 +15,8 @@ export type {
|
|
|
15
15
|
AuthUser,
|
|
16
16
|
GoogleAuth,
|
|
17
17
|
GoogleAuthConfig,
|
|
18
|
+
GoogleHostedAuthFlow,
|
|
18
19
|
GoogleSignInButtonProps,
|
|
19
20
|
GoogleSignInOptions,
|
|
20
21
|
SignInResult,
|
|
21
|
-
} from './types'
|
|
22
|
+
} from './types'
|
package/src/index.web.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
|
|
2
2
|
// reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
|
|
3
|
-
export { appleAuth } from './apple.web'
|
|
4
|
-
export { googleAuth } from './google.web'
|
|
5
|
-
export { AppleSignInButton } from './AppleSignInButton.web.tsrx'
|
|
6
|
-
export { GoogleSignInButton } from './GoogleSignInButton.web.tsrx'
|
|
3
|
+
export { appleAuth } from './apple.web'
|
|
4
|
+
export { googleAuth } from './google.web'
|
|
5
|
+
export { AppleSignInButton } from './AppleSignInButton.web.tsrx'
|
|
6
|
+
export { GoogleSignInButton } from './GoogleSignInButton.web.tsrx'
|
|
7
7
|
export type {
|
|
8
8
|
AppleAuth,
|
|
9
9
|
AppleAuthConfig,
|
|
@@ -15,7 +15,8 @@ export type {
|
|
|
15
15
|
AuthUser,
|
|
16
16
|
GoogleAuth,
|
|
17
17
|
GoogleAuthConfig,
|
|
18
|
+
GoogleHostedAuthFlow,
|
|
18
19
|
GoogleSignInButtonProps,
|
|
19
20
|
GoogleSignInOptions,
|
|
20
21
|
SignInResult,
|
|
21
|
-
} from './types'
|
|
22
|
+
} from './types'
|
package/src/types.ts
CHANGED
|
@@ -87,6 +87,19 @@ export interface AppleAuth {
|
|
|
87
87
|
getCredentialState(userId: string): Promise<AppleCredentialState>
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** App-owned Google OAuth ceremony for the macOS system browser. */
|
|
91
|
+
export interface GoogleHostedAuthFlow {
|
|
92
|
+
/** Issue a fresh backend attempt, binding state, nonce, scopes and callback destination. */
|
|
93
|
+
createRequest(
|
|
94
|
+
options: GoogleSignInOptions &
|
|
95
|
+
Pick<GoogleAuthConfig, 'clientId' | 'serverClientId' | 'scopes' | 'hostedDomain'>,
|
|
96
|
+
): Promise<{ url: string; callbackScheme: string }>
|
|
97
|
+
/** Validate/redeem the callback against that attempt on your backend; return its Google credential. */
|
|
98
|
+
complete(callbackURL: string): Promise<AuthCredential>
|
|
99
|
+
/** Optional backend logout. macOS requests ephemeral browser sessions. */
|
|
100
|
+
signOut?(): Promise<void>
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
export interface GoogleAuthConfig {
|
|
91
104
|
/**
|
|
92
105
|
* OAuth client id — the web client id on web (required there). On iOS it
|
|
@@ -99,11 +112,13 @@ export interface GoogleAuthConfig {
|
|
|
99
112
|
scopes?: string[]
|
|
100
113
|
/** Restrict to a Google Workspace domain. */
|
|
101
114
|
hostedDomain?: string
|
|
115
|
+
/** macOS only — hosted OAuth adapter; other targets keep using their provider SDK. */
|
|
116
|
+
hostedFlow?: GoogleHostedAuthFlow
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
export interface GoogleSignInOptions {
|
|
105
120
|
/**
|
|
106
|
-
* Web
|
|
121
|
+
* Web/macOS — nonce bound into the id token. Scope requests go through
|
|
107
122
|
* `GoogleAuthConfig.scopes`; neither provider SDK takes per-call scopes.
|
|
108
123
|
*/
|
|
109
124
|
nonce?: string
|
|
@@ -111,14 +126,14 @@ export interface GoogleSignInOptions {
|
|
|
111
126
|
|
|
112
127
|
export interface GoogleAuth {
|
|
113
128
|
/**
|
|
114
|
-
* Whether the provider
|
|
115
|
-
*
|
|
129
|
+
* Whether the provider can run here — iOS/Android + browsers, or macOS
|
|
130
|
+
* AuthenticationServices (requires `configure({ hostedFlow })`). Android devices without
|
|
116
131
|
* Play services still report true — the sign-in call surfaces the failure.
|
|
117
132
|
*/
|
|
118
133
|
readonly supported: boolean
|
|
119
134
|
configure(config?: GoogleAuthConfig): Promise<void>
|
|
120
135
|
signIn(options?: GoogleSignInOptions): Promise<SignInResult>
|
|
121
|
-
/** Clear
|
|
136
|
+
/** Clear SDK account selection; macOS calls hostedFlow.signOut and requests ephemeral sessions. */
|
|
122
137
|
signOut(): Promise<void>
|
|
123
138
|
}
|
|
124
139
|
|
|
@@ -2,4 +2,4 @@ export { appleAuth } from './apple.js';
|
|
|
2
2
|
export { googleAuth } from './google.js';
|
|
3
3
|
export { AppleSignInButton } from './AppleSignInButton.js';
|
|
4
4
|
export { GoogleSignInButton } from './GoogleSignInButton.js';
|
|
5
|
-
export type { AppleAuth, AppleAuthConfig, AppleCredentialState, AppleScope, AppleSignInButtonProps, AppleSignInOptions, AuthCredential, AuthUser, GoogleAuth, GoogleAuthConfig, GoogleSignInButtonProps, GoogleSignInOptions, SignInResult, } from './types.js';
|
|
5
|
+
export type { AppleAuth, AppleAuthConfig, AppleCredentialState, AppleScope, AppleSignInButtonProps, AppleSignInOptions, AuthCredential, AuthUser, GoogleAuth, GoogleAuthConfig, GoogleHostedAuthFlow, GoogleSignInButtonProps, GoogleSignInOptions, SignInResult, } from './types.js';
|
|
@@ -2,4 +2,4 @@ export { appleAuth } from './apple.web.js';
|
|
|
2
2
|
export { googleAuth } from './google.web.js';
|
|
3
3
|
export { AppleSignInButton } from './AppleSignInButton.web.js';
|
|
4
4
|
export { GoogleSignInButton } from './GoogleSignInButton.web.js';
|
|
5
|
-
export type { AppleAuth, AppleAuthConfig, AppleCredentialState, AppleScope, AppleSignInButtonProps, AppleSignInOptions, AuthCredential, AuthUser, GoogleAuth, GoogleAuthConfig, GoogleSignInButtonProps, GoogleSignInOptions, SignInResult, } from './types.js';
|
|
5
|
+
export type { AppleAuth, AppleAuthConfig, AppleCredentialState, AppleScope, AppleSignInButtonProps, AppleSignInOptions, AuthCredential, AuthUser, GoogleAuth, GoogleAuthConfig, GoogleHostedAuthFlow, GoogleSignInButtonProps, GoogleSignInOptions, SignInResult, } from './types.js';
|
|
@@ -77,6 +77,18 @@ export interface AppleAuth {
|
|
|
77
77
|
/** iOS and macOS — credential state for a prior `user.id`. */
|
|
78
78
|
getCredentialState(userId: string): Promise<AppleCredentialState>;
|
|
79
79
|
}
|
|
80
|
+
/** App-owned Google OAuth ceremony for the macOS system browser. */
|
|
81
|
+
export interface GoogleHostedAuthFlow {
|
|
82
|
+
/** Issue a fresh backend attempt, binding state, nonce, scopes and callback destination. */
|
|
83
|
+
createRequest(options: GoogleSignInOptions & Pick<GoogleAuthConfig, 'clientId' | 'serverClientId' | 'scopes' | 'hostedDomain'>): Promise<{
|
|
84
|
+
url: string;
|
|
85
|
+
callbackScheme: string;
|
|
86
|
+
}>;
|
|
87
|
+
/** Validate/redeem the callback against that attempt on your backend; return its Google credential. */
|
|
88
|
+
complete(callbackURL: string): Promise<AuthCredential>;
|
|
89
|
+
/** Optional backend logout. macOS requests ephemeral browser sessions. */
|
|
90
|
+
signOut?(): Promise<void>;
|
|
91
|
+
}
|
|
80
92
|
export interface GoogleAuthConfig {
|
|
81
93
|
/**
|
|
82
94
|
* OAuth client id — the web client id on web (required there). On iOS it
|
|
@@ -89,24 +101,26 @@ export interface GoogleAuthConfig {
|
|
|
89
101
|
scopes?: string[];
|
|
90
102
|
/** Restrict to a Google Workspace domain. */
|
|
91
103
|
hostedDomain?: string;
|
|
104
|
+
/** macOS only — hosted OAuth adapter; other targets keep using their provider SDK. */
|
|
105
|
+
hostedFlow?: GoogleHostedAuthFlow;
|
|
92
106
|
}
|
|
93
107
|
export interface GoogleSignInOptions {
|
|
94
108
|
/**
|
|
95
|
-
* Web
|
|
109
|
+
* Web/macOS — nonce bound into the id token. Scope requests go through
|
|
96
110
|
* `GoogleAuthConfig.scopes`; neither provider SDK takes per-call scopes.
|
|
97
111
|
*/
|
|
98
112
|
nonce?: string;
|
|
99
113
|
}
|
|
100
114
|
export interface GoogleAuth {
|
|
101
115
|
/**
|
|
102
|
-
* Whether the provider
|
|
103
|
-
*
|
|
116
|
+
* Whether the provider can run here — iOS/Android + browsers, or macOS
|
|
117
|
+
* AuthenticationServices (requires `configure({ hostedFlow })`). Android devices without
|
|
104
118
|
* Play services still report true — the sign-in call surfaces the failure.
|
|
105
119
|
*/
|
|
106
120
|
readonly supported: boolean;
|
|
107
121
|
configure(config?: GoogleAuthConfig): Promise<void>;
|
|
108
122
|
signIn(options?: GoogleSignInOptions): Promise<SignInResult>;
|
|
109
|
-
/** Clear
|
|
123
|
+
/** Clear SDK account selection; macOS calls hostedFlow.signOut and requests ephemeral sessions. */
|
|
110
124
|
signOut(): Promise<void>;
|
|
111
125
|
}
|
|
112
126
|
interface SignInButtonBaseProps {
|
package/types/index.macos.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
AuthUser,
|
|
10
10
|
GoogleAuth,
|
|
11
11
|
GoogleAuthConfig,
|
|
12
|
+
GoogleHostedAuthFlow,
|
|
12
13
|
GoogleSignInButtonProps,
|
|
13
14
|
GoogleSignInOptions,
|
|
14
15
|
SignInResult,
|
|
@@ -25,6 +26,7 @@ export type {
|
|
|
25
26
|
AuthUser,
|
|
26
27
|
GoogleAuth,
|
|
27
28
|
GoogleAuthConfig,
|
|
29
|
+
GoogleHostedAuthFlow,
|
|
28
30
|
GoogleSignInButtonProps,
|
|
29
31
|
GoogleSignInOptions,
|
|
30
32
|
SignInResult,
|