@octane-xplat/auth 0.9.0 → 0.11.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 +115 -16
- package/dist/native/auth/src/base64.js +17 -0
- package/dist/native/auth/src/hosted.js +252 -0
- package/dist/native/auth/src/sha256.js +141 -0
- package/dist/native/index.js +6 -5
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/platform/src/a11y.js +1 -0
- package/dist/native/platform/src/app-info.js +1 -0
- package/dist/native/platform/src/auth-session.js +138 -0
- package/dist/native/platform/src/breakpoints.tsrx.js +5 -0
- package/dist/native/platform/src/clipboard.js +1 -0
- package/dist/native/platform/src/connectivity.js +1 -0
- package/dist/native/platform/src/deep-links.js +1 -0
- package/dist/native/platform/src/device.js +3 -0
- package/dist/native/platform/src/index.js +17 -0
- package/dist/native/platform/src/lifecycle.tsrx.js +7 -0
- package/dist/native/platform/src/locale.js +3 -0
- package/dist/native/platform/src/open-url.js +1 -0
- package/dist/native/platform/src/random.js +38 -0
- package/dist/native/platform/src/safe-area.tsrx.js +4 -0
- package/dist/native/platform/src/screen.tsrx.js +4 -0
- package/dist/native/platform/src/storage.js +1 -0
- package/dist/native/platform/src/system-bars.js +1 -0
- package/dist/native/secure-storage/src/index.js +2 -0
- package/dist/native/secure-storage/src/secure-storage.js +17 -0
- package/dist/web/auth/src/base64.js +17 -0
- package/dist/web/auth/src/hosted.js +251 -0
- package/dist/web/auth/src/sha256.js +141 -0
- package/dist/web/index.js +6 -5
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/platform/src/app-info.web.js +3 -0
- package/dist/web/platform/src/auth-session.web.js +10 -0
- package/dist/web/platform/src/breakpoints.web.tsrx.js +7 -0
- package/dist/web/platform/src/clipboard.web.js +3 -0
- package/dist/web/platform/src/deep-links.web.js +3 -0
- package/dist/web/platform/src/device.web.js +2 -0
- package/dist/web/platform/src/host-bridge.web.js +18 -0
- package/dist/web/platform/src/host-protocol.js +73 -0
- package/dist/web/platform/src/host-runtime.web.js +76 -0
- package/dist/web/platform/src/index.web.js +13 -0
- package/dist/web/platform/src/lifecycle.web.tsrx.js +3 -0
- package/dist/web/platform/src/locale.web.js +10 -0
- package/dist/web/platform/src/random.web.js +21 -0
- package/dist/web/platform/src/safe-area.web.tsrx.js +3 -0
- package/dist/web/platform/src/screen.web.tsrx.js +3 -0
- package/dist/web/platform/src/webauthn.web.js +2 -0
- package/dist/web/secure-storage/src/secure-storage.web.js +21 -0
- package/package.json +11 -5
- 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 +87 -130
- package/src/base64.ts +25 -0
- package/src/google.macos.ts +81 -14
- package/src/hosted.ts +392 -0
- package/src/index.macos.ts +17 -0
- package/src/index.ts +17 -0
- package/src/index.web.ts +17 -0
- package/src/sha256.ts +118 -0
- package/src/types.ts +216 -4
- package/types/generated/.tsrx-typegen-manifest.json +3 -0
- package/types/generated/base64.d.ts +1 -0
- package/types/generated/hosted.d.ts +6 -0
- package/types/generated/index.d.ts +2 -1
- package/types/generated/index.web.d.ts +2 -1
- package/types/generated/sha256.d.ts +4 -0
- package/types/generated/types.d.ts +200 -4
- package/types/index.macos.d.ts +40 -0
- /package/dist/native/{AppleSignInButton.tsrx.js → auth/src/AppleSignInButton.tsrx.js} +0 -0
- /package/dist/native/{GoogleSignInButton.tsrx.js → auth/src/GoogleSignInButton.tsrx.js} +0 -0
- /package/dist/native/{apple.js → auth/src/apple.js} +0 -0
- /package/dist/native/{google.js → auth/src/google.js} +0 -0
- /package/dist/web/{AppleSignInButton.web.tsrx.js → auth/src/AppleSignInButton.web.tsrx.js} +0 -0
- /package/dist/web/{GoogleSignInButton.web.tsrx.js → auth/src/GoogleSignInButton.web.tsrx.js} +0 -0
- /package/dist/web/{apple.web.js → auth/src/apple.web.js} +0 -0
- /package/dist/web/{google.web.js → auth/src/google.web.js} +0 -0
package/dist/web/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { appleAuth } from "./apple.web.js";
|
|
2
|
-
import { googleAuth } from "./google.web.js";
|
|
3
|
-
import { AppleSignInButton } from "./AppleSignInButton.web.tsrx.js";
|
|
4
|
-
import { GoogleSignInButton } from "./GoogleSignInButton.web.tsrx.js";
|
|
5
|
-
|
|
1
|
+
import { appleAuth } from "./auth/src/apple.web.js";
|
|
2
|
+
import { googleAuth } from "./auth/src/google.web.js";
|
|
3
|
+
import { AppleSignInButton } from "./auth/src/AppleSignInButton.web.tsrx.js";
|
|
4
|
+
import { GoogleSignInButton } from "./auth/src/GoogleSignInButton.web.tsrx.js";
|
|
5
|
+
import { createHostedAuth, postJson, queryParam } from "./auth/src/hosted.js";
|
|
6
|
+
export { AppleSignInButton, GoogleSignInButton, appleAuth, createHostedAuth, googleAuth, postJson, queryParam };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { hookSlots } from "octane";
|
|
2
|
+
import { enableNativeReadCollection } from "octane/internal/client";
|
|
3
|
+
import "octane/signals/client";
|
|
4
|
+
//#region ../platform/src/breakpoints.web.tsrx
|
|
5
|
+
enableNativeReadCollection(1);
|
|
6
|
+
/* @__PURE__ */ hookSlots(2) + 1;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region ../platform/src/host-bridge.web.ts
|
|
2
|
+
/** Return the native message channel when this page is hosted by a desktop app. */
|
|
3
|
+
function createWebKitTransport() {
|
|
4
|
+
if (typeof window === "undefined") return null;
|
|
5
|
+
const handler = window.webkit?.messageHandlers?.xplat;
|
|
6
|
+
const host = window.__xplatHostTransport;
|
|
7
|
+
if (!handler || !host) return null;
|
|
8
|
+
return {
|
|
9
|
+
postMessage(message) {
|
|
10
|
+
handler.postMessage(message);
|
|
11
|
+
},
|
|
12
|
+
listen(listener) {
|
|
13
|
+
return host.listen(listener);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createWebKitTransport };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region ../platform/src/host-protocol.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create the webview-side client. Host methods are typed at compile time; this
|
|
4
|
+
* deliberately does not add runtime schema validation.
|
|
5
|
+
*/
|
|
6
|
+
function createHostClient(transport) {
|
|
7
|
+
const pending = /* @__PURE__ */ new Map();
|
|
8
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
9
|
+
let nextId = 0;
|
|
10
|
+
let disposed = false;
|
|
11
|
+
const removeListener = transport.listen((message) => {
|
|
12
|
+
const packet = JSON.parse(message);
|
|
13
|
+
if (packet.type === "reply") {
|
|
14
|
+
const task = pending.get(packet.id);
|
|
15
|
+
if (!task) return;
|
|
16
|
+
pending.delete(packet.id);
|
|
17
|
+
if (packet.ok) task.resolve(packet.value);
|
|
18
|
+
else task.reject(new Error(packet.error));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const listener of listeners.get(packet.name) ?? []) listener(packet.payload);
|
|
22
|
+
});
|
|
23
|
+
const request = (value) => {
|
|
24
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("desktop host client is disposed"));
|
|
25
|
+
const id = ++nextId;
|
|
26
|
+
const promise = new Promise((resolve, reject) => pending.set(id, {
|
|
27
|
+
resolve,
|
|
28
|
+
reject
|
|
29
|
+
}));
|
|
30
|
+
try {
|
|
31
|
+
transport.postMessage(JSON.stringify({
|
|
32
|
+
...value,
|
|
33
|
+
id
|
|
34
|
+
}));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const task = pending.get(id);
|
|
37
|
+
pending.delete(id);
|
|
38
|
+
task?.reject(error instanceof Error ? error : new Error(String(error)));
|
|
39
|
+
}
|
|
40
|
+
return promise;
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
call: ((service, method, ...args) => request({
|
|
44
|
+
type: "call",
|
|
45
|
+
service,
|
|
46
|
+
method,
|
|
47
|
+
args
|
|
48
|
+
})),
|
|
49
|
+
capabilities() {
|
|
50
|
+
return request({ type: "capabilities" });
|
|
51
|
+
},
|
|
52
|
+
on(name, listener) {
|
|
53
|
+
const eventListeners = listeners.get(name) ?? /* @__PURE__ */ new Set();
|
|
54
|
+
const receive = listener;
|
|
55
|
+
eventListeners.add(receive);
|
|
56
|
+
listeners.set(name, eventListeners);
|
|
57
|
+
return () => {
|
|
58
|
+
eventListeners.delete(receive);
|
|
59
|
+
if (!eventListeners.size) listeners.delete(name);
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
dispose() {
|
|
63
|
+
if (disposed) return;
|
|
64
|
+
disposed = true;
|
|
65
|
+
removeListener();
|
|
66
|
+
listeners.clear();
|
|
67
|
+
for (const task of pending.values()) task.reject(/* @__PURE__ */ new Error("desktop host client is disposed"));
|
|
68
|
+
pending.clear();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createHostClient };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createWebKitTransport } from "./host-bridge.web.js";
|
|
2
|
+
import { createHostClient } from "./host-protocol.js";
|
|
3
|
+
//#region ../platform/src/host-runtime.web.ts
|
|
4
|
+
var client;
|
|
5
|
+
var capabilityTable;
|
|
6
|
+
/** Return the native service client when this page is inside a desktop webview. */
|
|
7
|
+
function desktopHostClient() {
|
|
8
|
+
if (client !== void 0) return client;
|
|
9
|
+
const transport = createWebKitTransport();
|
|
10
|
+
client = transport ? createHostClient(transport) : null;
|
|
11
|
+
return client;
|
|
12
|
+
}
|
|
13
|
+
/** Read values installed before the document's application scripts execute. */
|
|
14
|
+
function desktopHostBootstrap() {
|
|
15
|
+
if (typeof window === "undefined") return null;
|
|
16
|
+
return window.__xplatHostSnapshot ?? null;
|
|
17
|
+
}
|
|
18
|
+
function desktopHostSupports(service, method) {
|
|
19
|
+
const host = desktopHostClient();
|
|
20
|
+
if (!host) return Promise.resolve(false);
|
|
21
|
+
capabilityTable ??= host.capabilities().catch(() => ({}));
|
|
22
|
+
return capabilityTable.then((capabilities) => capabilities[service]?.includes(method) ?? false);
|
|
23
|
+
}
|
|
24
|
+
/** Typed desktop service facade; null outside a supported native webview. */
|
|
25
|
+
function desktopHost() {
|
|
26
|
+
const host = desktopHostClient();
|
|
27
|
+
if (!host) return null;
|
|
28
|
+
return {
|
|
29
|
+
app: {
|
|
30
|
+
getInfo: () => host.call("app", "getInfo"),
|
|
31
|
+
getState: () => host.call("app", "getState"),
|
|
32
|
+
getWindowSize: () => host.call("app", "getWindowSize"),
|
|
33
|
+
consumeInitialUrl: () => host.call("app", "consumeInitialUrl")
|
|
34
|
+
},
|
|
35
|
+
clipboard: {
|
|
36
|
+
read: () => host.call("clipboard", "read"),
|
|
37
|
+
write: (value) => host.call("clipboard", "write", value)
|
|
38
|
+
},
|
|
39
|
+
files: {
|
|
40
|
+
pick: (accept, options) => host.call("files", "pick", accept, options),
|
|
41
|
+
readText: (uri) => host.call("files", "readText", uri),
|
|
42
|
+
writeText: (name, text) => host.call("files", "writeText", name, text)
|
|
43
|
+
},
|
|
44
|
+
notifications: {
|
|
45
|
+
ensure: () => host.call("notifications", "ensure"),
|
|
46
|
+
notify: (title, body) => host.call("notifications", "notify", title, body)
|
|
47
|
+
},
|
|
48
|
+
secureStorage: {
|
|
49
|
+
get: (key) => host.call("secureStorage", "get", key),
|
|
50
|
+
set: (key, value) => host.call("secureStorage", "set", key, value),
|
|
51
|
+
remove: (key) => host.call("secureStorage", "remove", key)
|
|
52
|
+
},
|
|
53
|
+
appearance: { get: () => host.call("appearance", "get") },
|
|
54
|
+
windows: {
|
|
55
|
+
open: (options) => host.call("windows", "open", options),
|
|
56
|
+
close: (id) => host.call("windows", "close", id),
|
|
57
|
+
setTitle: (id, title) => host.call("windows", "setTitle", id, title)
|
|
58
|
+
},
|
|
59
|
+
system: {
|
|
60
|
+
openUrl: (url) => host.call("system", "openUrl", url),
|
|
61
|
+
openPath: (path) => host.call("system", "openPath", path),
|
|
62
|
+
shareContent: (input) => host.call("system", "shareContent", input)
|
|
63
|
+
},
|
|
64
|
+
storage: {
|
|
65
|
+
get: (key) => host.call("storage", "get", key),
|
|
66
|
+
set: (key, value) => host.call("storage", "set", key, value),
|
|
67
|
+
remove: (key) => host.call("storage", "remove", key)
|
|
68
|
+
},
|
|
69
|
+
capabilities: () => host.capabilities(),
|
|
70
|
+
supports: desktopHostSupports,
|
|
71
|
+
on: (name, listener) => host.on(name, listener),
|
|
72
|
+
dispose: () => host.dispose()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { desktopHost, desktopHostBootstrap, desktopHostClient, desktopHostSupports };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "./device.web.js";
|
|
2
|
+
import "./app-info.web.js";
|
|
3
|
+
import "./clipboard.web.js";
|
|
4
|
+
import "./locale.web.js";
|
|
5
|
+
import "./webauthn.web.js";
|
|
6
|
+
import { random } from "./random.web.js";
|
|
7
|
+
import { authSession } from "./auth-session.web.js";
|
|
8
|
+
import "./deep-links.web.js";
|
|
9
|
+
import "./lifecycle.web.tsrx.js";
|
|
10
|
+
import "./safe-area.web.tsrx.js";
|
|
11
|
+
import "./screen.web.tsrx.js";
|
|
12
|
+
import "./breakpoints.web.tsrx.js";
|
|
13
|
+
export { authSession, random };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region ../platform/src/random.web.ts
|
|
2
|
+
var source = () => typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function" ? crypto : null;
|
|
3
|
+
var random = {
|
|
4
|
+
get supported() {
|
|
5
|
+
return source() !== null;
|
|
6
|
+
},
|
|
7
|
+
fill(view) {
|
|
8
|
+
if (!ArrayBuffer.isView(view)) throw new TypeError("random.fill: expected an ArrayBufferView");
|
|
9
|
+
const crypto = source();
|
|
10
|
+
if (!crypto) throw new Error("random: crypto.getRandomValues is unavailable");
|
|
11
|
+
const target = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
12
|
+
for (let offset = 0; offset < target.length; offset += 65536) crypto.getRandomValues(target.subarray(offset, offset + 65536));
|
|
13
|
+
return view;
|
|
14
|
+
},
|
|
15
|
+
bytes(length) {
|
|
16
|
+
if (!Number.isInteger(length) || length < 0) throw new RangeError("random.bytes: length must be a non-negative integer");
|
|
17
|
+
return random.fill(new Uint8Array(length));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { random };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { desktopHost } from "../../platform/src/host-runtime.web.js";
|
|
2
|
+
//#region ../secure-storage/src/secure-storage.web.ts
|
|
3
|
+
var hostStore = {
|
|
4
|
+
get: (key) => desktopHost()?.secureStorage.get(key) ?? Promise.resolve(null),
|
|
5
|
+
set: (key, value) => desktopHost()?.secureStorage.set(key, value) ?? Promise.resolve(false),
|
|
6
|
+
remove: (key) => desktopHost()?.secureStorage.remove(key) ?? Promise.resolve(false)
|
|
7
|
+
};
|
|
8
|
+
var secureStorage = {
|
|
9
|
+
get supported() {
|
|
10
|
+
return desktopHost() !== null;
|
|
11
|
+
},
|
|
12
|
+
async ensure() {
|
|
13
|
+
const host = desktopHost();
|
|
14
|
+
return host && await host.supports("secureStorage", "get") ? "granted" : "unsupported";
|
|
15
|
+
},
|
|
16
|
+
get impl() {
|
|
17
|
+
return desktopHost() ? hostStore : null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { secureStorage };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sign in with Apple + Google Sign-In for Octane xplat — native SDKs on iOS/Android, platform web SDKs in the browser",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Sign in with Apple + Google Sign-In + hosted-auth sessions for Octane xplat — native SDKs on iOS/Android, platform web SDKs in the browser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
7
7
|
"apple",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"nativescript",
|
|
13
13
|
"oauth",
|
|
14
14
|
"octane",
|
|
15
|
+
"pkce",
|
|
15
16
|
"sign-in",
|
|
16
17
|
"web",
|
|
17
18
|
"xplat"
|
|
@@ -52,15 +53,18 @@
|
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"@nativescript/apple-sign-in": "2.0.0",
|
|
55
|
-
"@nativescript/google-signin": "2.1.1"
|
|
56
|
+
"@nativescript/google-signin": "2.1.1",
|
|
57
|
+
"@octane-xplat/platform": "0.11.0",
|
|
58
|
+
"@octane-xplat/secure-storage": "0.11.0"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
61
|
"@nativescript-community/octane": "0.2.4",
|
|
59
62
|
"@nativescript/core": "9.1.2",
|
|
63
|
+
"@octane-xplat/macos-renderer": "0.11.0",
|
|
60
64
|
"@octanejs/vite-plugin": "0.1.61",
|
|
61
65
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
62
66
|
"octane": "0.6.3",
|
|
63
|
-
"tsrx-typegen": "0.2.
|
|
67
|
+
"tsrx-typegen": "0.2.2",
|
|
64
68
|
"typescript": "5.9.3",
|
|
65
69
|
"vite": "8.3.0"
|
|
66
70
|
},
|
|
@@ -81,6 +85,8 @@
|
|
|
81
85
|
"build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
|
|
82
86
|
"test:packed": "node tests/packed-consumer.mjs",
|
|
83
87
|
"typegen": "tsrx-typegen --target octane",
|
|
84
|
-
"pack:check": "tsrx-typegen --pack-check"
|
|
88
|
+
"pack:check": "tsrx-typegen --pack-check",
|
|
89
|
+
"test": "node --test tests/*.test.mjs",
|
|
90
|
+
"typecheck:macos": "tsrx-tsc --noEmit -p tsconfig.macos.json"
|
|
85
91
|
}
|
|
86
92
|
}
|
|
@@ -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
|
+
}
|