authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-19 10:28:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 10:28:28-07:00
log3e01c4bc572ea6a938c9a47143c914ff042ea41a
treea2eaaca128a13a7f7583996817daeff6477679bc
parent20f37f367a146fb4118bd0f0182336f4ab8c6ac8
signature Signed by PGP key B5690EEEBB952194

Migrate desktop auth to Clerk bridge (#3092)

Co-authored-by: codex <codex@users.noreply.github.com>

67 files changed, 1986 insertions(+), 3318 deletions(-)

.env.example+7
......@@ -8,6 +8,13 @@
88# T3CODE_CLERK_JWT_TEMPLATE=t3-relay
99# T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauthapp_...
1010
11# Optional: signed macOS passkey builds. The RP domain defaults to the Frontend API
12# hostname encoded in T3CODE_CLERK_PUBLISHABLE_KEY. Set the override only when Clerk
13# returns a different RP ID or when multiple domains must be entitled.
14# T3CODE_APPLE_TEAM_ID=ABC1234567
15# T3CODE_MACOS_PROVISIONING_PROFILE=/absolute/path/to/t3code.provisionprofile
16# T3CODE_CLERK_PASSKEY_RP_DOMAINS=example.clerk.accounts.dev,clerk.example.com
17
1118# Get this from your relay deployment. `infra/relay` deploys update it automatically.
1219# T3CODE_RELAY_URL=https://relay.example.com
1320
.github/workflows/ci.yml+1
......@@ -38,6 +38,7 @@ jobs:
3838 run: |
3939 test -f apps/desktop/dist-electron/preload.cjs
4040 grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.cjs
41 grep -n "__clerk_internal_electron_passkeys" apps/desktop/dist-electron/preload.cjs
4142
4243 test:
4344 name: Test
.github/workflows/release.yml+15
......@@ -425,6 +425,9 @@ jobs:
425425 APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
426426 APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
427427 APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
428 APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
429 MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_PROVISIONING_PROFILE }}
430 T3CODE_CLERK_PASSKEY_RP_DOMAINS: ${{ vars.CLERK_PASSKEY_RP_DOMAINS }}
428431 AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
429432 AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
430433 AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
......@@ -452,9 +455,21 @@ jobs:
452455
453456 if [[ "${{ matrix.platform }}" == "mac" ]]; then
454457 if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then
458 if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then
459 echo "macOS signing is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2
460 exit 1
461 fi
462
455463 key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
456464 printf '%s' "$APPLE_API_KEY" > "$key_path"
457465 export APPLE_API_KEY="$key_path"
466
467 profile_path="$RUNNER_TEMP/t3code.provisionprofile"
468 printf '%s' "$MACOS_PROVISIONING_PROFILE" | base64 -D > "$profile_path"
469 security cms -D -i "$profile_path" >/dev/null
470 export T3CODE_APPLE_TEAM_ID="$APPLE_TEAM_ID"
471 export T3CODE_MACOS_PROVISIONING_PROFILE="$profile_path"
472
458473 echo "macOS signing enabled."
459474 args+=(--signed)
460475 else
apps/desktop/package.json+3
......@@ -12,6 +12,8 @@
1212 "smoke-test": "node scripts/smoke-test.mjs"
1313 },
1414 "dependencies": {
15 "@clerk/electron": "catalog:",
16 "@clerk/electron-passkeys": "catalog:",
1517 "@effect/platform-node": "catalog:",
1618 "@t3tools/client-runtime": "workspace:*",
1719 "@t3tools/contracts": "workspace:*",
......@@ -20,6 +22,7 @@
2022 "@t3tools/tailscale": "workspace:*",
2123 "effect": "catalog:",
2224 "electron": "41.5.0",
25 "electron-store": "^8.2.0",
2326 "electron-updater": "^6.6.2",
2427 "playwright-core": "1.60.0",
2528 "react-grab": "^0.1.32"
apps/desktop/scripts/electron-launcher.mjs+1-21
......@@ -31,20 +31,12 @@ export const APP_BUNDLE_ID = isDevelopment
3131 ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
3232 : "com.t3tools.t3code";
3333const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
34const LAUNCHER_VERSION = 11;
34const LAUNCHER_VERSION = 12;
3535const defaultIconPath = join(desktopDir, "resources", "icon.icns");
3636const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png");
3737// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.
3838const hostPlatform = NodeOS.platform();
3939
40function resolveDevelopmentProtocolCallbackPort() {
41 const configuredPort = Number.parseInt(process.env.T3CODE_PORT ?? "", 10);
42 if (Number.isInteger(configuredPort) && configuredPort > 0 && configuredPort < 65535) {
43 return configuredPort + 1;
44 }
45 return 13774;
46}
47
4840function setPlistString(plistPath, key, value) {
4941 const replaceResult = spawnSync("plutil", ["-replace", key, "-string", value, plistPath], {
5042 encoding: "utf8",
......@@ -100,7 +92,6 @@ function shellSingleQuote(value) {
10092
10193function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
10294 const mainEntryPath = join(desktopDir, "dist-electron", "main.cjs");
103 const protocolCallbackUrl = `http://127.0.0.1:${resolveDevelopmentProtocolCallbackPort()}/auth/callback`;
10495 const envEntries = [
10596 ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL],
10697 ["T3CODE_PORT", process.env.T3CODE_PORT],
......@@ -109,23 +100,12 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
109100 ["T3CODE_OTLP_TRACES_URL", process.env.T3CODE_OTLP_TRACES_URL],
110101 ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS],
111102 ["T3CODE_DESKTOP_APP_USER_MODEL_ID", APP_BUNDLE_ID],
112 ["T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED", "1"],
113 ["T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL", protocolCallbackUrl],
114103 ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);
115104 writeFileSync(
116105 targetBinaryPath,
117106 [
118107 "#!/bin/sh",
119108 ...envEntries.map(([name, value]) => `export ${name}=${shellSingleQuote(value)}`),
120 'for arg in "$@"; do',
121 ' case "$arg" in',
122 " t3code-dev://auth/callback*)",
123 ' if [ -n "$T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL" ]; then',
124 ' /usr/bin/curl -fsS --max-time 2 -X POST --data-binary "$arg" "$T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL" >/dev/null 2>&1 && exit 0',
125 " fi",
126 " ;;",
127 " esac",
128 "done",
129109 `exec ${shellSingleQuote(electronBinaryPath)} --t3code-dev-root=${shellSingleQuote(desktopDir)} ${shellSingleQuote(mainEntryPath)} "$@"`,
130110 "",
131111 ].join("\n"),
apps/desktop/src/app/DesktopApp.ts+13-5
......@@ -11,7 +11,7 @@ import * as ElectronDialog from "../electron/ElectronDialog.ts";
1111import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
1212import { installDesktopIpcHandlers } from "../ipc/DesktopIpcHandlers.ts";
1313import * as DesktopAppIdentity from "./DesktopAppIdentity.ts";
14import * as DesktopCloudAuth from "./DesktopCloudAuth.ts";
14import * as DesktopClerk from "./DesktopClerk.ts";
1515import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts";
1616import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts";
1717import * as DesktopEnvironment from "./DesktopEnvironment.ts";
......@@ -163,6 +163,16 @@ const bootstrap = Effect.gen(function* () {
163163 }
164164 const serverExposureState = yield* serverExposure.configureFromSettings({ port: backendPort });
165165 const backendConfig = yield* serverExposure.backendConfig;
166 const electronProtocol = yield* ElectronProtocol.ElectronProtocol;
167 const rendererTarget = environment.isDevelopment
168 ? Option.getOrThrow(environment.devServerUrl)
169 : backendConfig.httpBaseUrl;
170 yield* electronProtocol.registerDesktopProtocol({
171 scheme: ElectronProtocol.getDesktopScheme(environment.isDevelopment),
172 targetOrigin: rendererTarget,
173 backendOrigin: backendConfig.httpBaseUrl,
174 clerkFrontendApiHostname: DesktopClerk.desktopClerkFrontendApiHostname,
175 });
166176 yield* logBootstrapInfo("bootstrap resolved backend endpoint", {
167177 baseUrl: backendConfig.httpBaseUrl.href,
168178 });
......@@ -189,9 +199,8 @@ const startup = Effect.gen(function* () {
189199 const appIdentity = yield* DesktopAppIdentity.DesktopAppIdentity;
190200 const applicationMenu = yield* DesktopApplicationMenu.DesktopApplicationMenu;
191201 const electronApp = yield* ElectronApp.ElectronApp;
192 const electronProtocol = yield* ElectronProtocol.ElectronProtocol;
193202 const lifecycle = yield* DesktopLifecycle.DesktopLifecycle;
194 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
203 const clerk = yield* DesktopClerk.DesktopClerk;
195204 const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment;
196205 const desktopSettings = yield* DesktopAppSettings.DesktopAppSettings;
197206 const updates = yield* DesktopUpdates.DesktopUpdates;
......@@ -209,7 +218,7 @@ const startup = Effect.gen(function* () {
209218
210219 yield* appIdentity.configure;
211220 yield* lifecycle.register;
212 yield* cloudAuth.configure;
221 yield* clerk.configure;
213222
214223 yield* electronApp.whenReady.pipe(
215224 Effect.withSpan("desktop.electron.whenReady"),
......@@ -218,7 +227,6 @@ const startup = Effect.gen(function* () {
218227 yield* logStartupInfo("app ready");
219228 yield* appIdentity.configure;
220229 yield* applicationMenu.configure;
221 yield* electronProtocol.registerDesktopFileProtocol;
222230 yield* updates.configure;
223231 yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause)));
224232}).pipe(Effect.withSpan("desktop.startup"));
apps/desktop/src/app/DesktopClerk.test.ts created+95
......@@ -0,0 +1,95 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import { vi } from "vite-plus/test";
5
6const { createClerkBridgeMock, storageAdapter, storageMock } = vi.hoisted(() => ({
7 createClerkBridgeMock: vi.fn(),
8 storageAdapter: {
9 getItem: vi.fn(),
10 setItem: vi.fn(),
11 removeItem: vi.fn(),
12 },
13 storageMock: vi.fn(),
14}));
15
16vi.mock("@clerk/electron", () => ({
17 createClerkBridge: createClerkBridgeMock,
18}));
19
20vi.mock("@clerk/electron/storage", () => ({
21 storage: storageMock,
22}));
23
24import {
25 createDesktopClerkBridge,
26 resolveDesktopClerkFrontendApiHostname,
27} from "./DesktopClerk.ts";
28import * as DesktopClerk from "./DesktopClerk.ts";
29import * as DesktopEnvironment from "./DesktopEnvironment.ts";
30
31describe("DesktopClerk", () => {
32 it("derives the Clerk Frontend API hostname used by the desktop CSP", () => {
33 const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`;
34
35 assert.equal(resolveDesktopClerkFrontendApiHostname(publishableKey), "clerk.t3.codes");
36 assert.equal(resolveDesktopClerkFrontendApiHostname(""), undefined);
37 assert.equal(resolveDesktopClerkFrontendApiHostname("invalid"), undefined);
38 });
39
40 it.effect("acquires and releases the SDK bridge with the layer", () => {
41 const cleanup = vi.fn();
42 storageMock.mockReturnValue(storageAdapter);
43 createClerkBridgeMock.mockReturnValue({ cleanup });
44 const environment = DesktopEnvironment.DesktopEnvironment.of({
45 stateDir: "/tmp/t3-state",
46 isDevelopment: true,
47 } as unknown as DesktopEnvironment.DesktopEnvironmentShape);
48
49 return Effect.gen(function* () {
50 yield* Effect.scoped(
51 Layer.build(
52 DesktopClerk.layer.pipe(
53 Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
54 ),
55 ),
56 );
57
58 assert.deepEqual(createClerkBridgeMock.mock.calls, [
59 [
60 {
61 storage: storageAdapter,
62 passkeys: true,
63 renderer: { scheme: "t3code-dev", host: "app" },
64 },
65 ],
66 ]);
67 assert.equal(cleanup.mock.calls.length, 1);
68 storageMock.mockClear();
69 createClerkBridgeMock.mockClear();
70 });
71 });
72
73 it.each([
74 { isDevelopment: true, scheme: "t3code-dev" },
75 { isDevelopment: false, scheme: "t3code" },
76 ])("configures the SDK with the $scheme renderer origin", ({ isDevelopment, scheme }) => {
77 const bridge = { cleanup: vi.fn() };
78 storageMock.mockReturnValue(storageAdapter);
79 createClerkBridgeMock.mockReturnValue(bridge);
80
81 assert.equal(createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge);
82 assert.deepEqual(storageMock.mock.calls, [[{ path: "/tmp/t3-state" }]]);
83 assert.deepEqual(createClerkBridgeMock.mock.calls, [
84 [
85 {
86 storage: storageAdapter,
87 passkeys: true,
88 renderer: { scheme, host: "app" },
89 },
90 ],
91 ]);
92 storageMock.mockClear();
93 createClerkBridgeMock.mockClear();
94 });
95});
apps/desktop/src/app/DesktopClerk.ts created+92
......@@ -0,0 +1,92 @@
1import { createClerkBridge } from "@clerk/electron";
2import { storage } from "@clerk/electron/storage";
3import * as Context from "effect/Context";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Scope from "effect/Scope";
8
9import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
10import * as ElectronApp from "../electron/ElectronApp.ts";
11import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
12import * as ElectronWindow from "../electron/ElectronWindow.ts";
13import * as DesktopEnvironment from "./DesktopEnvironment.ts";
14
15declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;
16
17export interface DesktopClerkShape {
18 readonly configure: Effect.Effect<
19 void,
20 never,
21 ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope
22 >;
23}
24
25export class DesktopClerk extends Context.Service<DesktopClerk, DesktopClerkShape>()(
26 "@t3tools/desktop/app/DesktopClerk",
27) {}
28
29export function resolveDesktopClerkFrontendApiHostname(
30 publishableKey: string | undefined,
31): string | undefined {
32 const normalizedKey = publishableKey?.trim();
33 if (!normalizedKey) return undefined;
34
35 try {
36 return clerkFrontendApiHostnameFromPublishableKey(normalizedKey);
37 } catch {
38 return undefined;
39 }
40}
41
42export const desktopClerkFrontendApiHostname = resolveDesktopClerkFrontendApiHostname(
43 typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined"
44 ? undefined
45 : __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__,
46);
47
48export function createDesktopClerkBridge(stateDir: string, isDevelopment: boolean) {
49 return createClerkBridge({
50 storage: storage({ path: stateDir }),
51 passkeys: true,
52 renderer: {
53 scheme: ElectronProtocol.getDesktopScheme(isDevelopment),
54 host: ElectronProtocol.DESKTOP_HOST,
55 },
56 });
57}
58
59const make = Effect.gen(function* () {
60 const environment = yield* DesktopEnvironment.DesktopEnvironment;
61 yield* Effect.acquireRelease(
62 Effect.sync(() => createDesktopClerkBridge(environment.stateDir, environment.isDevelopment)),
63 (bridge) => Effect.sync(() => bridge.cleanup()),
64 );
65
66 return DesktopClerk.of({
67 configure: Effect.gen(function* () {
68 const electronApp = yield* ElectronApp.ElectronApp;
69 const electronWindow = yield* ElectronWindow.ElectronWindow;
70 const context = yield* Effect.context<ElectronWindow.ElectronWindow>();
71 const runPromise = Effect.runPromiseWith(context);
72
73 if (!(yield* electronApp.requestSingleInstanceLock)) {
74 yield* electronApp.quit;
75 return yield* Effect.interrupt;
76 }
77
78 yield* electronApp.on("second-instance", () => {
79 void runPromise(
80 Effect.gen(function* () {
81 const mainWindow = yield* electronWindow.currentMainOrFirst;
82 if (Option.isSome(mainWindow)) {
83 yield* electronWindow.reveal(mainWindow.value);
84 }
85 }),
86 );
87 });
88 }).pipe(Effect.withSpan("desktop.clerk.configure")),
89 });
90});
91
92export const layer = Layer.effect(DesktopClerk, make);
apps/desktop/src/app/DesktopCloudAuth.test.ts deleted-302
......@@ -1,302 +0,0 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6
7import * as ElectronApp from "../electron/ElectronApp.ts";
8import * as ElectronWindow from "../electron/ElectronWindow.ts";
9import * as IpcChannels from "../ipc/channels.ts";
10import * as DesktopCloudAuth from "./DesktopCloudAuth.ts";
11import * as DesktopEnvironment from "./DesktopEnvironment.ts";
12
13interface CloudAuthHarness {
14 readonly app: ElectronApp.ElectronAppShape;
15 readonly window: ElectronWindow.ElectronWindowShape;
16 readonly listeners: Map<string, readonly ((...args: readonly unknown[]) => void)[]>;
17 readonly protocolRegistrations: {
18 readonly protocol: string;
19 readonly path?: string;
20 readonly args?: readonly string[];
21 }[];
22 readonly sends: { readonly channel: string; readonly args: readonly unknown[] }[];
23 readonly reveals: unknown[];
24 readonly layer: Layer.Layer<
25 | DesktopCloudAuth.DesktopCloudAuth
26 | DesktopEnvironment.DesktopEnvironment
27 | ElectronApp.ElectronApp
28 | ElectronWindow.ElectronWindow
29 >;
30}
31
32function makeHarness(input: { readonly isDevelopment: boolean }): CloudAuthHarness {
33 const listeners = new Map<string, ((...args: readonly unknown[]) => void)[]>();
34 const protocolRegistrations: CloudAuthHarness["protocolRegistrations"] = [];
35 const sends: CloudAuthHarness["sends"] = [];
36 const reveals: unknown[] = [];
37 const mainWindow = { id: "main-window" };
38
39 const app = ElectronApp.ElectronApp.of({
40 metadata: Effect.succeed({
41 appVersion: "0.0.0-test",
42 appPath: "/tmp/t3-code-test",
43 isPackaged: !input.isDevelopment,
44 resourcesPath: "/tmp/t3-code-test/resources",
45 runningUnderArm64Translation: false,
46 }),
47 name: Effect.succeed("T3 Code"),
48 whenReady: Effect.void,
49 quit: Effect.void,
50 exit: () => Effect.void,
51 relaunch: () => Effect.void,
52 setPath: () => Effect.void,
53 setName: () => Effect.void,
54 setAboutPanelOptions: () => Effect.void,
55 setAppUserModelId: () => Effect.void,
56 requestSingleInstanceLock: Effect.succeed(true),
57 isDefaultProtocolClient: () => Effect.succeed(false),
58 setAsDefaultProtocolClient: (protocol, path, args) =>
59 Effect.sync(() => {
60 protocolRegistrations.push({
61 protocol,
62 ...(path === undefined ? {} : { path }),
63 ...(args === undefined ? {} : { args }),
64 });
65 return true;
66 }),
67 setDesktopName: () => Effect.void,
68 setDockIcon: () => Effect.void,
69 appendCommandLineSwitch: () => Effect.void,
70 on: (eventName, listener) =>
71 Effect.sync(() => {
72 const erasedListener = listener as (...args: readonly unknown[]) => void;
73 listeners.set(eventName, [...(listeners.get(eventName) ?? []), erasedListener]);
74 }),
75 });
76
77 const window = ElectronWindow.ElectronWindow.of({
78 create: () => Effect.die("not used"),
79 main: Effect.succeed(Option.some(mainWindow as never)),
80 currentMainOrFirst: Effect.succeed(Option.some(mainWindow as never)),
81 focusedMainOrFirst: Effect.succeed(Option.some(mainWindow as never)),
82 setMain: () => Effect.void,
83 clearMain: () => Effect.void,
84 reveal: (target) =>
85 Effect.sync(() => {
86 reveals.push(target);
87 }),
88 sendAll: (channel, ...args) =>
89 Effect.sync(() => {
90 sends.push({ channel, args });
91 }),
92 destroyAll: Effect.void,
93 syncAllAppearance: () => Effect.void,
94 });
95
96 const environment = DesktopEnvironment.DesktopEnvironment.of({
97 isDevelopment: input.isDevelopment,
98 } as DesktopEnvironment.DesktopEnvironmentShape);
99 const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment);
100
101 return {
102 app,
103 window,
104 listeners,
105 protocolRegistrations,
106 sends,
107 reveals,
108 layer: Layer.mergeAll(
109 DesktopCloudAuth.layer.pipe(
110 Layer.provideMerge(environmentLayer),
111 Layer.provide(NodeServices.layer),
112 ),
113 Layer.succeed(ElectronApp.ElectronApp, app),
114 Layer.succeed(ElectronWindow.ElectronWindow, window),
115 ),
116 };
117}
118
119function emitAppEvent(
120 harness: CloudAuthHarness,
121 eventName: string,
122 ...args: readonly unknown[]
123): void {
124 for (const listener of harness.listeners.get(eventName) ?? []) {
125 listener(...args);
126 }
127}
128
129const flushCloudAuthDispatch = Effect.promise(() => Promise.resolve());
130
131describe("DesktopCloudAuth", () => {
132 it("uses separate callback schemes for packaged and development builds", () => {
133 assert.equal(
134 DesktopCloudAuth.resolveCloudAuthCallbackScheme({ isDevelopment: false }),
135 "t3code",
136 );
137 assert.equal(
138 DesktopCloudAuth.resolveCloudAuthCallbackScheme({ isDevelopment: true }),
139 "t3code-dev",
140 );
141 });
142
143 it("builds a native callback URL with request state", () => {
144 assert.equal(
145 DesktopCloudAuth.buildCloudAuthCallbackUrl({
146 scheme: "t3code",
147 state: "state-1",
148 }),
149 "t3code://auth/callback?t3_state=state-1",
150 );
151 });
152
153 it("accepts only the expected scheme, host, path, and state", () => {
154 assert.isNotNull(
155 DesktopCloudAuth.parseCloudAuthCallbackUrl({
156 rawUrl: "t3code://auth/callback?rotating_token_nonce=nonce&t3_state=state-1",
157 scheme: "t3code",
158 state: "state-1",
159 }),
160 );
161 assert.isNull(
162 DesktopCloudAuth.parseCloudAuthCallbackUrl({
163 rawUrl: "t3code://auth/callback?rotating_token_nonce=nonce&t3_state=wrong",
164 scheme: "t3code",
165 state: "state-1",
166 }),
167 );
168 assert.isNull(
169 DesktopCloudAuth.parseCloudAuthCallbackUrl({
170 rawUrl: "https://example.com/callback?rotating_token_nonce=nonce&t3_state=state-1",
171 scheme: "t3code",
172 state: "state-1",
173 }),
174 );
175 });
176
177 it("builds a native development callback URL with request state", () => {
178 assert.equal(
179 DesktopCloudAuth.buildCloudAuthCallbackUrl({
180 scheme: "t3code-dev",
181 state: "state-1",
182 }),
183 "t3code-dev://auth/callback?t3_state=state-1",
184 );
185 });
186
187 it.effect("registers the development protocol client and dispatches matching callbacks", () => {
188 const harness = makeHarness({ isDevelopment: true });
189
190 return Effect.gen(function* () {
191 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
192 yield* cloudAuth.configure;
193 const redirectUrl = yield* cloudAuth.createRequest;
194 const callbackUrl = new URL(redirectUrl);
195 callbackUrl.searchParams.set("rotating_token_nonce", "nonce-1");
196
197 let prevented = false;
198 emitAppEvent(
199 harness,
200 "open-url",
201 { preventDefault: () => (prevented = true) },
202 callbackUrl.toString(),
203 );
204 yield* flushCloudAuthDispatch;
205
206 assert.isTrue(prevented);
207 assert.deepEqual(
208 harness.protocolRegistrations.map((registration) => registration.protocol),
209 ["t3code-dev"],
210 );
211 assert.isString(harness.protocolRegistrations[0]?.path);
212 assert.isArray(harness.protocolRegistrations[0]?.args);
213 assert.deepEqual(harness.sends, [
214 {
215 channel: IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
216 args: [callbackUrl.toString()],
217 },
218 ]);
219 assert.lengthOf(harness.reveals, 1);
220 }).pipe(Effect.provide(harness.layer), Effect.scoped);
221 });
222
223 it.effect("rejects mismatched callback state and only consumes the pending request once", () => {
224 const harness = makeHarness({ isDevelopment: false });
225
226 return Effect.gen(function* () {
227 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
228 yield* cloudAuth.configure;
229 const redirectUrl = yield* cloudAuth.createRequest;
230 const validCallback = new URL(redirectUrl);
231 validCallback.searchParams.set("rotating_token_nonce", "nonce-1");
232 const invalidCallback = new URL(validCallback);
233 invalidCallback.searchParams.set(DesktopCloudAuth.CLOUD_AUTH_CALLBACK_STATE_PARAM, "wrong");
234
235 emitAppEvent(
236 harness,
237 "open-url",
238 { preventDefault: () => undefined },
239 invalidCallback.toString(),
240 );
241 yield* flushCloudAuthDispatch;
242 assert.deepEqual(harness.sends, []);
243
244 emitAppEvent(
245 harness,
246 "open-url",
247 { preventDefault: () => undefined },
248 validCallback.toString(),
249 );
250 yield* flushCloudAuthDispatch;
251 emitAppEvent(
252 harness,
253 "open-url",
254 { preventDefault: () => undefined },
255 validCallback.toString(),
256 );
257 yield* flushCloudAuthDispatch;
258
259 assert.deepEqual(
260 harness.protocolRegistrations.map((registration) => registration.protocol),
261 ["t3code"],
262 );
263 assert.deepEqual(harness.sends, [
264 {
265 channel: IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
266 args: [validCallback.toString()],
267 },
268 ]);
269 }).pipe(Effect.provide(harness.layer), Effect.scoped);
270 });
271
272 it.effect(
273 "routes second-instance callbacks and reveals the window for non-callback launches",
274 () => {
275 const harness = makeHarness({ isDevelopment: true });
276
277 return Effect.gen(function* () {
278 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
279 yield* cloudAuth.configure;
280 const redirectUrl = yield* cloudAuth.createRequest;
281 const callbackUrl = new URL(redirectUrl);
282 callbackUrl.searchParams.set("rotating_token_nonce", "nonce-1");
283
284 emitAppEvent(harness, "second-instance", {}, ["electron", callbackUrl.toString()]);
285 yield* flushCloudAuthDispatch;
286
287 const revealCountAfterCallback = harness.reveals.length;
288 emitAppEvent(harness, "second-instance", {}, ["electron", "--opened-from-dock"]);
289 yield* flushCloudAuthDispatch;
290
291 assert.deepEqual(harness.sends, [
292 {
293 channel: IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
294 args: [callbackUrl.toString()],
295 },
296 ]);
297 assert.equal(revealCountAfterCallback, 1);
298 assert.equal(harness.reveals.length, 2);
299 }).pipe(Effect.provide(harness.layer), Effect.scoped);
300 },
301 );
302});
apps/desktop/src/app/DesktopCloudAuth.ts deleted-330
......@@ -1,330 +0,0 @@
1import * as Context from "effect/Context";
2import * as Crypto from "effect/Crypto";
3import * as Data from "effect/Data";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Scope from "effect/Scope";
8import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
9
10import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
11import * as ElectronApp from "../electron/ElectronApp.ts";
12import * as ElectronWindow from "../electron/ElectronWindow.ts";
13import * as IpcChannels from "../ipc/channels.ts";
14import * as DesktopEnvironment from "./DesktopEnvironment.ts";
15import type * as Electron from "electron";
16
17export const CLOUD_AUTH_CALLBACK_HOST = "auth";
18export const CLOUD_AUTH_CALLBACK_PATHNAME = "/callback";
19export const CLOUD_AUTH_CALLBACK_STATE_PARAM = "t3_state";
20export const CLOUD_AUTH_CALLBACK_SCHEME = "t3code";
21export const DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME = "t3code-dev";
22
23const CLOUD_AUTH_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
24
25export class DesktopCloudAuthCallbackServerError extends Data.TaggedError(
26 "DesktopCloudAuthCallbackServerError",
27)<{
28 readonly cause: unknown;
29}> {
30 override get message() {
31 return "Failed to start the desktop cloud auth callback server.";
32 }
33}
34
35interface PendingCloudAuthRequest {
36 readonly state: string;
37 readonly redirectUrl: string;
38 readonly close: () => void;
39}
40
41export interface DesktopCloudAuthShape {
42 readonly createRequest: Effect.Effect<string, DesktopCloudAuthCallbackServerError>;
43 readonly configure: Effect.Effect<
44 void,
45 never,
46 ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope
47 >;
48}
49
50export class DesktopCloudAuth extends Context.Service<DesktopCloudAuth, DesktopCloudAuthShape>()(
51 "@t3tools/desktop/app/DesktopCloudAuth",
52) {}
53
54export function resolveCloudAuthCallbackScheme(input: { readonly isDevelopment: boolean }): string {
55 return input.isDevelopment ? DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME : CLOUD_AUTH_CALLBACK_SCHEME;
56}
57
58export function buildCloudAuthCallbackUrl(input: {
59 readonly scheme: string;
60 readonly state: string;
61}): string {
62 const url = new URL(
63 `${input.scheme}://${CLOUD_AUTH_CALLBACK_HOST}${CLOUD_AUTH_CALLBACK_PATHNAME}`,
64 );
65 url.searchParams.set(CLOUD_AUTH_CALLBACK_STATE_PARAM, input.state);
66 return url.toString();
67}
68
69export function parseCloudAuthCallbackUrl(input: {
70 readonly rawUrl: unknown;
71 readonly scheme: string;
72 readonly state: string;
73}): URL | null {
74 if (typeof input.rawUrl !== "string") {
75 return null;
76 }
77
78 try {
79 const url = new URL(input.rawUrl);
80 if (url.protocol !== `${input.scheme}:`) return null;
81 if (url.hostname !== CLOUD_AUTH_CALLBACK_HOST) return null;
82 if (url.pathname !== CLOUD_AUTH_CALLBACK_PATHNAME) return null;
83 if (url.searchParams.get(CLOUD_AUTH_CALLBACK_STATE_PARAM) !== input.state) return null;
84 return url;
85 } catch {
86 return null;
87 }
88}
89
90export function findCloudAuthCallbackUrl(input: {
91 readonly values: readonly unknown[];
92 readonly scheme: string;
93 readonly state: string;
94}): URL | null {
95 for (const value of input.values) {
96 const url = parseCloudAuthCallbackUrl({
97 rawUrl: value,
98 scheme: input.scheme,
99 state: input.state,
100 });
101 if (url) return url;
102 }
103 return null;
104}
105
106export function resolveProtocolClientLaunchArgs(input: {
107 readonly argv: readonly string[];
108}): readonly string[] {
109 return input.argv.slice(1);
110}
111
112function resolveConfiguredProtocolClient(): {
113 readonly path: string;
114 readonly args: readonly string[];
115} | null {
116 const path = process.env.T3CODE_DESKTOP_PROTOCOL_CLIENT_PATH?.trim();
117 if (!path) return null;
118
119 return {
120 path,
121 args: (process.env.T3CODE_DESKTOP_PROTOCOL_CLIENT_ARGS ?? "")
122 .split("\n")
123 .map((arg) => arg.trim())
124 .filter((arg) => arg.length > 0),
125 };
126}
127
128function isProtocolRegistrationManagedExternally(): boolean {
129 return process.env.T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED?.trim() === "1";
130}
131
132function resolveProtocolCallbackForwardUrl(): URL | null {
133 const rawUrl = process.env.T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL?.trim();
134 if (!rawUrl) return null;
135
136 try {
137 const url = new URL(rawUrl);
138 if (url.protocol !== "http:") return null;
139 if (url.hostname !== "127.0.0.1") return null;
140 if (url.pathname !== "/auth/callback") return null;
141 if (!url.port) return null;
142 return url;
143 } catch {
144 return null;
145 }
146}
147
148const closeCloudAuthRequest = (request: PendingCloudAuthRequest | null): null => {
149 request?.close();
150 return null;
151};
152
153function createCloudAuthRequestTimeout(onExpire: () => void): ReturnType<typeof setTimeout> {
154 // @effect-diagnostics-next-line globalTimers:off - Auth request expiry is tied to an Electron callback server, not fiber scheduling.
155 return setTimeout(onExpire, CLOUD_AUTH_REQUEST_TIMEOUT_MS);
156}
157
158function ignoreCloudAuthCallback(_rawUrl: string) {}
159
160function startProtocolCallbackForwardServer(
161 callbackUrl: URL,
162 dispatch: (rawUrl: string) => void,
163): Effect.Effect<void, never, Scope.Scope> {
164 const port = Number.parseInt(callbackUrl.port, 10);
165 const routesLayer = HttpRouter.add(
166 "POST",
167 "/auth/callback",
168 Effect.gen(function* () {
169 const request = yield* HttpServerRequest.HttpServerRequest;
170 const rawUrl = yield* request.text;
171 yield* Effect.sync(() => {
172 dispatch(rawUrl);
173 });
174 return HttpServerResponse.empty({ status: 204 });
175 }),
176 );
177
178 return Effect.gen(function* () {
179 const NodeHttp = yield* Effect.promise(() => import("node:http"));
180 const serverLayer = NodeHttpServer.layer(NodeHttp.createServer, {
181 host: callbackUrl.hostname,
182 port,
183 });
184 yield* Layer.launch(HttpRouter.serve(routesLayer).pipe(Layer.provideMerge(serverLayer))).pipe(
185 Effect.forkScoped,
186 );
187 });
188}
189
190const make = Effect.gen(function* () {
191 const crypto = yield* Crypto.Crypto;
192 const environment = yield* DesktopEnvironment.DesktopEnvironment;
193 let pendingAuthRequest: PendingCloudAuthRequest | null = null;
194 let dispatchCloudAuthCallback: (rawUrl: string) => void = ignoreCloudAuthCallback;
195 const makeCloudAuthRequestState = Effect.gen(function* () {
196 const [left, right] = yield* Effect.all([crypto.randomUUIDv4, crypto.randomUUIDv4]);
197 return `${left}${right}`.replaceAll("-", "");
198 });
199
200 return DesktopCloudAuth.of({
201 createRequest: Effect.gen(function* () {
202 const scheme = resolveCloudAuthCallbackScheme({
203 isDevelopment: environment.isDevelopment,
204 });
205 const state = yield* makeCloudAuthRequestState.pipe(
206 Effect.mapError((cause) => new DesktopCloudAuthCallbackServerError({ cause })),
207 );
208
209 pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);
210
211 const redirectUrl = buildCloudAuthCallbackUrl({ scheme, state });
212 const timeout = createCloudAuthRequestTimeout(() => {
213 pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);
214 });
215 pendingAuthRequest = {
216 state,
217 redirectUrl,
218 close: () => clearTimeout(timeout),
219 };
220 return redirectUrl;
221 }),
222 configure: Effect.gen(function* () {
223 const electronApp = yield* ElectronApp.ElectronApp;
224 const electronWindow = yield* ElectronWindow.ElectronWindow;
225 const scope = yield* Scope.Scope;
226 const context = yield* Effect.context<ElectronWindow.ElectronWindow>();
227 const runPromise = Effect.runPromiseWith(context);
228 const scheme = resolveCloudAuthCallbackScheme({
229 isDevelopment: environment.isDevelopment,
230 });
231
232 yield* Scope.addFinalizer(
233 scope,
234 Effect.sync(() => {
235 pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);
236 }),
237 );
238
239 if (isProtocolRegistrationManagedExternally()) {
240 // Development macOS launchers set the default URL handler before the stock Electron
241 // process starts so LaunchServices binds the scheme to the worktree-specific app bundle.
242 } else if (environment.isDevelopment) {
243 const configuredClient = resolveConfiguredProtocolClient();
244 if (configuredClient) {
245 yield* electronApp.setAsDefaultProtocolClient(
246 scheme,
247 configuredClient.path,
248 configuredClient.args,
249 );
250 } else {
251 yield* electronApp.setAsDefaultProtocolClient(
252 scheme,
253 process.execPath,
254 resolveProtocolClientLaunchArgs({ argv: process.argv }),
255 );
256 }
257 } else {
258 yield* electronApp.setAsDefaultProtocolClient(scheme);
259 }
260
261 dispatchCloudAuthCallback = (rawUrl: string) => {
262 const pending = pendingAuthRequest;
263 const callbackUrl = pending
264 ? parseCloudAuthCallbackUrl({ rawUrl, scheme, state: pending.state })
265 : null;
266 if (!callbackUrl) {
267 return;
268 }
269
270 pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);
271 void runPromise(
272 Effect.gen(function* () {
273 yield* electronWindow.sendAll(
274 IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
275 callbackUrl.toString(),
276 );
277 const mainWindow = yield* electronWindow.currentMainOrFirst;
278 if (Option.isSome(mainWindow)) {
279 yield* electronWindow.reveal(mainWindow.value);
280 }
281 }),
282 );
283 };
284
285 const protocolCallbackForwardUrl = resolveProtocolCallbackForwardUrl();
286 if (environment.isDevelopment && protocolCallbackForwardUrl) {
287 yield* startProtocolCallbackForwardServer(
288 protocolCallbackForwardUrl,
289 dispatchCloudAuthCallback,
290 );
291 }
292
293 const hasInstanceLock = yield* electronApp.requestSingleInstanceLock;
294 if (!hasInstanceLock) {
295 return yield* electronApp.quit;
296 }
297
298 yield* electronApp.on<[Electron.Event, string]>("open-url", (event, rawUrl) => {
299 event.preventDefault?.();
300 dispatchCloudAuthCallback(rawUrl);
301 });
302
303 yield* electronApp.on<[Electron.Event, readonly string[]]>(
304 "second-instance",
305 (_event, argv) => {
306 const values = resolveProtocolClientLaunchArgs({ argv });
307 const pending = pendingAuthRequest;
308 const callbackUrl = pending
309 ? findCloudAuthCallbackUrl({ values, scheme, state: pending.state })
310 : null;
311 if (callbackUrl) {
312 dispatchCloudAuthCallback(callbackUrl.toString());
313 return;
314 }
315
316 void runPromise(
317 Effect.gen(function* () {
318 const mainWindow = yield* electronWindow.currentMainOrFirst;
319 if (Option.isSome(mainWindow)) {
320 yield* electronWindow.reveal(mainWindow.value);
321 }
322 }),
323 );
324 },
325 );
326 }).pipe(Effect.withSpan("desktop.cloudAuth.configure")),
327 });
328});
329
330export const layer = Layer.effect(DesktopCloudAuth, make);
apps/desktop/src/app/DesktopCloudAuthTokenStore.test.ts deleted-96
......@@ -1,96 +0,0 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7
8import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
9import * as DesktopConfig from "./DesktopConfig.ts";
10import * as DesktopEnvironment from "./DesktopEnvironment.ts";
11import * as DesktopCloudAuthTokenStore from "./DesktopCloudAuthTokenStore.ts";
12
13const textDecoder = new TextDecoder();
14const textEncoder = new TextEncoder();
15
16function makeSafeStorageLayer(input: { readonly available: boolean }) {
17 return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, {
18 isEncryptionAvailable: Effect.succeed(input.available),
19 encryptString: (value) => Effect.succeed(textEncoder.encode(`enc:${value}`)),
20 decryptString: (value) => {
21 const decoded = textDecoder.decode(value);
22 if (!decoded.startsWith("enc:")) {
23 return Effect.fail(
24 new ElectronSafeStorage.ElectronSafeStorageDecryptError({
25 cause: new Error("invalid encrypted token"),
26 }),
27 );
28 }
29 return Effect.succeed(decoded.slice("enc:".length));
30 },
31 } satisfies ElectronSafeStorage.ElectronSafeStorageShape);
32}
33
34function makeLayer(baseDir: string, input?: { readonly encryptionAvailable?: boolean }) {
35 const environmentLayer = DesktopEnvironment.layer({
36 dirname: "/repo/apps/desktop/src",
37 homeDirectory: baseDir,
38 platform: "darwin",
39 processArch: "x64",
40 appVersion: "1.2.3",
41 appPath: "/repo",
42 isPackaged: true,
43 resourcesPath: "/missing/resources",
44 runningUnderArm64Translation: false,
45 }).pipe(
46 Layer.provide(
47 Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),
48 ),
49 );
50
51 return DesktopCloudAuthTokenStore.layer.pipe(
52 Layer.provideMerge(environmentLayer),
53 Layer.provideMerge(makeSafeStorageLayer({ available: input?.encryptionAvailable ?? true })),
54 Layer.provideMerge(NodeServices.layer),
55 );
56}
57
58const withTokenStore = <A, E, R>(
59 effect: Effect.Effect<A, E, R | DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore>,
60 input?: { readonly encryptionAvailable?: boolean },
61) =>
62 Effect.gen(function* () {
63 const fileSystem = yield* FileSystem.FileSystem;
64 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
65 prefix: "t3-desktop-cloud-auth-token-test-",
66 });
67 return yield* effect.pipe(Effect.provide(makeLayer(baseDir, input)));
68 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped);
69
70describe("DesktopCloudAuthTokenStore", () => {
71 it.effect("persists, reads, and clears the encrypted Clerk client JWT", () =>
72 withTokenStore(
73 Effect.gen(function* () {
74 const tokenStore = yield* DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore;
75
76 assert.isTrue(yield* tokenStore.set("__client=test.jwt"));
77 assert.deepStrictEqual(yield* tokenStore.get, Option.some("__client=test.jwt"));
78
79 yield* tokenStore.clear;
80 assert.deepStrictEqual(yield* tokenStore.get, Option.none());
81 }),
82 ),
83 );
84
85 it.effect("does not persist a token when Electron safe storage is unavailable", () =>
86 withTokenStore(
87 Effect.gen(function* () {
88 const tokenStore = yield* DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore;
89
90 assert.isFalse(yield* tokenStore.set("__client=test.jwt"));
91 assert.deepStrictEqual(yield* tokenStore.get, Option.none());
92 }),
93 { encryptionAvailable: false },
94 ),
95 );
96});
apps/desktop/src/app/DesktopCloudAuthTokenStore.ts deleted-155
......@@ -1,155 +0,0 @@
1import { fromLenientJson } from "@t3tools/shared/schemaJson";
2import * as Context from "effect/Context";
3import * as Crypto from "effect/Crypto";
4import * as Data from "effect/Data";
5import * as Effect from "effect/Effect";
6import * as Encoding from "effect/Encoding";
7import * as FileSystem from "effect/FileSystem";
8import * as Layer from "effect/Layer";
9import * as Option from "effect/Option";
10import * as Path from "effect/Path";
11import * as PlatformError from "effect/PlatformError";
12import * as Schema from "effect/Schema";
13
14import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
15import * as DesktopEnvironment from "./DesktopEnvironment.ts";
16
17interface CloudAuthTokenDocument {
18 readonly version: number;
19 readonly encryptedClientJwt: string;
20}
21
22const CloudAuthTokenDocumentSchema = Schema.Struct({
23 version: Schema.Number,
24 encryptedClientJwt: Schema.String,
25});
26
27const CloudAuthTokenDocumentJson = fromLenientJson(CloudAuthTokenDocumentSchema);
28const decodeCloudAuthTokenDocumentJson = Schema.decodeEffect(CloudAuthTokenDocumentJson);
29const encodeCloudAuthTokenDocumentJson = Schema.encodeEffect(CloudAuthTokenDocumentJson);
30
31export class DesktopCloudAuthTokenStoreWriteError extends Data.TaggedError(
32 "DesktopCloudAuthTokenStoreWriteError",
33)<{
34 readonly cause: PlatformError.PlatformError | Schema.SchemaError;
35}> {
36 override get message() {
37 return `Failed to write desktop cloud auth token: ${this.cause.message}`;
38 }
39}
40
41export class DesktopCloudAuthTokenStoreDecodeError extends Data.TaggedError(
42 "DesktopCloudAuthTokenStoreDecodeError",
43)<{
44 readonly cause: Encoding.EncodingError;
45}> {
46 override get message() {
47 return "Failed to decode desktop cloud auth token.";
48 }
49}
50
51export interface DesktopCloudAuthTokenStoreShape {
52 readonly get: Effect.Effect<
53 Option.Option<string>,
54 | DesktopCloudAuthTokenStoreDecodeError
55 | ElectronSafeStorage.ElectronSafeStorageAvailabilityError
56 | ElectronSafeStorage.ElectronSafeStorageDecryptError
57 >;
58 readonly set: (
59 token: string,
60 ) => Effect.Effect<
61 boolean,
62 | DesktopCloudAuthTokenStoreWriteError
63 | ElectronSafeStorage.ElectronSafeStorageAvailabilityError
64 | ElectronSafeStorage.ElectronSafeStorageEncryptError
65 >;
66 readonly clear: Effect.Effect<void>;
67}
68
69export class DesktopCloudAuthTokenStore extends Context.Service<
70 DesktopCloudAuthTokenStore,
71 DesktopCloudAuthTokenStoreShape
72>()("@t3tools/desktop/app/DesktopCloudAuthTokenStore") {}
73
74function decodeSecretBytes(
75 encoded: string,
76): Effect.Effect<Uint8Array, DesktopCloudAuthTokenStoreDecodeError> {
77 return Effect.fromResult(Encoding.decodeBase64(encoded)).pipe(
78 Effect.mapError((cause) => new DesktopCloudAuthTokenStoreDecodeError({ cause })),
79 );
80}
81
82const readDocument = (
83 fileSystem: FileSystem.FileSystem,
84 tokenPath: string,
85): Effect.Effect<Option.Option<CloudAuthTokenDocument>> =>
86 fileSystem.readFileString(tokenPath).pipe(
87 Effect.option,
88 Effect.flatMap(
89 Option.match({
90 onNone: () => Effect.succeed(Option.none<CloudAuthTokenDocument>()),
91 onSome: (raw) => decodeCloudAuthTokenDocumentJson(raw).pipe(Effect.option),
92 }),
93 ),
94 );
95
96const writeDocument = Effect.fn("desktop.cloudAuthTokenStore.writeDocument")(function* (input: {
97 readonly fileSystem: FileSystem.FileSystem;
98 readonly path: Path.Path;
99 readonly tokenPath: string;
100 readonly document: CloudAuthTokenDocument;
101 readonly suffix: string;
102}): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> {
103 const directory = input.path.dirname(input.tokenPath);
104 const tempPath = `${input.tokenPath}.${process.pid}.${input.suffix}.tmp`;
105 const encoded = yield* encodeCloudAuthTokenDocumentJson(input.document);
106 yield* input.fileSystem.makeDirectory(directory, { recursive: true });
107 yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`);
108 yield* input.fileSystem.rename(tempPath, input.tokenPath);
109});
110
111export const layer = Layer.effect(
112 DesktopCloudAuthTokenStore,
113 Effect.gen(function* () {
114 const environment = yield* DesktopEnvironment.DesktopEnvironment;
115 const fileSystem = yield* FileSystem.FileSystem;
116 const path = yield* Path.Path;
117 const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage;
118 const crypto = yield* Crypto.Crypto;
119 const tokenPath = path.join(environment.stateDir, "cloud-auth-token.json");
120
121 return DesktopCloudAuthTokenStore.of({
122 get: Effect.gen(function* () {
123 const document = yield* readDocument(fileSystem, tokenPath);
124 if (Option.isNone(document) || !(yield* safeStorage.isEncryptionAvailable)) {
125 return Option.none<string>();
126 }
127
128 const secretBytes = yield* decodeSecretBytes(document.value.encryptedClientJwt);
129 return Option.some(yield* safeStorage.decryptString(secretBytes));
130 }).pipe(Effect.withSpan("desktop.cloudAuthTokenStore.get")),
131 set: Effect.fn("desktop.cloudAuthTokenStore.set")(function* (token) {
132 if (!(yield* safeStorage.isEncryptionAvailable)) {
133 return false;
134 }
135
136 const encryptedClientJwt = Encoding.encodeBase64(yield* safeStorage.encryptString(token));
137 const suffix = (yield* crypto.randomUUIDv4.pipe(
138 Effect.mapError((cause) => new DesktopCloudAuthTokenStoreWriteError({ cause })),
139 )).replace(/-/g, "");
140 yield* writeDocument({
141 fileSystem,
142 path,
143 tokenPath,
144 document: { version: 1, encryptedClientJwt },
145 suffix,
146 }).pipe(Effect.mapError((cause) => new DesktopCloudAuthTokenStoreWriteError({ cause })));
147 return true;
148 }),
149 clear: fileSystem.remove(tokenPath, { force: true }).pipe(
150 Effect.catch(() => Effect.void),
151 Effect.withSpan("desktop.cloudAuthTokenStore.clear"),
152 ),
153 });
154 }),
155);
apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts created+82
......@@ -0,0 +1,82 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5import * as Ref from "effect/Ref";
6import { HttpClient, HttpClientResponse } from "effect/unstable/http";
7
8import * as DesktopBackendManager from "./DesktopBackendManager.ts";
9import * as DesktopLocalEnvironmentAuth from "./DesktopLocalEnvironmentAuth.ts";
10
11const config: DesktopBackendManager.DesktopBackendStartConfig = {
12 executablePath: "/electron",
13 entryPath: "/server/bin.mjs",
14 cwd: "/server",
15 env: {},
16 bootstrap: {
17 mode: "desktop",
18 noBrowser: true,
19 port: 3773,
20 t3Home: "/tmp/t3",
21 host: "127.0.0.1",
22 desktopBootstrapToken: "desktop-bootstrap-token",
23 tailscaleServeEnabled: false,
24 tailscaleServePort: 443,
25 },
26 httpBaseUrl: new URL("http://127.0.0.1:3773"),
27 captureOutput: true,
28};
29
30describe("DesktopLocalEnvironmentAuth", () => {
31 it.effect("exchanges the desktop bootstrap credential only once", () =>
32 Effect.gen(function* () {
33 const requestCount = yield* Ref.make(0);
34 const httpClientLayer = Layer.succeed(
35 HttpClient.HttpClient,
36 HttpClient.make((request) =>
37 Ref.update(requestCount, (count) => count + 1).pipe(
38 Effect.as(
39 HttpClientResponse.fromWeb(
40 request,
41 new Response(
42 JSON.stringify({
43 access_token: "desktop-bearer-token",
44 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
45 token_type: "Bearer",
46 expires_in: 3600,
47 scope: "orchestration:read",
48 }),
49 { status: 200, headers: { "content-type": "application/json" } },
50 ),
51 ),
52 ),
53 ),
54 ),
55 );
56 const managerLayer = Layer.succeed(DesktopBackendManager.DesktopBackendManager, {
57 start: Effect.void,
58 stop: () => Effect.void,
59 currentConfig: Effect.succeed(Option.some(config)),
60 snapshot: Effect.succeed({
61 desiredRunning: true,
62 ready: true,
63 activePid: Option.none(),
64 restartAttempt: 0,
65 restartScheduled: false,
66 }),
67 });
68 const testLayer = DesktopLocalEnvironmentAuth.layer.pipe(
69 Layer.provide(Layer.mergeAll(managerLayer, httpClientLayer)),
70 );
71
72 const [first, second] = yield* Effect.gen(function* () {
73 const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth;
74 return yield* Effect.all([auth.getBearerToken, auth.getBearerToken]);
75 }).pipe(Effect.provide(testLayer));
76
77 assert.strictEqual(first, "desktop-bearer-token");
78 assert.strictEqual(second, "desktop-bearer-token");
79 assert.strictEqual(yield* Ref.get(requestCount), 1);
80 }),
81 );
82});
apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts created+77
......@@ -0,0 +1,77 @@
1import { bootstrapRemoteBearerSession } from "@t3tools/client-runtime/authorization";
2import * as Context from "effect/Context";
3import * as Data from "effect/Data";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Ref from "effect/Ref";
8import * as Semaphore from "effect/Semaphore";
9import { HttpClient } from "effect/unstable/http";
10
11import * as DesktopBackendManager from "./DesktopBackendManager.ts";
12
13export interface DesktopLocalEnvironmentAuthShape {
14 readonly getBearerToken: Effect.Effect<string, DesktopLocalEnvironmentAuthError>;
15}
16
17export class DesktopLocalEnvironmentAuthError extends Data.TaggedError(
18 "DesktopLocalEnvironmentAuthError",
19)<{
20 readonly message: string;
21 readonly cause?: unknown;
22}> {}
23
24export class DesktopLocalEnvironmentAuth extends Context.Service<
25 DesktopLocalEnvironmentAuth,
26 DesktopLocalEnvironmentAuthShape
27>()("@t3tools/desktop/backend/DesktopLocalEnvironmentAuth") {}
28
29export const layer = Layer.effect(
30 DesktopLocalEnvironmentAuth,
31 Effect.gen(function* () {
32 const backendManager = yield* DesktopBackendManager.DesktopBackendManager;
33 const httpClient = yield* HttpClient.HttpClient;
34 const tokenRef = yield* Ref.make(Option.none<string>());
35 const mutex = yield* Semaphore.make(1);
36
37 const getBearerToken = mutex
38 .withPermits(1)(
39 Effect.gen(function* () {
40 const cached = yield* Ref.get(tokenRef);
41 if (Option.isSome(cached)) {
42 return cached.value;
43 }
44
45 const configOption = yield* backendManager.currentConfig;
46 if (Option.isNone(configOption)) {
47 return yield* new DesktopLocalEnvironmentAuthError({
48 message: "Local backend is not configured.",
49 });
50 }
51 const config = configOption.value;
52 const session = yield* bootstrapRemoteBearerSession({
53 httpBaseUrl: config.httpBaseUrl.href,
54 credential: config.bootstrap.desktopBootstrapToken,
55 clientMetadata: {
56 label: "T3 Code Desktop",
57 deviceType: "desktop",
58 },
59 }).pipe(
60 Effect.provideService(HttpClient.HttpClient, httpClient),
61 Effect.mapError(
62 (cause) =>
63 new DesktopLocalEnvironmentAuthError({
64 message: "Failed to create the local desktop bearer session.",
65 cause,
66 }),
67 ),
68 );
69 yield* Ref.set(tokenRef, Option.some(session.access_token));
70 return session.access_token;
71 }),
72 )
73 .pipe(Effect.withSpan("desktop.localEnvironmentAuth.getBearerToken"));
74
75 return DesktopLocalEnvironmentAuth.of({ getBearerToken });
76 }),
77);
apps/desktop/src/electron/ElectronProtocol.test.ts+103-76
......@@ -1,105 +1,132 @@
11import { assert, describe, it } from "@effect/vitest";
22import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5import type * as Electron from "electron";
63import { beforeEach, vi } from "vite-plus/test";
74
8const { registerFileProtocolMock, registerSchemesAsPrivilegedMock, unregisterProtocolMock } =
9 vi.hoisted(() => ({
10 registerFileProtocolMock: vi.fn(),
11 registerSchemesAsPrivilegedMock: vi.fn(),
12 unregisterProtocolMock: vi.fn(),
13 }));
5const { handleMock, netFetchMock, unhandleMock } = vi.hoisted(() => ({
6 handleMock: vi.fn(),
7 netFetchMock: vi.fn(),
8 unhandleMock: vi.fn(),
9}));
1410
1511vi.mock("electron", () => ({
16 protocol: {
17 registerFileProtocol: registerFileProtocolMock,
18 registerSchemesAsPrivileged: registerSchemesAsPrivilegedMock,
19 unregisterProtocol: unregisterProtocolMock,
20 },
12 net: { fetch: netFetchMock },
13 protocol: { handle: handleMock, unhandle: unhandleMock },
2114}));
2215
2316import * as ElectronProtocol from "./ElectronProtocol.ts";
2417
2518describe("ElectronProtocol", () => {
2619 beforeEach(() => {
27 registerFileProtocolMock.mockReset();
28 registerSchemesAsPrivilegedMock.mockReset();
29 unregisterProtocolMock.mockReset();
20 handleMock.mockReset();
21 netFetchMock.mockReset();
22 unhandleMock.mockReset();
3023 });
3124
32 it("normalizes safe desktop protocol pathnames", () => {
33 assert.equal(
34 Option.getOrNull(ElectronProtocol.normalizeDesktopProtocolPathname("/settings/./general")),
35 "settings/general",
36 );
37 assert.isTrue(Option.isNone(ElectronProtocol.normalizeDesktopProtocolPathname("/../secret")));
38 });
25 it.effect("proxies the stable renderer origin to the current app server", () =>
26 Effect.gen(function* () {
27 let handler: ((request: Request) => Promise<Response>) | undefined;
28 handleMock.mockImplementation((_scheme, nextHandler) => {
29 handler = nextHandler;
30 });
31 netFetchMock.mockResolvedValue(new Response("ok"));
32
33 yield* Effect.scoped(
34 Effect.gen(function* () {
35 const protocol = yield* ElectronProtocol.ElectronProtocol;
36 yield* protocol.registerDesktopProtocol({
37 scheme: "t3code-dev",
38 targetOrigin: new URL("http://127.0.0.1:3773/"),
39 backendOrigin: new URL("http://127.0.0.1:3774/"),
40 clerkFrontendApiHostname: "clerk.t3.codes",
41 });
42 assert.isDefined(handler);
43
44 const response = yield* Effect.promise(() =>
45 handler!(new Request("t3code-dev://app/api/health?verbose=1")),
46 );
47 assert.equal(yield* Effect.promise(() => response.text()), "ok");
48 assert.include(
49 response.headers.get("content-security-policy") ?? "",
50 "script-src 'self' 'unsafe-inline' https://clerk.t3.codes https://challenges.cloudflare.com",
51 );
52 assert.include(
53 response.headers.get("content-security-policy") ?? "",
54 "connect-src 'self' http: https: ws: wss:",
55 );
56 assert.include(
57 response.headers.get("content-security-policy") ?? "",
58 "img-src 'self' t3code-dev: blob: data: http: https:",
59 );
60 assert.include(
61 response.headers.get("content-security-policy") ?? "",
62 "font-src 'self' t3code-dev: data:",
63 );
64 }),
65 );
3966
40 it.effect("registers desktop scheme privileges through a layer", () =>
41 Effect.scoped(
42 Layer.build(ElectronProtocol.layerSchemePrivileges).pipe(
43 Effect.andThen(
44 Effect.sync(() => {
45 assert.deepEqual(registerSchemesAsPrivilegedMock.mock.calls, [
46 [
47 [
48 {
49 scheme: "t3",
50 privileges: {
51 standard: true,
52 secure: true,
53 supportFetchAPI: true,
54 corsEnabled: true,
55 },
56 },
57 ],
58 ],
59 ]);
60 }),
61 ),
62 ),
63 ),
67 assert.deepEqual(
68 handleMock.mock.calls.map((call) => call[0]),
69 ["t3code-dev"],
70 );
71 assert.equal(netFetchMock.mock.calls[0]?.[0], "http://127.0.0.1:3773/api/health?verbose=1");
72 assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]);
73 }).pipe(Effect.provide(ElectronProtocol.layer)),
6474 );
6575
66 it.effect("scopes registered file protocols", () =>
76 it.effect("rejects custom protocol requests for another host", () =>
6777 Effect.gen(function* () {
68 let capturedHandler:
69 | ((
70 request: Electron.ProtocolRequest,
71 callback: (response: Electron.ProtocolResponse) => void,
72 ) => void)
73 | undefined;
74
75 registerFileProtocolMock.mockImplementation((_scheme, handler) => {
76 capturedHandler = handler;
77 return true;
78 let handler: ((request: Request) => Promise<Response>) | undefined;
79 handleMock.mockImplementation((_scheme, nextHandler) => {
80 handler = nextHandler;
7881 });
7982
8083 const response = yield* Effect.scoped(
8184 Effect.gen(function* () {
82 const electronProtocol = yield* ElectronProtocol.ElectronProtocol;
83 yield* electronProtocol.registerFileProtocol({
84 scheme: "t3",
85 handler: () => Effect.succeed({ path: "/app/index.html" }),
86 });
87
88 assert.isDefined(capturedHandler);
89 return yield* Effect.callback<Electron.ProtocolResponse>((resume) => {
90 capturedHandler?.({ url: "t3://app/" } as Electron.ProtocolRequest, (response) =>
91 resume(Effect.succeed(response)),
92 );
85 const protocol = yield* ElectronProtocol.ElectronProtocol;
86 yield* protocol.registerDesktopProtocol({
87 scheme: "t3code",
88 targetOrigin: new URL("http://127.0.0.1:3773/"),
89 backendOrigin: new URL("http://127.0.0.1:3773/"),
90 clerkFrontendApiHostname: undefined,
9391 });
92 return yield* Effect.promise(() => handler!(new Request("t3code://other/")));
9493 }),
9594 );
9695
97 assert.deepEqual(response, { path: "/app/index.html" });
98 assert.deepEqual(
99 registerFileProtocolMock.mock.calls.map((call) => call[0]),
100 ["t3"],
101 );
102 assert.deepEqual(unregisterProtocolMock.mock.calls, [["t3"]]);
96 assert.equal(response.status, 404);
97 assert.equal(netFetchMock.mock.calls.length, 0);
10398 }).pipe(Effect.provide(ElectronProtocol.layer)),
10499 );
100
101 it("keeps executable sources host-restricted while allowing runtime network resources", () => {
102 const policy = ElectronProtocol.makeDesktopContentSecurityPolicy({
103 scheme: "t3code",
104 targetOrigin: new URL("http://127.0.0.1:3773/"),
105 backendOrigin: new URL("http://127.0.0.1:3773/"),
106 clerkFrontendApiHostname: "clerk.t3.codes",
107 });
108 const directives = Object.fromEntries(
109 policy.split("; ").map((directive) => {
110 const [name, ...sources] = directive.split(" ");
111 return [name, sources];
112 }),
113 );
114
115 assert.deepEqual(directives["script-src"], [
116 "'self'",
117 "'unsafe-inline'",
118 "https://clerk.t3.codes",
119 "https://challenges.cloudflare.com",
120 ]);
121 assert.deepEqual(directives["connect-src"], ["'self'", "http:", "https:", "ws:", "wss:"]);
122 assert.deepEqual(directives["img-src"], [
123 "'self'",
124 "t3code:",
125 "blob:",
126 "data:",
127 "http:",
128 "https:",
129 ]);
130 assert.deepEqual(directives["font-src"], ["'self'", "t3code:", "data:"]);
131 });
105132});
apps/desktop/src/electron/ElectronProtocol.ts+90-216
......@@ -1,18 +1,27 @@
1import * as Cause from "effect/Cause";
21import * as Context from "effect/Context";
32import * as Data from "effect/Data";
43import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
64import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
85import * as Ref from "effect/Ref";
96import * as Scope from "effect/Scope";
107
118import * as Electron from "electron";
129
13import { DesktopEnvironment, type DesktopEnvironmentShape } from "../app/DesktopEnvironment.ts";
10export const DESKTOP_HOST = "app";
11export const DESKTOP_PRODUCTION_SCHEME = "t3code";
12export const DESKTOP_DEVELOPMENT_SCHEME = "t3code-dev";
1413
15export const DESKTOP_SCHEME = "t3";
14export function getDesktopScheme(isDevelopment: boolean): string {
15 return isDevelopment ? DESKTOP_DEVELOPMENT_SCHEME : DESKTOP_PRODUCTION_SCHEME;
16}
17
18export function getDesktopOrigin(isDevelopment: boolean): string {
19 return `${getDesktopScheme(isDevelopment)}://${DESKTOP_HOST}`;
20}
21
22export function getDesktopUrl(isDevelopment: boolean): string {
23 return `${getDesktopOrigin(isDevelopment)}/`;
24}
1625
1726export class ElectronProtocolRegistrationError extends Data.TaggedError(
1827 "ElectronProtocolRegistrationError",
......@@ -21,252 +30,117 @@ export class ElectronProtocolRegistrationError extends Data.TaggedError(
2130 readonly cause: unknown;
2231}> {
2332 override get message() {
24 return `Failed to register ${this.scheme}: file protocol.`;
33 return `Failed to register ${this.scheme}: protocol.`;
2534 }
2635}
2736
28export class ElectronProtocolStaticBundleMissingError extends Data.TaggedError(
29 "ElectronProtocolStaticBundleMissingError",
30)<{}> {
31 override get message() {
32 return "Desktop static bundle missing. Build apps/server (with bundled client) first.";
33 }
37export interface DesktopProtocolRegistrationInput {
38 readonly scheme: string;
39 readonly targetOrigin: URL;
40 readonly backendOrigin: URL;
41 readonly clerkFrontendApiHostname: string | undefined;
3442}
3543
3644export interface ElectronProtocolShape {
37 readonly registerFileProtocol: <E, R>(input: {
38 readonly scheme: string;
39 readonly handler: (
40 request: Electron.ProtocolRequest,
41 ) => Effect.Effect<Electron.ProtocolResponse, E, R>;
42 readonly onFailure?: (
43 request: Electron.ProtocolRequest,
44 cause: Cause.Cause<E>,
45 ) => Electron.ProtocolResponse;
46 }) => Effect.Effect<void, ElectronProtocolRegistrationError, R | Scope.Scope>;
47 readonly registerDesktopFileProtocol: Effect.Effect<
48 void,
49 ElectronProtocolRegistrationError | ElectronProtocolStaticBundleMissingError,
50 FileSystem.FileSystem | DesktopEnvironment | Scope.Scope
51 >;
45 readonly registerDesktopProtocol: (
46 input: DesktopProtocolRegistrationInput,
47 ) => Effect.Effect<void, ElectronProtocolRegistrationError, Scope.Scope>;
5248}
5349
5450export class ElectronProtocol extends Context.Service<ElectronProtocol, ElectronProtocolShape>()(
5551 "@t3tools/desktop/electron/ElectronProtocol",
5652) {}
5753
58export function normalizeDesktopProtocolPathname(rawPath: string): Option.Option<string> {
59 const segments: string[] = [];
60 for (const segment of rawPath.split("/")) {
61 if (segment.length === 0 || segment === ".") {
62 continue;
63 }
64 if (segment === "..") {
65 return Option.none();
66 }
67 segments.push(segment);
68 }
69 return Option.some(segments.join("/"));
70}
71
72const registerDesktopSchemePrivileges = Effect.sync(() => {
73 Electron.protocol.registerSchemesAsPrivileged([
74 {
75 scheme: DESKTOP_SCHEME,
76 privileges: {
77 standard: true,
78 secure: true,
79 supportFetchAPI: true,
80 corsEnabled: true,
81 },
82 },
83 ]);
84}).pipe(Effect.withSpan("desktop.electron.protocol.registerSchemePrivileges"));
85
86export const layerSchemePrivileges = Layer.effectDiscard(registerDesktopSchemePrivileges);
87
88const resolveDesktopStaticDir: Effect.Effect<
89 Option.Option<string>,
90 never,
91 FileSystem.FileSystem | DesktopEnvironment
92> = Effect.gen(function* () {
93 const fileSystem = yield* FileSystem.FileSystem;
94 const environment = yield* DesktopEnvironment;
95 const candidates = [
96 environment.path.join(environment.appRoot, "apps/server/dist/client"),
97 environment.path.join(environment.appRoot, "apps/web/dist"),
54export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrationInput): string {
55 const clerkOrigin = input.clerkFrontendApiHostname
56 ? `https://${input.clerkFrontendApiHostname}`
57 : undefined;
58 const scriptSources = [
59 "'self'",
60 "'unsafe-inline'",
61 ...(clerkOrigin ? [clerkOrigin] : []),
62 "https://challenges.cloudflare.com",
9863 ];
99 for (const candidate of candidates) {
100 const hasIndex = yield* fileSystem
101 .exists(environment.path.join(candidate, "index.html"))
102 .pipe(Effect.orElseSucceed(() => false));
103 if (hasIndex) {
104 return Option.some(candidate);
105 }
106 }
107 return Option.none<string>();
108});
10964
110const resolveDesktopStaticPath = Effect.fn("desktop.electron.protocol.resolveDesktopStaticPath")(
111 function* (
112 staticRoot: string,
113 requestUrl: string,
114 ): Effect.fn.Return<string, never, FileSystem.FileSystem | DesktopEnvironment> {
115 const fileSystem = yield* FileSystem.FileSystem;
116 const environment = yield* DesktopEnvironment;
117 const url = new URL(requestUrl);
118 const rawPath = decodeURIComponent(url.pathname);
119 const normalizedPath = normalizeDesktopProtocolPathname(rawPath);
120 if (Option.isNone(normalizedPath)) {
121 return environment.path.join(staticRoot, "index.html");
122 }
123
124 const requestedPath = normalizedPath.value.length > 0 ? normalizedPath.value : "index.html";
125 const resolvedPath = environment.path.join(staticRoot, requestedPath);
126
127 if (environment.path.extname(resolvedPath)) {
128 return resolvedPath;
129 }
65 // The renderer connects directly to user-configured environments in addition to
66 // the build-configured Clerk, relay, and OTLP endpoints. Those environment
67 // origins are not known when this response policy is created, so restrict
68 // connections by the network schemes the client supports instead of by host.
69 const connectSources = ["'self'", "http:", "https:", "ws:", "wss:"];
70
71 return [
72 "default-src 'self'",
73 `script-src ${scriptSources.join(" ")}`,
74 `connect-src ${connectSources.join(" ")}`,
75 `img-src 'self' ${input.scheme}: blob: data: http: https:`,
76 "style-src 'self' 'unsafe-inline'",
77 `font-src 'self' ${input.scheme}: data:`,
78 "worker-src 'self' blob:",
79 "frame-src 'self' https://challenges.cloudflare.com",
80 "form-action 'self'",
81 ].join("; ");
82}
13083
131 const nestedIndex = environment.path.join(resolvedPath, "index.html");
132 const nestedIndexExists = yield* fileSystem
133 .exists(nestedIndex)
134 .pipe(Effect.orElseSucceed(() => false));
135 if (nestedIndexExists) {
136 return nestedIndex;
137 }
84function withContentSecurityPolicy(response: Response, policy: string): Response {
85 const headers = new Headers(response.headers);
86 headers.set("Content-Security-Policy", policy);
87 return new Response(response.body, {
88 status: response.status,
89 statusText: response.statusText,
90 headers,
91 });
92}
13893
139 return environment.path.join(staticRoot, "index.html");
140 },
141);
94async function proxyRequest(
95 request: Request,
96 targetOrigin: URL,
97 contentSecurityPolicy: string,
98): Promise<Response> {
99 const requestUrl = new URL(request.url);
100 if (requestUrl.host !== DESKTOP_HOST) {
101 return new Response(null, { status: 404 });
102 }
142103
143function isStaticAssetRequest(requestUrl: string, environment: DesktopEnvironmentShape): boolean {
144 try {
145 const url = new URL(requestUrl);
146 return environment.path.extname(url.pathname).length > 0;
147 } catch {
148 return false;
104 const targetUrl = new URL(`${requestUrl.pathname}${requestUrl.search}`, targetOrigin);
105 const init: RequestInit = {
106 method: request.method,
107 headers: request.headers,
108 };
109 if (request.method !== "GET" && request.method !== "HEAD") {
110 init.body = request.body;
111 (init as RequestInit & { duplex: "half" }).duplex = "half";
149112 }
113 const response = await Electron.net.fetch(targetUrl.toString(), init);
114 return withContentSecurityPolicy(response, contentSecurityPolicy);
150115}
151116
152117const make = Effect.gen(function* () {
153 const registeredProtocols = yield* Ref.make<ReadonlySet<string>>(new Set());
118 const registered = yield* Ref.make(false);
154119
155 const registerFileProtocol = Effect.fn("desktop.electron.protocol.registerFileProtocol")(
156 function* <E, R>({
157 scheme,
158 handler,
159 onFailure,
160 }: {
161 readonly scheme: string;
162 readonly handler: (
163 request: Electron.ProtocolRequest,
164 ) => Effect.Effect<Electron.ProtocolResponse, E, R>;
165 readonly onFailure?: (
166 request: Electron.ProtocolRequest,
167 cause: Cause.Cause<E>,
168 ) => Electron.ProtocolResponse;
169 }): Effect.fn.Return<void, ElectronProtocolRegistrationError, R | Scope.Scope> {
170 yield* Effect.annotateCurrentSpan({ scheme });
171 const alreadyRegistered = yield* Ref.get(registeredProtocols).pipe(
172 Effect.map((protocols) => protocols.has(scheme)),
173 );
174 if (alreadyRegistered) {
175 return;
176 }
120 const registerDesktopProtocol = Effect.fn("desktop.electron.protocol.registerDesktopProtocol")(
121 function* (input: DesktopProtocolRegistrationInput) {
122 if (yield* Ref.get(registered)) return;
177123
178 const context = yield* Effect.context<R>();
179 const runPromise = Effect.runPromiseWith(context);
124 const contentSecurityPolicy = makeDesktopContentSecurityPolicy(input);
180125
181126 yield* Effect.acquireRelease(
182127 Effect.try({
183128 try: () => {
184 const registered = Electron.protocol.registerFileProtocol(
185 scheme,
186 (request, callback) => {
187 const response = handler(request).pipe(
188 Effect.withSpan("desktop.electron.protocol.handleFileRequest"),
189 Effect.catchCause((cause) =>
190 Effect.succeed(onFailure?.(request, cause) ?? ({ error: -2 } as const)),
191 ),
192 );
193
194 void runPromise(response).then(callback, () => callback({ error: -2 }));
195 },
129 Electron.protocol.handle(input.scheme, (request) =>
130 proxyRequest(request, input.targetOrigin, contentSecurityPolicy),
196131 );
197 if (!registered) {
198 throw new ElectronProtocolRegistrationError({
199 scheme,
200 cause: "registerFileProtocol returned false",
201 });
202 }
203132 },
204 catch: (cause) =>
205 cause instanceof ElectronProtocolRegistrationError
206 ? cause
207 : new ElectronProtocolRegistrationError({ scheme, cause }),
208 }).pipe(
209 Effect.andThen(
210 Ref.update(registeredProtocols, (protocols) => new Set(protocols).add(scheme)),
211 ),
212 ),
133 catch: (cause) => new ElectronProtocolRegistrationError({ scheme: input.scheme, cause }),
134 }).pipe(Effect.andThen(Ref.set(registered, true))),
213135 () =>
214136 Effect.sync(() => {
215 Electron.protocol.unregisterProtocol(scheme);
216 }).pipe(
217 Effect.andThen(
218 Ref.update(registeredProtocols, (protocols) => {
219 const next = new Set(protocols);
220 next.delete(scheme);
221 return next;
222 }),
223 ),
224 ),
137 Electron.protocol.unhandle(input.scheme);
138 }).pipe(Effect.andThen(Ref.set(registered, false))),
225139 );
226140 },
227141 );
228142
229 const registerDesktopFileProtocol = Effect.gen(function* () {
230 const environment = yield* DesktopEnvironment;
231 if (environment.isDevelopment) return;
232
233 const staticRoot = yield* resolveDesktopStaticDir;
234 if (Option.isNone(staticRoot)) {
235 return yield* new ElectronProtocolStaticBundleMissingError();
236 }
237
238 const staticRootResolved = environment.path.resolve(staticRoot.value);
239 const staticRootPrefix = `${staticRootResolved}${environment.path.sep}`;
240 const fallbackIndex = environment.path.join(staticRootResolved, "index.html");
241
242 yield* registerFileProtocol({
243 scheme: DESKTOP_SCHEME,
244 handler: Effect.fn("desktop.electron.protocol.handleDesktopFileRequest")(function* (request) {
245 const fileSystem = yield* FileSystem.FileSystem;
246 const environment = yield* DesktopEnvironment;
247 const candidate = yield* resolveDesktopStaticPath(staticRootResolved, request.url);
248 const resolvedCandidate = environment.path.resolve(candidate);
249 const isInRoot =
250 resolvedCandidate === fallbackIndex || resolvedCandidate.startsWith(staticRootPrefix);
251 const isAssetRequest = isStaticAssetRequest(request.url, environment);
252 const exists = yield* fileSystem
253 .exists(resolvedCandidate)
254 .pipe(Effect.orElseSucceed(() => false));
255
256 if (!isInRoot || !exists) {
257 return isAssetRequest ? ({ error: -6 } as const) : ({ path: fallbackIndex } as const);
258 }
259
260 return { path: resolvedCandidate } as const;
261 }),
262 onFailure: () => ({ path: fallbackIndex }),
263 });
264 }).pipe(Effect.withSpan("desktop.electron.protocol.registerDesktopFileProtocol"));
265
266 return ElectronProtocol.of({
267 registerFileProtocol,
268 registerDesktopFileProtocol,
269 });
143 return ElectronProtocol.of({ registerDesktopProtocol });
270144});
271145
272146export const layer = Layer.effect(ElectronProtocol, make);
apps/desktop/src/ipc/DesktopIpcHandlers.ts+2-12
......@@ -1,13 +1,6 @@
11import * as Effect from "effect/Effect";
22
33import * as DesktopIpc from "./DesktopIpc.ts";
4import {
5 clearCloudAuthToken,
6 createCloudAuthRequest,
7 fetchCloudAuth,
8 getCloudAuthToken,
9 setCloudAuthToken,
10} from "./methods/cloudAuth.ts";
114import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts";
125import {
136 clearConnectionCatalog,
......@@ -40,6 +33,7 @@ import {
4033import {
4134 confirm,
4235 getAppBranding,
36 getLocalEnvironmentBearerToken,
4337 getLocalEnvironmentBootstrap,
4438 openExternal,
4539 pickFolder,
......@@ -54,6 +48,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
5448
5549 yield* ipc.handleSync(getAppBranding);
5650 yield* ipc.handleSync(getLocalEnvironmentBootstrap);
51 yield* ipc.handle(getLocalEnvironmentBearerToken);
5752
5853 yield* ipc.handle(getClientSettings);
5954 yield* ipc.handle(setClientSettings);
......@@ -80,11 +75,6 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
8075 yield* ipc.handle(setTheme);
8176 yield* ipc.handle(showContextMenu);
8277 yield* ipc.handle(openExternal);
83 yield* ipc.handle(createCloudAuthRequest);
84 yield* ipc.handle(getCloudAuthToken);
85 yield* ipc.handle(setCloudAuthToken);
86 yield* ipc.handle(clearCloudAuthToken);
87 yield* ipc.handle(fetchCloudAuth);
8878 yield* ipc.handle(getUpdateState);
8979 yield* ipc.handle(setUpdateChannel);
9080 yield* ipc.handle(downloadUpdate);
apps/desktop/src/ipc/channels.ts+2-6
......@@ -3,12 +3,6 @@ export const CONFIRM_CHANNEL = "desktop:confirm";
33export const SET_THEME_CHANNEL = "desktop:set-theme";
44export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
55export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
6export const CREATE_CLOUD_AUTH_REQUEST_CHANNEL = "desktop:create-cloud-auth-request";
7export const GET_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:get-cloud-auth-token";
8export const SET_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:set-cloud-auth-token";
9export const CLEAR_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:clear-cloud-auth-token";
10export const FETCH_CLOUD_AUTH_CHANNEL = "desktop:fetch-cloud-auth";
11export const CLOUD_AUTH_CALLBACK_CHANNEL = "desktop:cloud-auth-callback";
126export const MENU_ACTION_CHANNEL = "desktop:menu-action";
137export const UPDATE_STATE_CHANNEL = "desktop:update-state";
148export const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
......@@ -18,6 +12,8 @@ export const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
1812export const UPDATE_CHECK_CHANNEL = "desktop:update-check";
1913export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";
2014export const GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL = "desktop:get-local-environment-bootstrap";
15export const GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL =
16 "desktop:get-local-environment-bearer-token";
2117export const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";
2218export const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings";
2319export const GET_CONNECTION_CATALOG_CHANNEL = "desktop:get-connection-catalog";
apps/desktop/src/ipc/methods/cloudAuth.test.ts deleted-97
......@@ -1,97 +0,0 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import { afterEach } from "vite-plus/test";
4
5import { fetchCloudAuth, validateClerkFrontendApiUrl } from "./cloudAuth.ts";
6
7const originalClerkPublishableKey = process.env.T3CODE_CLERK_PUBLISHABLE_KEY;
8const originalFetch = globalThis.fetch;
9
10const clerkPublishableKey = (hostname: string): string =>
11 `pk_test_${Buffer.from(`${hostname}$`).toString("base64")}`;
12
13type FetchCall = readonly [input: RequestInfo | URL, init: RequestInit];
14
15const recordedFetch = (...responses: ReadonlyArray<Response>) => {
16 const calls: Array<FetchCall> = [];
17 let responseIndex = 0;
18 const fetchFn = ((input, init) => {
19 calls.push([input, init ?? {}]);
20 const response = responses[responseIndex++];
21 if (!response) {
22 return Promise.reject(new Error("Unexpected fetch call"));
23 }
24 return Promise.resolve(response);
25 }) satisfies typeof fetch;
26
27 return { fetchFn, calls };
28};
29
30describe("Desktop cloud auth IPC", () => {
31 afterEach(() => {
32 globalThis.fetch = originalFetch;
33 if (originalClerkPublishableKey === undefined) {
34 delete process.env.T3CODE_CLERK_PUBLISHABLE_KEY;
35 } else {
36 process.env.T3CODE_CLERK_PUBLISHABLE_KEY = originalClerkPublishableKey;
37 }
38 });
39
40 it.effect("preserves Clerk's URL-encoded OAuth form content type", () => {
41 const body = "strategy=oauth_google&redirect_url=t3code%3A%2F%2Fauth%2Fcallback";
42 const fetch = recordedFetch(Response.json({ response: { object: "sign_in_attempt" } }));
43 globalThis.fetch = fetch.fetchFn;
44
45 return Effect.gen(function* () {
46 yield* fetchCloudAuth.handler({
47 url: "https://example.clerk.accounts.dev/v1/client/sign_ins",
48 method: "POST",
49 headers: {
50 "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
51 "x-mobile": "1",
52 },
53 body,
54 });
55
56 const forwardedRequest = fetch.calls[0];
57 assert(forwardedRequest !== undefined);
58 const [url, init] = forwardedRequest;
59 assert.equal(String(url), "https://example.clerk.accounts.dev/v1/client/sign_ins");
60 assert.equal(init.method, "POST");
61 assert.equal(
62 new Headers(init.headers).get("content-type"),
63 "application/x-www-form-urlencoded;charset=UTF-8",
64 );
65 assert.equal(new TextDecoder().decode(init.body as Uint8Array), body);
66 });
67 });
68
69 it.effect(
70 "allows the custom Clerk Frontend API host encoded by the configured publishable key",
71 () => {
72 process.env.T3CODE_CLERK_PUBLISHABLE_KEY = clerkPublishableKey("clerk.t3.codes");
73 const fetch = recordedFetch(Response.json({ response: { object: "client" } }));
74 globalThis.fetch = fetch.fetchFn;
75
76 return Effect.gen(function* () {
77 yield* fetchCloudAuth.handler({
78 url: "https://clerk.t3.codes/v1/client",
79 method: "GET",
80 headers: {},
81 });
82
83 const forwardedRequest = fetch.calls[0];
84 assert(forwardedRequest !== undefined);
85 assert.equal(String(forwardedRequest[0]), "https://clerk.t3.codes/v1/client");
86 });
87 },
88 );
89
90 it("rejects arbitrary HTTPS hosts that are not configured Clerk Frontend API hosts", () => {
91 process.env.T3CODE_CLERK_PUBLISHABLE_KEY = clerkPublishableKey("clerk.t3.codes");
92 assert.throws(
93 () => validateClerkFrontendApiUrl("https://attacker.example/v1/client"),
94 /restricted to Clerk Frontend API HTTPS hosts/u,
95 );
96 });
97});
apps/desktop/src/ipc/methods/cloudAuth.ts deleted-177
......@@ -1,177 +0,0 @@
1import {
2 DesktopCloudAuthFetchInputSchema,
3 DesktopCloudAuthFetchResultSchema,
4} from "@t3tools/contracts";
5import {
6 clerkFrontendApiHostnameFromPublishableKey,
7 isAllowedClerkFrontendApiHostname,
8} from "@t3tools/shared/relayAuth";
9import * as Data from "effect/Data";
10import * as Effect from "effect/Effect";
11import { identity } from "effect/Function";
12import * as Layer from "effect/Layer";
13import * as Option from "effect/Option";
14import * as Schema from "effect/Schema";
15import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
16
17import * as DesktopCloudAuth from "../../app/DesktopCloudAuth.ts";
18import * as DesktopCloudAuthTokenStore from "../../app/DesktopCloudAuthTokenStore.ts";
19import * as IpcChannels from "../channels.ts";
20import { makeIpcMethod } from "../DesktopIpc.ts";
21
22declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;
23
24export class DesktopCloudAuthFetchError extends Data.TaggedError("DesktopCloudAuthFetchError")<{
25 readonly reason: string;
26 readonly cause?: unknown;
27}> {
28 override get message() {
29 return this.reason;
30 }
31}
32
33function configuredClerkFrontendApiHostname(): string | null {
34 const publishableKey =
35 process.env.T3CODE_CLERK_PUBLISHABLE_KEY?.trim() ||
36 (typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined"
37 ? ""
38 : __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__.trim());
39 if (!publishableKey) return null;
40
41 return clerkFrontendApiHostnameFromPublishableKey(publishableKey);
42}
43
44const allowedClerkFrontendApiHosts = (hostname: string): boolean =>
45 isAllowedClerkFrontendApiHostname(hostname, configuredClerkFrontendApiHostname());
46
47export function validateClerkFrontendApiUrl(rawUrl: string): URL {
48 const url = new URL(rawUrl);
49 if (url.protocol !== "https:" || !allowedClerkFrontendApiHosts(url.hostname)) {
50 throw new DesktopCloudAuthFetchError({
51 reason: "Desktop cloud auth fetch is restricted to Clerk Frontend API HTTPS hosts.",
52 });
53 }
54 return url;
55}
56
57function executeCloudAuthFetch(url: URL, input: typeof DesktopCloudAuthFetchInputSchema.Type) {
58 return Effect.gen(function* () {
59 const method = (input.method ?? "GET") as "GET" | "POST";
60 const headers = new Headers(input.headers);
61 const response = yield* HttpClientRequest.make(method)(url).pipe(
62 HttpClientRequest.setHeaders(Object.fromEntries(headers.entries())),
63 input.body === undefined
64 ? identity
65 : HttpClientRequest.bodyText(input.body, headers.get("content-type") ?? undefined),
66 HttpClient.execute,
67 Effect.mapError(
68 (cause) =>
69 new DesktopCloudAuthFetchError({
70 reason: "Desktop cloud auth fetch failed to execute.",
71 cause,
72 }),
73 ),
74 );
75
76 const body = yield* response.text.pipe(
77 Effect.mapError(
78 (cause) =>
79 new DesktopCloudAuthFetchError({
80 reason: "Desktop cloud auth fetch response could not be read.",
81 cause,
82 }),
83 ),
84 );
85
86 return {
87 ok: response.status >= 200 && response.status < 300,
88 status: response.status,
89 statusText: "",
90 headers: response.headers,
91 body,
92 };
93 });
94}
95
96const electronNetFetchLayer = Layer.unwrap(
97 Effect.gen(function* () {
98 const electronFetch = yield* Effect.promise(async () => {
99 const electron = (await import("electron")) as {
100 readonly net?: { readonly fetch?: typeof globalThis.fetch };
101 };
102 return typeof electron.net?.fetch === "function"
103 ? electron.net.fetch.bind(electron.net)
104 : null;
105 }).pipe(Effect.catchCause(() => Effect.succeed(null)));
106
107 if (!electronFetch) {
108 yield* Effect.logWarning(
109 "electron.net.fetch is not available, falling back to global fetch. This may cause unexpected errors.",
110 );
111 }
112
113 return FetchHttpClient.layer.pipe(
114 Layer.provide(Layer.succeed(FetchHttpClient.Fetch, electronFetch ?? globalThis.fetch)),
115 );
116 }),
117);
118
119export const createCloudAuthRequest = makeIpcMethod({
120 channel: IpcChannels.CREATE_CLOUD_AUTH_REQUEST_CHANNEL,
121 payload: Schema.Void,
122 result: Schema.String,
123 handler: Effect.fn("desktop.ipc.cloudAuth.createRequest")(function* () {
124 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
125 return yield* cloudAuth.createRequest;
126 }),
127});
128
129export const getCloudAuthToken = makeIpcMethod({
130 channel: IpcChannels.GET_CLOUD_AUTH_TOKEN_CHANNEL,
131 payload: Schema.Void,
132 result: Schema.NullOr(Schema.String),
133 handler: Effect.fn("desktop.ipc.cloudAuth.getToken")(function* () {
134 const tokenStore = yield* DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore;
135 return Option.getOrNull(yield* tokenStore.get);
136 }),
137});
138
139export const setCloudAuthToken = makeIpcMethod({
140 channel: IpcChannels.SET_CLOUD_AUTH_TOKEN_CHANNEL,
141 payload: Schema.String,
142 result: Schema.Boolean,
143 handler: Effect.fn("desktop.ipc.cloudAuth.setToken")(function* (token) {
144 const tokenStore = yield* DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore;
145 return yield* tokenStore.set(token);
146 }),
147});
148
149export const clearCloudAuthToken = makeIpcMethod({
150 channel: IpcChannels.CLEAR_CLOUD_AUTH_TOKEN_CHANNEL,
151 payload: Schema.Void,
152 result: Schema.Void,
153 handler: Effect.fn("desktop.ipc.cloudAuth.clearToken")(function* () {
154 const tokenStore = yield* DesktopCloudAuthTokenStore.DesktopCloudAuthTokenStore;
155 yield* tokenStore.clear;
156 }),
157});
158
159export const fetchCloudAuth = makeIpcMethod({
160 channel: IpcChannels.FETCH_CLOUD_AUTH_CHANNEL,
161 payload: DesktopCloudAuthFetchInputSchema,
162 result: DesktopCloudAuthFetchResultSchema,
163 handler: Effect.fn("desktop.ipc.cloudAuth.fetch")(function* (input) {
164 const url = yield* Effect.try({
165 try: () => validateClerkFrontendApiUrl(input.url),
166 catch: (cause) =>
167 cause instanceof DesktopCloudAuthFetchError
168 ? cause
169 : new DesktopCloudAuthFetchError({
170 reason: "Desktop cloud auth fetch received an invalid URL.",
171 cause,
172 }),
173 });
174
175 return yield* executeCloudAuthFetch(url, input).pipe(Effect.provide(electronNetFetchLayer));
176 }),
177});
apps/desktop/src/ipc/methods/window.ts+11
......@@ -10,6 +10,7 @@ import * as Option from "effect/Option";
1010import * as Schema from "effect/Schema";
1111
1212import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts";
13import * as DesktopLocalEnvironmentAuth from "../../backend/DesktopLocalEnvironmentAuth.ts";
1314import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts";
1415import * as ElectronDialog from "../../electron/ElectronDialog.ts";
1516import * as ElectronMenu from "../../electron/ElectronMenu.ts";
......@@ -64,6 +65,16 @@ export const getLocalEnvironmentBootstrap = makeSyncIpcMethod({
6465 }),
6566});
6667
68export const getLocalEnvironmentBearerToken = makeIpcMethod({
69 channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL,
70 payload: Schema.Void,
71 result: Schema.String,
72 handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBearerToken")(function* () {
73 const localAuth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth;
74 return yield* localAuth.getBearerToken;
75 }),
76});
77
6778export const pickFolder = makeIpcMethod({
6879 channel: IpcChannels.PICK_FOLDER_CHANNEL,
6980 payload: Schema.UndefinedOr(PickFolderOptionsSchema),
apps/desktop/src/main.ts+19-7
......@@ -27,13 +27,13 @@ import * as ElectronUpdater from "./electron/ElectronUpdater.ts";
2727import * as ElectronWindow from "./electron/ElectronWindow.ts";
2828import * as DesktopApp from "./app/DesktopApp.ts";
2929import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts";
30import * as DesktopCloudAuth from "./app/DesktopCloudAuth.ts";
31import * as DesktopCloudAuthTokenStore from "./app/DesktopCloudAuthTokenStore.ts";
3230import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts";
31import * as DesktopClerk from "./app/DesktopClerk.ts";
3332import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts";
3433import * as DesktopAssets from "./app/DesktopAssets.ts";
3534import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts";
3635import * as DesktopBackendManager from "./backend/DesktopBackendManager.ts";
36import * as DesktopLocalEnvironmentAuth from "./backend/DesktopLocalEnvironmentAuth.ts";
3737import * as DesktopEnvironment from "./app/DesktopEnvironment.ts";
3838import * as DesktopLifecycle from "./app/DesktopLifecycle.ts";
3939import * as DesktopObservability from "./app/DesktopObservability.ts";
......@@ -119,7 +119,6 @@ const desktopFoundationLayer = Layer.mergeAll(
119119 DesktopAppSettings.layer,
120120 DesktopClientSettings.layer,
121121 DesktopConnectionCatalogStore.layer.pipe(Layer.provideMerge(DesktopSavedEnvironments.layer)),
122 DesktopCloudAuthTokenStore.layer,
123122 DesktopAssets.layer,
124123 DesktopObservability.layer,
125124).pipe(Layer.provideMerge(desktopEnvironmentLayer));
......@@ -149,17 +148,30 @@ const desktopBackendLayer = DesktopBackendManager.layer.pipe(
149148 Layer.provideMerge(desktopWindowLayer),
150149);
151150
151const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe(
152 Layer.provideMerge(desktopBackendLayer),
153);
154
152155const desktopApplicationLayer = Layer.mergeAll(
153156 DesktopLifecycle.layer,
154157 DesktopApplicationMenu.layer,
155 DesktopCloudAuth.layer,
156158 DesktopShellEnvironment.layer,
157159 desktopSshLayer,
158).pipe(Layer.provideMerge(DesktopUpdates.layer), Layer.provideMerge(desktopBackendLayer));
160).pipe(
161 Layer.provideMerge(DesktopUpdates.layer),
162 Layer.provideMerge(desktopLocalEnvironmentAuthLayer),
163);
164
165const desktopClerkLayer = DesktopClerk.layer.pipe(
166 Layer.provideMerge(desktopEnvironmentLayer),
167 Layer.provideMerge(NodeServices.layer),
168 Layer.provideMerge(ElectronApp.layer),
169);
159170
160const desktopRuntimeLayer = ElectronProtocol.layerSchemePrivileges.pipe(
161 Layer.flatMap(() =>
171const desktopRuntimeLayer = desktopClerkLayer.pipe(
172 Layer.flatMap((clerkContext) =>
162173 desktopApplicationLayer.pipe(
174 Layer.provideMerge(Layer.succeedContext(clerkContext)),
163175 Layer.provideMerge(NodeServices.layer),
164176 Layer.provideMerge(NodeHttpClient.layerUndici),
165177 Layer.provideMerge(NetService.layer),
apps/desktop/src/preload.ts+5-17
......@@ -4,10 +4,13 @@ import type {
44 DesktopPreviewRecordingFrame,
55 DesktopPreviewTabState,
66} from "@t3tools/contracts";
7import { exposeClerkBridge } from "@clerk/electron/preload";
78import { contextBridge, ipcRenderer } from "electron";
89
910import * as IpcChannels from "./ipc/channels.ts";
1011
12exposeClerkBridge({ passkeys: true });
13
1114function unwrapEnsureSshEnvironmentResult(result: unknown) {
1215 if (
1316 typeof result === "object" &&
......@@ -39,6 +42,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {
3942 }
4043 return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>;
4144 },
45 getLocalEnvironmentBearerToken: () =>
46 ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL),
4247 getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL),
4348 setClientSettings: (settings) =>
4449 ipcRenderer.invoke(IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, settings),
......@@ -95,23 +100,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {
95100 ...(position === undefined ? {} : { position }),
96101 }),
97102 openExternal: (url: string) => ipcRenderer.invoke(IpcChannels.OPEN_EXTERNAL_CHANNEL, url),
98 createCloudAuthRequest: () => ipcRenderer.invoke(IpcChannels.CREATE_CLOUD_AUTH_REQUEST_CHANNEL),
99 getCloudAuthToken: () => ipcRenderer.invoke(IpcChannels.GET_CLOUD_AUTH_TOKEN_CHANNEL),
100 setCloudAuthToken: (token: string) =>
101 ipcRenderer.invoke(IpcChannels.SET_CLOUD_AUTH_TOKEN_CHANNEL, token),
102 clearCloudAuthToken: () => ipcRenderer.invoke(IpcChannels.CLEAR_CLOUD_AUTH_TOKEN_CHANNEL),
103 fetchCloudAuth: (input) => ipcRenderer.invoke(IpcChannels.FETCH_CLOUD_AUTH_CHANNEL, input),
104 onCloudAuthCallback: (listener) => {
105 const wrappedListener = (_event: Electron.IpcRendererEvent, rawUrl: unknown) => {
106 if (typeof rawUrl !== "string") return;
107 listener(rawUrl);
108 };
109
110 ipcRenderer.on(IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL, wrappedListener);
111 return () => {
112 ipcRenderer.removeListener(IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL, wrappedListener);
113 };
114 },
115103 onMenuAction: (listener) => {
116104 const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => {
117105 if (typeof action !== "string") return;
apps/desktop/src/window/DesktopWindow.test.ts+5-5
......@@ -191,19 +191,19 @@ describe("DesktopWindow", () => {
191191 it("recognizes only same-origin renderer navigations", () => {
192192 assert.isTrue(
193193 DesktopWindow.isSameOriginRendererNavigation({
194 applicationUrl: "http://127.0.0.1:3773/",
195 navigationUrl: "http://127.0.0.1:3773/settings/connections",
194 applicationUrl: "t3code://app/",
195 navigationUrl: "t3code://app/settings/connections",
196196 }),
197197 );
198198 assert.isFalse(
199199 DesktopWindow.isSameOriginRendererNavigation({
200 applicationUrl: "http://127.0.0.1:3773/",
200 applicationUrl: "t3code://app/",
201201 navigationUrl: "https://accounts.microsoft.com/oauth",
202202 }),
203203 );
204204 assert.isFalse(
205205 DesktopWindow.isSameOriginRendererNavigation({
206 applicationUrl: "http://127.0.0.1:3773/",
206 applicationUrl: "t3code://app/",
207207 navigationUrl: "not a url",
208208 }),
209209 );
......@@ -231,7 +231,7 @@ describe("DesktopWindow", () => {
231231 assert.equal(yield* Ref.get(createCount), 1);
232232 assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);
233233 assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]);
234 assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["http://127.0.0.1:5733/"]);
234 assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["t3code-dev://app/"]);
235235 assert.equal(fakeWindow.openDevTools.mock.calls.length, 1);
236236 }).pipe(Effect.provide(layer));
237237 }),
apps/desktop/src/window/DesktopWindow.ts+7-30
......@@ -1,5 +1,4 @@
11import * as Context from "effect/Context";
2import * as Data from "effect/Data";
32import * as Effect from "effect/Effect";
43import * as Layer from "effect/Layer";
54import * as Option from "effect/Option";
......@@ -16,8 +15,8 @@ import * as ElectronMenu from "../electron/ElectronMenu.ts";
1615import * as ElectronShell from "../electron/ElectronShell.ts";
1716import * as ElectronTheme from "../electron/ElectronTheme.ts";
1817import * as ElectronWindow from "../electron/ElectronWindow.ts";
18import { getDesktopUrl } from "../electron/ElectronProtocol.ts";
1919import * as IpcChannels from "../ipc/channels.ts";
20import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts";
2120
2221const TITLEBAR_HEIGHT = 40;
2322const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux
......@@ -32,7 +31,6 @@ type WindowTitleBarOptions = Pick<
3231type DesktopWindowRuntimeServices =
3332 | DesktopEnvironment.DesktopEnvironment
3433 | DesktopAssets.DesktopAssets
35 | DesktopServerExposure.DesktopServerExposure
3634 | DesktopState.DesktopState
3735 | ElectronMenu.ElectronMenu
3836 | ElectronShell.ElectronShell
......@@ -40,16 +38,7 @@ type DesktopWindowRuntimeServices =
4038 | ElectronWindow.ElectronWindow
4139 | PreviewManager.PreviewManager;
4240
43export class DesktopWindowDevServerUrlMissingError extends Data.TaggedError(
44 "DesktopWindowDevServerUrlMissingError",
45)<{}> {
46 override get message() {
47 return "VITE_DEV_SERVER_URL is required in desktop development.";
48 }
49}
50
5141export type DesktopWindowError =
52 | DesktopWindowDevServerUrlMissingError
5342 | ElectronWindow.ElectronWindowCreateError
5443 | PreviewManager.PreviewManagerError;
5544
......@@ -71,15 +60,6 @@ export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowS
7160const { logInfo: logWindowInfo, logWarning: logWindowWarning } =
7261 DesktopObservability.makeComponentLogger("desktop-window");
7362
74function resolveDesktopDevServerUrl(
75 environment: DesktopEnvironment.DesktopEnvironmentShape,
76): Effect.Effect<string, DesktopWindowDevServerUrlMissingError> {
77 return Option.match(environment.devServerUrl, {
78 onNone: () => Effect.fail(new DesktopWindowDevServerUrlMissingError()),
79 onSome: (url) => Effect.succeed(url.href),
80 });
81}
82
8363function getIconOption(
8464 iconPaths: DesktopAssets.DesktopIconPaths,
8565 platform: NodeJS.Platform,
......@@ -171,18 +151,16 @@ const make = Effect.gen(function* () {
171151 const electronTheme = yield* ElectronTheme.ElectronTheme;
172152 const electronWindow = yield* ElectronWindow.ElectronWindow;
173153 const previewManager = yield* PreviewManager.PreviewManager;
174 const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
175154 const state = yield* DesktopState.DesktopState;
176155 const context = yield* Effect.context<DesktopWindowRuntimeServices>();
177156 const runPromise = Effect.runPromiseWith(context);
178157
179 const createWindow = Effect.fn("desktop.window.createWindow")(function* (
180 backendHttpUrl: URL,
181 ): Effect.fn.Return<Electron.BrowserWindow, DesktopWindowError> {
158 const createWindow = Effect.fn("desktop.window.createWindow")(function* (): Effect.fn.Return<
159 Electron.BrowserWindow,
160 DesktopWindowError
161 > {
182162 yield* previewManager.getBrowserSession();
183 const applicationUrl = environment.isDevelopment
184 ? yield* resolveDesktopDevServerUrl(environment)
185 : backendHttpUrl.href;
163 const applicationUrl = getDesktopUrl(environment.isDevelopment);
186164 const iconPaths = yield* assets.iconPaths;
187165 const iconOption = getIconOption(iconPaths, environment.platform);
188166 const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors;
......@@ -350,8 +328,7 @@ const make = Effect.gen(function* () {
350328 });
351329
352330 const createMain = Effect.gen(function* () {
353 const backendConfig = yield* serverExposure.backendConfig;
354 const window = yield* createWindow(backendConfig.httpBaseUrl);
331 const window = yield* createWindow();
355332 yield* electronWindow.setMain(window);
356333 yield* logWindowInfo("main window created");
357334 return window;
apps/desktop/vite.config.ts+6
......@@ -56,6 +56,12 @@ export default defineConfig({
5656 outExtensions: () => ({ js: ".cjs" }),
5757 define: publicConfigDefine,
5858 entry: ["src/preload.ts"],
59 deps: {
60 // Sandboxed Electron preloads cannot reliably resolve package imports
61 // from inside the packaged ASAR. Bundle Clerk's preload bridge into the
62 // preload artifact instead of leaving a runtime require() behind.
63 alwaysBundle: (id) => id === "@clerk/electron" || id.startsWith("@clerk/electron/"),
64 },
5965 },
6066 {
6167 format: "cjs",
apps/mobile/package.json+1-1
......@@ -40,7 +40,7 @@
4040 },
4141 "dependencies": {
4242 "@callstack/liquid-glass": "^0.7.1",
43 "@clerk/expo": "^3.4.1",
43 "@clerk/expo": "catalog:",
4444 "@effect/atom-react": "catalog:",
4545 "@expo-google-fonts/dm-sans": "^0.4.2",
4646 "@expo/ui": "~56.0.8",
apps/mobile/src/connection/platform.ts+5
......@@ -3,6 +3,7 @@ import {
33 CloudSession,
44 EnvironmentOwnedDataCleanup,
55 PlatformConnectionSource,
6 PrimaryEnvironmentAuth,
67 RelayDeviceIdentity,
78 SshEnvironmentGateway,
89} from "@t3tools/client-runtime/platform";
......@@ -119,6 +120,10 @@ const capabilitiesLayer = Layer.succeedContext(
119120 }),
120121 }),
121122 ).pipe(
123 Context.add(
124 PrimaryEnvironmentAuth,
125 PrimaryEnvironmentAuth.of({ bearerToken: Effect.succeed(Option.none()) }),
126 ),
122127 Context.add(
123128 RelayDeviceIdentity,
124129 RelayDeviceIdentity.of({
apps/server/src/relay/AgentAwarenessRelay.test.ts+5-1
......@@ -625,7 +625,11 @@ describe.sequential("signRelayAgentActivityPublishProof", () => {
625625 } satisfies ExecutionEnvironmentDescriptor;
626626
627627 globalThis.fetch = ((input: Parameters<typeof fetch>[0]) => {
628 const url = new URL(input instanceof Request ? input.url : input.toString());
628 const url = new URL(
629 typeof input === "string" || input instanceof URL
630 ? input
631 : (input as unknown as { readonly url: string }).url,
632 );
629633 runFork(Deferred.succeed(fetchSeen, url));
630634 return Promise.resolve(Response.json({ ok: true, deliveries: [] }));
631635 }) as unknown as typeof fetch;
apps/web/package.json+2-2
......@@ -12,8 +12,8 @@
1212 },
1313 "dependencies": {
1414 "@base-ui/react": "^1.4.1",
15 "@clerk/clerk-js": "^6.16.0",
16 "@clerk/react": "^6.9.0",
15 "@clerk/electron": "catalog:",
16 "@clerk/react": "catalog:",
1717 "@dnd-kit/core": "^6.3.1",
1818 "@dnd-kit/modifiers": "^9.0.0",
1919 "@dnd-kit/sortable": "^10.0.0",
apps/web/src/authBootstrap.test.ts+20-17
......@@ -1,5 +1,4 @@
11import {
2 AuthSessionState as AuthSessionStateSchema,
32 EnvironmentAuthInvalidError,
43 type AuthBrowserSessionResult,
54 type AuthCreatePairingCredentialInput,
......@@ -8,10 +7,11 @@ import {
87} from "@t3tools/contracts";
98import * as DateTime from "effect/DateTime";
109import * as Effect from "effect/Effect";
11import * as Schema from "effect/Schema";
10import { HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
1211import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
1312
1413import { installEnvironmentHttpTest } from "../test/environmentHttpTest";
14import { __setPrimaryHttpRunnerForTests, type PrimaryHttpEffectRunner } from "./lib/runtime";
1515
1616type TestWindow = {
1717 location: URL;
......@@ -36,8 +36,6 @@ const DESKTOP_AUTH = {
3636} as const;
3737
3838const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z");
39const encodeAuthSessionState = Schema.encodeSync(AuthSessionStateSchema);
40
4139const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
4240 authenticated: false,
4341 auth,
......@@ -117,6 +115,7 @@ describe("resolveInitialServerAuthGateState", () => {
117115 disposeHttpTest = undefined;
118116 const { __resetServerAuthBootstrapForTests } = await import("./environments/primary");
119117 __resetServerAuthBootstrapForTests();
118 __setPrimaryHttpRunnerForTests();
120119 vi.unstubAllEnvs();
121120 vi.useRealTimers();
122121 vi.restoreAllMocks();
......@@ -220,18 +219,22 @@ describe("resolveInitialServerAuthGateState", () => {
220219
221220 it("retries transient auth session bootstrap failures after restart", async () => {
222221 vi.useFakeTimers();
223 const fetchMock = vi
224 .fn<typeof fetch>()
225 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))
226 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))
227 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))
228 .mockResolvedValueOnce(
229 new Response(
230 JSON.stringify(encodeAuthSessionState(unauthenticatedSession(LOOPBACK_AUTH))),
231 { status: 200, headers: { "content-type": "application/json" } },
232 ),
233 );
234 vi.stubGlobal("fetch", fetchMock);
222 let attempts = 0;
223 const request = HttpClientRequest.get("http://localhost/api/auth/session");
224 const response = HttpClientResponse.fromWeb(
225 request,
226 new Response("Bad Gateway", { status: 502 }),
227 );
228 const runner: PrimaryHttpEffectRunner = async <A>() => {
229 attempts += 1;
230 if (attempts < 4) {
231 throw new HttpClientError.HttpClientError({
232 reason: new HttpClientError.StatusCodeError({ request, response }),
233 });
234 }
235 return unauthenticatedSession(LOOPBACK_AUTH) as A;
236 };
237 __setPrimaryHttpRunnerForTests(runner);
235238
236239 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
237240
......@@ -242,7 +245,7 @@ describe("resolveInitialServerAuthGateState", () => {
242245 status: "requires-auth",
243246 auth: LOOPBACK_AUTH,
244247 });
245 expect(fetchMock).toHaveBeenCalledTimes(4);
248 expect(attempts).toBe(4);
246249 });
247250
248251 it("takes a pairing token from the location hash and strips it immediately", async () => {
apps/web/src/cloud/desktopAuth.test.ts deleted-59
......@@ -1,59 +0,0 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { resolveDesktopCloudAuthOAuthOptions } from "./desktopAuth";
4
5describe("resolveDesktopCloudAuthOAuthOptions", () => {
6 it("ignores absent social provider settings", () => {
7 expect(
8 resolveDesktopCloudAuthOAuthOptions({
9 environment: {
10 userSettings: {
11 social: {
12 github: null,
13 google: {
14 strategy: "oauth_google",
15 enabled: true,
16 authenticatable: true,
17 },
18 },
19 },
20 },
21 }),
22 ).toEqual([
23 {
24 strategy: "oauth_google",
25 label: "Google",
26 providerId: "google",
27 iconUrl: null,
28 },
29 ]);
30 });
31
32 it("preserves provider display metadata when Clerk exposes the strategy list", () => {
33 expect(
34 resolveDesktopCloudAuthOAuthOptions({
35 environment: {
36 userSettings: {
37 authenticatableSocialStrategies: ["oauth_google"],
38 social: {
39 oauth_google: {
40 strategy: "oauth_google",
41 enabled: true,
42 authenticatable: true,
43 name: "Google",
44 logo_url: "https://img.clerk.com/static/google.png",
45 },
46 },
47 },
48 },
49 }),
50 ).toEqual([
51 {
52 strategy: "oauth_google",
53 label: "Google",
54 providerId: "google",
55 iconUrl: "https://img.clerk.com/static/google.png",
56 },
57 ]);
58 });
59});
apps/web/src/cloud/desktopAuth.ts deleted-144
......@@ -1,144 +0,0 @@
1export type DesktopCloudAuthOAuthStrategy = `oauth_${string}`;
2
3export interface DesktopCloudAuthOAuthOption {
4 readonly strategy: DesktopCloudAuthOAuthStrategy;
5 readonly label: string;
6 readonly providerId: string;
7 readonly iconUrl: string | null;
8}
9
10interface ClerkOAuthProviderSetting {
11 readonly enabled?: unknown;
12 readonly authenticatable?: unknown;
13 readonly strategy?: unknown;
14 readonly name?: unknown;
15 readonly logo_url?: unknown;
16}
17
18interface ClerkUserSettingsLike {
19 readonly authenticatableSocialStrategies?: unknown;
20 readonly social?: unknown;
21}
22
23interface ClerkEnvironmentLike {
24 readonly userSettings?: ClerkUserSettingsLike;
25}
26
27interface ClerkLike {
28 readonly __internal_environment?: ClerkEnvironmentLike;
29 readonly environment?: ClerkEnvironmentLike;
30}
31
32const isClerkOAuthProviderSetting = (value: unknown): value is ClerkOAuthProviderSetting =>
33 typeof value === "object" && value !== null;
34
35const OAUTH_LABELS: Readonly<Record<string, string>> = {
36 oauth_apple: "Apple",
37 oauth_discord: "Discord",
38 oauth_github: "GitHub",
39 oauth_gitlab: "GitLab",
40 oauth_google: "Google",
41 oauth_linear: "Linear",
42 oauth_microsoft: "Microsoft",
43 oauth_slack: "Slack",
44 oauth_x: "X",
45};
46
47// Mirrors Clerk UI's enabled-provider projection for the local desktop replacement:
48// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/hooks/useEnabledThirdPartyProviders.tsx
49export function isDesktopCloudAuthOAuthStrategy(
50 value: unknown,
51): value is DesktopCloudAuthOAuthStrategy {
52 return typeof value === "string" && value.startsWith("oauth_");
53}
54
55export function getDesktopCloudAuthOAuthStrategyLabel(
56 strategy: DesktopCloudAuthOAuthStrategy,
57): string {
58 const mapped = OAUTH_LABELS[strategy];
59 if (mapped) return mapped;
60 return strategy
61 .replace(/^oauth_custom_/, "")
62 .replace(/^oauth_/, "")
63 .split("_")
64 .filter(Boolean)
65 .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
66 .join(" ");
67}
68
69export function resolveDesktopCloudAuthOAuthOptions(
70 clerk: unknown,
71): readonly DesktopCloudAuthOAuthOption[] {
72 const environment =
73 (clerk as ClerkLike | null | undefined)?.__internal_environment ??
74 (clerk as ClerkLike | null | undefined)?.environment;
75 const userSettings = environment?.userSettings;
76 const strategies = userSettings?.authenticatableSocialStrategies;
77 if (Array.isArray(strategies)) {
78 return uniqueOptions(
79 strategies
80 .filter(isDesktopCloudAuthOAuthStrategy)
81 .map((strategy) =>
82 createOAuthOption(strategy, findProviderSetting(userSettings, strategy)),
83 ),
84 );
85 }
86
87 const social = userSettings?.social;
88 if (!social || typeof social !== "object") {
89 return [];
90 }
91
92 return uniqueOptions(
93 Object.values(social as Record<string, ClerkOAuthProviderSetting>)
94 .filter(isClerkOAuthProviderSetting)
95 .filter((provider) => provider.enabled !== false && provider.authenticatable !== false)
96 .map((provider) => {
97 const strategy = isDesktopCloudAuthOAuthStrategy(provider.strategy)
98 ? provider.strategy
99 : null;
100 if (!strategy) return null;
101 return createOAuthOption(strategy, provider);
102 })
103 .filter((option): option is DesktopCloudAuthOAuthOption => option !== null),
104 );
105}
106
107function findProviderSetting(
108 userSettings: ClerkUserSettingsLike | undefined,
109 strategy: DesktopCloudAuthOAuthStrategy,
110): ClerkOAuthProviderSetting | undefined {
111 const social = userSettings?.social;
112 if (!social || typeof social !== "object") return undefined;
113
114 return Object.values(social as Record<string, ClerkOAuthProviderSetting>)
115 .filter(isClerkOAuthProviderSetting)
116 .find((provider) => provider.strategy === strategy);
117}
118
119function createOAuthOption(
120 strategy: DesktopCloudAuthOAuthStrategy,
121 provider?: ClerkOAuthProviderSetting,
122): DesktopCloudAuthOAuthOption {
123 return {
124 strategy,
125 label:
126 typeof provider?.name === "string" && provider.name.trim()
127 ? provider.name
128 : getDesktopCloudAuthOAuthStrategyLabel(strategy),
129 providerId: strategy.replace(/^oauth_/, ""),
130 iconUrl:
131 typeof provider?.logo_url === "string" && provider.logo_url.trim() ? provider.logo_url : null,
132 };
133}
134
135function uniqueOptions(
136 options: readonly DesktopCloudAuthOAuthOption[],
137): readonly DesktopCloudAuthOAuthOption[] {
138 const seen = new Set<string>();
139 return options.filter((option) => {
140 if (seen.has(option.strategy)) return false;
141 seen.add(option.strategy);
142 return true;
143 });
144}
apps/web/src/cloud/desktopClerk.tsx deleted-322
......@@ -1,322 +0,0 @@
1import { Clerk } from "@clerk/clerk-js";
2import {
3 buildClerkUIScriptAttributes,
4 clerkUIScriptUrl,
5 InternalClerkProvider,
6} from "@clerk/react/internal";
7import type { ClerkProviderProps } from "@clerk/react";
8import {
9 clerkFrontendApiHostnameFromPublishableKey,
10 isAllowedClerkFrontendApiHostname,
11} from "@t3tools/shared/relayAuth";
12import React, { useEffect, useState } from "react";
13
14import {
15 makeDesktopClerkExternalAccountAdapter,
16 type DesktopClerkUser,
17} from "./desktopClerkExternalAccounts";
18
19type DesktopClerkUiCtor = NonNullable<Window["__internal_ClerkUICtor"]>;
20
21interface ClerkFrontendApiRequest {
22 credentials?: RequestCredentials;
23 headers?: Headers;
24 url?: URL;
25}
26
27interface ClerkFrontendApiResponse {
28 headers: Headers;
29 payload?: {
30 errors?: readonly {
31 code?: string;
32 }[];
33 };
34}
35
36interface NativeRequestClerk {
37 readonly publishableKey?: string;
38 __internal_onBeforeRequest?: (
39 listener: (request: ClerkFrontendApiRequest) => void | Promise<void>,
40 ) => void;
41 __internal_onAfterResponse?: (
42 listener: (
43 request: ClerkFrontendApiRequest,
44 response?: ClerkFrontendApiResponse,
45 ) => void | Promise<void>,
46 ) => void;
47 __unstable__onBeforeRequest?: (
48 listener: (request: ClerkFrontendApiRequest) => void | Promise<void>,
49 ) => void;
50 __unstable__onAfterResponse?: (
51 listener: (
52 request: ClerkFrontendApiRequest,
53 response?: ClerkFrontendApiResponse,
54 ) => void | Promise<void>,
55 ) => void;
56}
57
58interface DesktopClerkProviderProps {
59 readonly children: React.ReactNode;
60 readonly publishableKey: string;
61}
62
63let desktopClerk: Clerk | null = null;
64let desktopClerkFetchInstalled = false;
65let desktopClerkUiLoad: Promise<DesktopClerkUiCtor> | null = null;
66let desktopClerkFrontendApiHostname: string | null = null;
67let desktopClerkExternalAccountCleanup: (() => void) | null = null;
68
69const isNativeRequestClerk = (value: unknown): value is NativeRequestClerk => {
70 if (typeof value !== "object" || value === null) return false;
71 const candidate = value as {
72 __internal_onBeforeRequest?: unknown;
73 __internal_onAfterResponse?: unknown;
74 __unstable__onBeforeRequest?: unknown;
75 __unstable__onAfterResponse?: unknown;
76 };
77 return (
78 (typeof candidate.__internal_onBeforeRequest === "function" ||
79 typeof candidate.__unstable__onBeforeRequest === "function") &&
80 (typeof candidate.__internal_onAfterResponse === "function" ||
81 typeof candidate.__unstable__onAfterResponse === "function")
82 );
83};
84
85const getStoredClientJwt = (): Promise<string | null> =>
86 window.desktopBridge?.getCloudAuthToken() ?? Promise.resolve(null);
87
88const setStoredClientJwt = (token: string): Promise<boolean> =>
89 window.desktopBridge?.setCloudAuthToken(token) ?? Promise.resolve(false);
90
91const clearStoredClientJwt = (): Promise<void> =>
92 window.desktopBridge?.clearCloudAuthToken() ?? Promise.resolve();
93
94const isClerkFrontendApiUrl = (url: URL): boolean =>
95 url.protocol === "https:" &&
96 isAllowedClerkFrontendApiHostname(url.hostname, desktopClerkFrontendApiHostname);
97
98const headersToRecord = (headers: Headers): Record<string, string> => {
99 const record: Record<string, string> = {};
100 headers.forEach((value, key) => {
101 record[key] = value;
102 });
103 return record;
104};
105
106function installDesktopClerkFetchProxy(publishableKey: string): void {
107 desktopClerkFrontendApiHostname = clerkFrontendApiHostnameFromPublishableKey(publishableKey);
108 if (desktopClerkFetchInstalled) return;
109 const bridge = window.desktopBridge;
110 if (!bridge) return;
111
112 const browserFetch = window.fetch.bind(window);
113 window.fetch = async (input, init) => {
114 const request = new Request(input, init);
115 const url = new URL(request.url);
116 if (!isClerkFrontendApiUrl(url)) {
117 return browserFetch(input, init);
118 }
119
120 const body =
121 request.method === "GET" || request.method === "HEAD"
122 ? undefined
123 : await request.clone().text();
124 const result = await bridge.fetchCloudAuth({
125 url: request.url,
126 method: request.method,
127 headers: headersToRecord(request.headers),
128 ...(body === undefined ? {} : { body }),
129 });
130
131 return new Response(result.body, {
132 status: result.status,
133 statusText: result.statusText,
134 headers: result.headers,
135 });
136 };
137 desktopClerkFetchInstalled = true;
138}
139
140function installDesktopClerkExternalAccounts(clerk: Clerk): void {
141 desktopClerkExternalAccountCleanup?.();
142 desktopClerkExternalAccountCleanup = null;
143
144 const bridge = window.desktopBridge;
145 if (!bridge) return;
146
147 const adapter = makeDesktopClerkExternalAccountAdapter({ bridge });
148 const unsubscribe = clerk.addListener(({ user }) => {
149 if (user) {
150 adapter.installUser(user as DesktopClerkUser);
151 }
152 });
153 desktopClerkExternalAccountCleanup = () => {
154 unsubscribe();
155 adapter.dispose();
156 };
157}
158
159function loadDesktopClerkUi(publishableKey: string): Promise<DesktopClerkUiCtor> {
160 if (window.__internal_ClerkUICtor) {
161 return Promise.resolve(window.__internal_ClerkUICtor);
162 }
163 if (desktopClerkUiLoad) {
164 return desktopClerkUiLoad;
165 }
166
167 const load = new Promise<DesktopClerkUiCtor>((resolve, reject) => {
168 const scriptUrl = clerkUIScriptUrl({ publishableKey });
169 const existingScript = document.querySelector<HTMLScriptElement>(
170 "script[data-clerk-ui-script]",
171 );
172
173 const resolveLoadedUi = () => {
174 const ClerkUI = window.__internal_ClerkUICtor;
175 if (ClerkUI) {
176 resolve(ClerkUI);
177 return true;
178 }
179 return false;
180 };
181 if (resolveLoadedUi()) {
182 return;
183 }
184
185 const script = existingScript ?? document.createElement("script");
186 script.async = true;
187 script.crossOrigin = "anonymous";
188 script.src = scriptUrl;
189 script.dataset.clerkUiScript = "true";
190 const attributes = buildClerkUIScriptAttributes({ publishableKey });
191 for (const [name, value] of Object.entries(attributes)) {
192 script.setAttribute(name, value);
193 }
194
195 const timeoutId = window.setTimeout(() => {
196 reject(new Error("Timed out loading Clerk UI for desktop auth."));
197 }, 15_000);
198 script.addEventListener("load", () => {
199 window.clearTimeout(timeoutId);
200 if (!resolveLoadedUi()) {
201 reject(new Error("Clerk UI loaded without exposing the UI constructor."));
202 }
203 });
204 script.addEventListener("error", () => {
205 window.clearTimeout(timeoutId);
206 reject(new Error("Failed to load Clerk UI for desktop auth."));
207 });
208 if (!existingScript) {
209 document.head.append(script);
210 }
211 }).catch((error: unknown) => {
212 desktopClerkUiLoad = null;
213 throw error;
214 });
215
216 desktopClerkUiLoad = load;
217 return load;
218}
219
220function getDesktopClerkInstance(publishableKey: string): Clerk {
221 installDesktopClerkFetchProxy(publishableKey);
222
223 const hasKeyChanged = desktopClerk !== null && desktopClerk.publishableKey !== publishableKey;
224 if (hasKeyChanged) {
225 void clearStoredClientJwt();
226 desktopClerkExternalAccountCleanup?.();
227 desktopClerkExternalAccountCleanup = null;
228 desktopClerk = null;
229 }
230
231 if (desktopClerk !== null) {
232 return desktopClerk;
233 }
234
235 const nextClerk = new Clerk(publishableKey);
236 installDesktopClerkExternalAccounts(nextClerk);
237 if (!isNativeRequestClerk(nextClerk)) {
238 desktopClerk = nextClerk;
239 return nextClerk;
240 }
241
242 const onBeforeRequest =
243 nextClerk.__internal_onBeforeRequest ?? nextClerk.__unstable__onBeforeRequest;
244 const onAfterResponse =
245 nextClerk.__internal_onAfterResponse ?? nextClerk.__unstable__onAfterResponse;
246
247 // Keep this aligned with Clerk Expo's native FAPI adapter:
248 // https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/expo/src/provider/singleton/createClerkInstance.ts
249 onBeforeRequest(async (request) => {
250 request.credentials = "omit";
251 request.url?.searchParams.append("_is_native", "1");
252 const headers = new Headers(request.headers);
253
254 const clientJwt = await getStoredClientJwt();
255 headers.set("authorization", clientJwt ?? "");
256 headers.set("x-mobile", "1");
257 request.headers = headers;
258 });
259
260 onAfterResponse(async (_request, response) => {
261 const clientJwt = response?.headers.get("authorization");
262 if (clientJwt) {
263 await setStoredClientJwt(clientJwt);
264 }
265
266 const errorCode = response?.payload?.errors?.[0]?.code;
267 if (errorCode === "native_api_disabled") {
268 console.error(
269 "Clerk Native API is disabled. Enable Native applications in the Clerk dashboard for desktop sign-in.",
270 );
271 }
272 });
273
274 desktopClerk = nextClerk;
275 return nextClerk;
276}
277
278export function DesktopClerkProvider({ children, publishableKey }: DesktopClerkProviderProps) {
279 const [clerkUiCtor, setClerkUiCtor] = useState<DesktopClerkUiCtor | undefined>(
280 () => window.__internal_ClerkUICtor,
281 );
282 const [clerkUiError, setClerkUiError] = useState<unknown>(null);
283
284 useEffect(() => {
285 let isCurrent = true;
286 void loadDesktopClerkUi(publishableKey).then(
287 (ClerkUI) => {
288 if (isCurrent) {
289 setClerkUiCtor(() => ClerkUI);
290 }
291 },
292 (error: unknown) => {
293 if (isCurrent) {
294 setClerkUiError(error);
295 }
296 },
297 );
298 return () => {
299 isCurrent = false;
300 };
301 }, [publishableKey]);
302
303 if (!clerkUiCtor) {
304 if (clerkUiError) {
305 console.error("Failed to load Clerk UI for desktop auth.", clerkUiError);
306 }
307 return null;
308 }
309
310 const clerk = getDesktopClerkInstance(publishableKey);
311 return (
312 <InternalClerkProvider
313 key={publishableKey}
314 publishableKey={publishableKey}
315 Clerk={clerk as ClerkProviderProps["Clerk"]}
316 ui={{ ClerkUI: clerkUiCtor }}
317 standardBrowser={false}
318 >
319 {children}
320 </InternalClerkProvider>
321 );
322}
apps/web/src/cloud/desktopClerkExternalAccounts.test.ts deleted-78
......@@ -1,78 +0,0 @@
1import { describe, expect, it, vi } from "vite-plus/test";
2
3import {
4 makeDesktopClerkExternalAccountAdapter,
5 type DesktopClerkUser,
6} from "./desktopClerkExternalAccounts";
7
8describe("desktop Clerk external account adapter", () => {
9 it("replaces renderer redirects with native callbacks and reloads the user on return", async () => {
10 const callbacks: ((rawUrl: string) => void)[] = [];
11 const callbackCleanup = vi.fn();
12 const bridge = {
13 createCloudAuthRequest: vi
14 .fn()
15 .mockResolvedValueOnce("t3code://auth/callback?t3_state=add")
16 .mockResolvedValueOnce("t3code://auth/callback?t3_state=reconnect"),
17 onCloudAuthCallback: vi.fn((listener: (rawUrl: string) => void) => {
18 callbacks.push(listener);
19 return callbackCleanup;
20 }),
21 };
22 const reauthorize = vi.fn(async (_params: Record<string, unknown>) => account);
23 const account = { reauthorize };
24 const createExternalAccount = vi.fn(async (_params: Record<string, unknown>) => account);
25 const reload = vi.fn(async () => undefined);
26 const user = {
27 externalAccounts: [],
28 createExternalAccount,
29 reload,
30 } satisfies DesktopClerkUser;
31 const adapter = makeDesktopClerkExternalAccountAdapter({ bridge });
32 adapter.installUser(user);
33
34 await user.createExternalAccount({
35 redirectUrl: "http://127.0.0.1:3773/?__clerk_modal_state=state",
36 strategy: "oauth_microsoft",
37 });
38
39 expect(createExternalAccount).toHaveBeenCalledWith({
40 redirectUrl: "t3code://auth/callback?t3_state=add",
41 strategy: "oauth_microsoft",
42 });
43
44 callbacks[0]?.("t3code://auth/callback?t3_state=add");
45 await Promise.resolve();
46 expect(reload).toHaveBeenCalledOnce();
47
48 await account.reauthorize({
49 redirectUrl: "http://127.0.0.1:3773/?__clerk_modal_state=state",
50 });
51 expect(reauthorize).toHaveBeenCalledWith({
52 redirectUrl: "t3code://auth/callback?t3_state=reconnect",
53 });
54 });
55
56 it("cleans up the pending callback when Clerk rejects account creation", async () => {
57 const callbackCleanup = vi.fn();
58 const bridge = {
59 createCloudAuthRequest: vi.fn().mockResolvedValue("t3code://auth/callback?t3_state=failed"),
60 onCloudAuthCallback: vi.fn(() => callbackCleanup),
61 };
62 const createError = new Error("oauth provider unavailable");
63 const user = {
64 externalAccounts: [],
65 createExternalAccount: vi.fn(async (_params: Record<string, unknown>) => {
66 throw createError;
67 }),
68 reload: vi.fn(async () => undefined),
69 } satisfies DesktopClerkUser;
70 const adapter = makeDesktopClerkExternalAccountAdapter({ bridge });
71 adapter.installUser(user);
72
73 await expect(user.createExternalAccount({ strategy: "oauth_microsoft" })).rejects.toBe(
74 createError,
75 );
76 expect(callbackCleanup).toHaveBeenCalledOnce();
77 });
78});
apps/web/src/cloud/desktopClerkExternalAccounts.ts deleted-112
......@@ -1,112 +0,0 @@
1interface DesktopClerkExternalAccountParams {
2 readonly redirectUrl?: string;
3 readonly [key: string]: unknown;
4}
5
6interface DesktopClerkExternalAccount {
7 reauthorize: (params: DesktopClerkExternalAccountParams) => Promise<DesktopClerkExternalAccount>;
8}
9
10interface DesktopClerkUser {
11 readonly externalAccounts: readonly DesktopClerkExternalAccount[];
12 createExternalAccount: (
13 params: DesktopClerkExternalAccountParams,
14 ) => Promise<DesktopClerkExternalAccount>;
15 reload: () => Promise<unknown>;
16}
17
18interface DesktopClerkExternalAccountBridge {
19 readonly createCloudAuthRequest: () => Promise<string>;
20 readonly onCloudAuthCallback: (listener: (rawUrl: string) => void) => () => void;
21}
22
23interface DesktopClerkExternalAccountAdapter {
24 readonly dispose: () => void;
25 readonly installUser: (user: DesktopClerkUser) => void;
26}
27
28interface MakeDesktopClerkExternalAccountAdapterInput {
29 readonly bridge: DesktopClerkExternalAccountBridge;
30 readonly reportError?: (message: string, error: unknown) => void;
31}
32
33// Clerk's profile component uses window.location.href as the OAuth callback and navigates the
34// current window to the provider. Keep the upstream component intact while adapting its resource
35// calls to the native callback bridge:
36// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx
37// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx
38export function makeDesktopClerkExternalAccountAdapter({
39 bridge,
40 reportError = console.error,
41}: MakeDesktopClerkExternalAccountAdapterInput): DesktopClerkExternalAccountAdapter {
42 const installedAccounts = new WeakSet<object>();
43 const installedUsers = new WeakSet<object>();
44 let callbackGeneration = 0;
45 let callbackCleanup: (() => void) | null = null;
46
47 const clearCallback = () => {
48 callbackGeneration += 1;
49 callbackCleanup?.();
50 callbackCleanup = null;
51 };
52
53 const createRedirectUrl = async (user: DesktopClerkUser): Promise<string> => {
54 clearCallback();
55 const redirectUrl = await bridge.createCloudAuthRequest();
56 const generation = callbackGeneration;
57 callbackCleanup = bridge.onCloudAuthCallback(() => {
58 if (generation !== callbackGeneration) return;
59 clearCallback();
60 void user.reload().catch((error: unknown) => {
61 reportError("Failed to reload Clerk after desktop account linking.", error);
62 });
63 });
64 return redirectUrl;
65 };
66
67 const installAccount = (user: DesktopClerkUser, account: DesktopClerkExternalAccount): void => {
68 if (installedAccounts.has(account)) return;
69 installedAccounts.add(account);
70
71 const reauthorize = account.reauthorize.bind(account);
72 account.reauthorize = async (params) => {
73 const redirectUrl = await createRedirectUrl(user);
74 try {
75 const nextAccount = await reauthorize({ ...params, redirectUrl });
76 installAccount(user, nextAccount);
77 return nextAccount;
78 } catch (error) {
79 clearCallback();
80 throw error;
81 }
82 };
83 };
84
85 const installUser = (user: DesktopClerkUser): void => {
86 for (const account of user.externalAccounts) {
87 installAccount(user, account);
88 }
89 if (installedUsers.has(user)) return;
90 installedUsers.add(user);
91
92 const createExternalAccount = user.createExternalAccount.bind(user);
93 user.createExternalAccount = async (params) => {
94 const redirectUrl = await createRedirectUrl(user);
95 try {
96 const account = await createExternalAccount({ ...params, redirectUrl });
97 installAccount(user, account);
98 return account;
99 } catch (error) {
100 clearCallback();
101 throw error;
102 }
103 };
104 };
105
106 return {
107 dispose: clearCallback,
108 installUser,
109 };
110}
111
112export type { DesktopClerkExternalAccountAdapter, DesktopClerkUser };
apps/web/src/cloud/linkEnvironment.test.ts+30
......@@ -1,4 +1,5 @@
11import {
2 type DesktopBridge,
23 EnvironmentId,
34 type RelayClientInstallProgressEvent,
45 WS_METHODS,
......@@ -28,6 +29,7 @@ import {
2829 ManagedRelayDpopSigner,
2930} from "@t3tools/client-runtime/relay";
3031import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
32import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth";
3133
3234import {
3335 collectCloudLinkTargets,
......@@ -146,6 +148,7 @@ beforeEach(() => {
146148});
147149
148150afterEach(() => {
151 __resetDesktopPrimaryAuthForTests();
149152 vi.unstubAllGlobals();
150153 vi.unstubAllEnvs();
151154 vi.restoreAllMocks();
......@@ -224,6 +227,33 @@ describe("web cloud link environment client", () => {
224227 }),
225228 );
226229
230 it.effect("uses desktop bearer auth for primary cloud link state", () =>
231 Effect.gen(function* () {
232 const fetchMock = vi.fn().mockResolvedValue(
233 Response.json({
234 linked: true,
235 cloudUserId: "user-1",
236 relayUrl: "https://relay.example.test",
237 relayIssuer: "https://relay.example.test",
238 publishAgentActivity: false,
239 }),
240 );
241 vi.stubGlobal("fetch", fetchMock);
242 vi.stubGlobal("window", {
243 location: { origin: "t3code://app" },
244 desktopBridge: {
245 getLocalEnvironmentBearerToken: vi.fn().mockResolvedValue("desktop-bearer-token"),
246 } as unknown as DesktopBridge,
247 });
248
249 yield* withServices(readPrimaryCloudLinkState({ target: TARGET }));
250
251 const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]);
252 expect(request.credentials).not.toBe("include");
253 expect(request.headers.get("authorization")).toBe("Bearer desktop-bearer-token");
254 }),
255 );
256
227257 it.effect("links an available primary environment without invoking installation", () =>
228258 Effect.gen(function* () {
229259 const fetchMock = vi
apps/web/src/cloud/linkEnvironment.ts+9-22
......@@ -31,7 +31,7 @@ import {
3131 readPrimaryEnvironmentDescriptor,
3232 readPrimaryEnvironmentTarget,
3333} from "../environments/primary";
34import { withPrimaryEnvironmentRequestInit } from "../environments/primary/requestInit";
34import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
3535import { resolveCloudPublicConfig } from "./publicConfig";
3636import {
3737 finishRelayClientInstall,
......@@ -327,11 +327,8 @@ export function readPrimaryCloudLinkState(input: {
327327 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
328328 return yield* client.connect
329329 .linkState({ headers: {} })
330 .pipe(
331 withPrimaryEnvironmentRequestInit,
332 Effect.mapError(environmentApiError("Could not read environment cloud link state.")),
333 );
334 });
330 .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state.")));
331 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
335332}
336333
337334export function updatePrimaryCloudPreferences(input: {
......@@ -346,10 +343,9 @@ export function updatePrimaryCloudPreferences(input: {
346343 payload: input,
347344 })
348345 .pipe(
349 withPrimaryEnvironmentRequestInit,
350346 Effect.mapError(environmentApiError("Could not update environment cloud preferences.")),
351347 );
352 });
348 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
353349}
354350
355351export function unlinkPrimaryEnvironmentFromCloud(input: {
......@@ -360,10 +356,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {
360356 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
361357 yield* client.connect
362358 .unlink({ headers: {} })
363 .pipe(
364 withPrimaryEnvironmentRequestInit,
365 Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")),
366 );
359 .pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")));
367360
368361 const configuredRelayUrl = relayUrl();
369362 if (configuredRelayUrl && input.clerkToken) {
......@@ -381,7 +374,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {
381374 ),
382375 );
383376 }
384 });
377 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
385378}
386379
387380export function linkPrimaryEnvironmentToCloud(input: {
......@@ -433,10 +426,7 @@ export function linkPrimaryEnvironmentToCloud(input: {
433426 origin: endpointOrigin(input.target.httpBaseUrl),
434427 },
435428 })
436 .pipe(
437 withPrimaryEnvironmentRequestInit,
438 Effect.mapError(environmentApiError("Could not obtain environment link proof.")),
439 );
429 .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof.")));
440430 const link = yield* relayClient
441431 .linkEnvironment({
442432 clerkToken: input.clerkToken,
......@@ -470,9 +460,6 @@ export function linkPrimaryEnvironmentToCloud(input: {
470460 endpointRuntime: link.endpointRuntime,
471461 },
472462 })
473 .pipe(
474 withPrimaryEnvironmentRequestInit,
475 Effect.mapError(environmentApiError("Could not configure environment relay access.")),
476 );
477 });
463 .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access.")));
464 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
478465}
apps/web/src/components/clerk/DesktopClerkCard.tsx deleted-137
......@@ -1,137 +0,0 @@
1import type { ReactNode } from "react";
2
3import { cn } from "../../lib/utils";
4
5// Mirrors Clerk's raised card/footer/branding composition for the desktop-native flow:
6// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/elements/Card/CardRoot.tsx
7// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/elements/Card/CardFooter.tsx
8// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx
9// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/elements/DevModeNotice.tsx
10export function DesktopClerkCard({
11 children,
12 footerAction,
13}: {
14 children: ReactNode;
15 footerAction?: ReactNode;
16}) {
17 return (
18 <div className="isolate w-full max-w-[25rem] overflow-hidden rounded-xl border border-border/80 bg-card text-card-foreground shadow-[0_12px_28px_rgba(0,0,0,0.12)]">
19 <div className="relative -m-px flex flex-col gap-8 rounded-lg border border-border/80 bg-card px-10 py-8 text-center">
20 {children}
21 </div>
22 <div className="-mt-2 flex flex-col bg-muted/45 pt-2">
23 {footerAction ? (
24 <div className="border-t border-border/70 px-8 py-4">{footerAction}</div>
25 ) : null}
26 <DesktopClerkBranding />
27 </div>
28 </div>
29 );
30}
31
32export function DesktopClerkHeader({ title, subtitle }: { title: string; subtitle: string }) {
33 return (
34 <div className="space-y-1">
35 <h2 className="text-xl font-bold tracking-[-0.025em] text-foreground">{title}</h2>
36 <p className="text-sm leading-5 text-muted-foreground">{subtitle}</p>
37 </div>
38 );
39}
40
41export function DesktopClerkFooterAction({
42 children,
43 actionLabel,
44 onAction,
45}: {
46 children: ReactNode;
47 actionLabel: string;
48 onAction: () => void;
49}) {
50 return (
51 <p className="flex items-center justify-center gap-1 text-sm text-muted-foreground">
52 <span>{children}</span>
53 <button
54 type="button"
55 className="cursor-pointer font-semibold text-foreground outline-none hover:underline focus-visible:underline"
56 onClick={onAction}
57 >
58 {actionLabel}
59 </button>
60 </p>
61 );
62}
63
64export function DesktopClerkAlert({ children }: { children?: ReactNode }) {
65 if (!children) return null;
66
67 return (
68 <div
69 role="alert"
70 className="rounded-md border border-destructive/20 bg-destructive/8 px-3 py-2 text-left text-xs leading-5 text-destructive-foreground"
71 >
72 {children}
73 </div>
74 );
75}
76
77export function DesktopClerkInput({
78 className,
79 ...props
80}: React.ComponentPropsWithoutRef<"input">) {
81 return (
82 <input
83 {...props}
84 className={cn(
85 "h-10 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-xs outline-none placeholder:text-muted-foreground/80 focus:border-ring focus:ring-2 focus:ring-ring/15 disabled:opacity-64",
86 className,
87 )}
88 />
89 );
90}
91
92export function DesktopClerkPrimaryButton({
93 children,
94 disabled,
95}: {
96 children: ReactNode;
97 disabled?: boolean;
98}) {
99 return (
100 <button
101 type="submit"
102 disabled={disabled}
103 className="h-10 w-full cursor-pointer rounded-md border border-neutral-950 bg-neutral-900 px-4 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-neutral-800 disabled:pointer-events-none disabled:opacity-64 dark:border-neutral-200 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white"
104 >
105 {children}
106 </button>
107 );
108}
109
110function DesktopClerkBranding() {
111 const isDevelopmentMode = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY?.startsWith("pk_test_");
112
113 return (
114 <div
115 className={cn(
116 "flex flex-col items-center justify-center gap-2 border-t border-border/70 px-8 py-4 text-xs text-muted-foreground",
117 isDevelopmentMode &&
118 "bg-[repeating-linear-gradient(-45deg,rgba(249,115,22,0.035),rgba(249,115,22,0.035)_8px,transparent_8px,transparent_16px)]",
119 )}
120 >
121 <span>
122 Secured by{" "}
123 <a
124 className="font-bold text-muted-foreground hover:text-foreground"
125 href="https://go.clerk.com/components"
126 rel="noreferrer"
127 target="_blank"
128 >
129 clerk
130 </a>
131 </span>
132 {isDevelopmentMode ? (
133 <strong className="font-semibold text-orange-500">Development mode</strong>
134 ) : null}
135 </div>
136 );
137}
apps/web/src/components/clerk/DesktopClerkSignIn.tsx deleted-150
......@@ -1,150 +0,0 @@
1import { LoaderCircleIcon } from "lucide-react";
2
3import type {
4 DesktopCloudAuthOAuthOption,
5 DesktopCloudAuthOAuthStrategy,
6} from "../../cloud/desktopAuth";
7import { cn } from "../../lib/utils";
8import {
9 DesktopClerkAlert,
10 DesktopClerkCard,
11 DesktopClerkFooterAction,
12 DesktopClerkHeader,
13} from "./DesktopClerkCard";
14import { useDesktopClerkSignIn } from "./useDesktopClerkSignIn";
15
16// Mirrors Clerk's compact social-button layout while delegating OAuth to the desktop bridge:
17// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/elements/SocialButtons.tsx
18export function DesktopClerkSignIn({ onJoinWaitlist }: { onJoinWaitlist: () => void }) {
19 const { isStarting, oauthOptions, startingStrategy, startOAuth } = useDesktopClerkSignIn();
20
21 return (
22 <DesktopClerkSignInCard
23 isStarting={isStarting}
24 oauthOptions={oauthOptions}
25 startingStrategy={startingStrategy}
26 onJoinWaitlist={onJoinWaitlist}
27 onStartOAuth={(strategy) => void startOAuth(strategy)}
28 />
29 );
30}
31
32export function DesktopClerkSignInCard({
33 isStarting,
34 oauthOptions,
35 startingStrategy,
36 onJoinWaitlist,
37 onStartOAuth,
38}: {
39 isStarting: boolean;
40 oauthOptions: readonly DesktopCloudAuthOAuthOption[];
41 startingStrategy: DesktopCloudAuthOAuthStrategy | null;
42 onJoinWaitlist: () => void;
43 onStartOAuth: (strategy: DesktopCloudAuthOAuthStrategy) => void;
44}) {
45 return (
46 <DesktopClerkCard
47 footerAction={
48 <DesktopClerkFooterAction actionLabel="Join waitlist" onAction={onJoinWaitlist}>
49 Want early access?
50 </DesktopClerkFooterAction>
51 }
52 >
53 <DesktopClerkHeader
54 title="Sign in to T3 Code"
55 subtitle="Welcome back! Please sign in to continue"
56 />
57 {oauthOptions.length === 0 ? (
58 <DesktopClerkAlert>No OAuth providers are enabled for desktop sign-in.</DesktopClerkAlert>
59 ) : (
60 <DesktopClerkSocialButtons
61 isStarting={isStarting}
62 oauthOptions={oauthOptions}
63 startingStrategy={startingStrategy}
64 onStartOAuth={onStartOAuth}
65 />
66 )}
67 </DesktopClerkCard>
68 );
69}
70
71function DesktopClerkSocialButtons({
72 isStarting,
73 oauthOptions,
74 startingStrategy,
75 onStartOAuth,
76}: {
77 isStarting: boolean;
78 oauthOptions: readonly DesktopCloudAuthOAuthOption[];
79 startingStrategy: DesktopCloudAuthOAuthStrategy | null;
80 onStartOAuth: (strategy: DesktopCloudAuthOAuthStrategy) => void;
81}) {
82 const useBlockButtons = oauthOptions.length <= 2;
83
84 return (
85 <div className={cn("grid gap-2", useBlockButtons ? "grid-cols-1" : "grid-cols-3")}>
86 {oauthOptions.map((option) => {
87 const isCurrent = option.strategy === startingStrategy;
88 return (
89 <button
90 key={option.strategy}
91 type="button"
92 aria-label={`Continue with ${option.label}`}
93 className={cn(
94 "flex h-10 cursor-pointer items-center justify-center rounded-md border border-input bg-popover px-3 text-sm font-semibold text-foreground shadow-xs outline-none transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring/20 disabled:pointer-events-none disabled:opacity-64",
95 useBlockButtons && "w-full",
96 )}
97 disabled={isStarting}
98 onClick={() => onStartOAuth(option.strategy)}
99 >
100 <span
101 className={cn(
102 "flex min-w-0 items-center justify-center gap-3",
103 useBlockButtons && "w-full",
104 )}
105 >
106 {isCurrent ? (
107 <LoaderCircleIcon className="size-4 animate-spin opacity-70" />
108 ) : (
109 <DesktopClerkProviderIcon option={option} />
110 )}
111 {useBlockButtons ? (
112 <span className="truncate">
113 {oauthOptions.length === 1 ? `Continue with ${option.label}` : option.label}
114 </span>
115 ) : null}
116 </span>
117 </button>
118 );
119 })}
120 </div>
121 );
122}
123
124function DesktopClerkProviderIcon({ option }: { option: DesktopCloudAuthOAuthOption }) {
125 if (!option.iconUrl) {
126 return (
127 <span
128 aria-hidden
129 className="flex size-4 shrink-0 items-center justify-center rounded bg-muted text-[9px] font-bold text-muted-foreground"
130 >
131 {option.label.slice(0, 1).toUpperCase()}
132 </span>
133 );
134 }
135
136 if (["apple", "github", "vercel"].includes(option.providerId)) {
137 return (
138 <span
139 aria-hidden
140 className="size-4 shrink-0 bg-foreground"
141 style={{
142 WebkitMask: `url(${option.iconUrl}) center / cover no-repeat`,
143 mask: `url(${option.iconUrl}) center / cover no-repeat`,
144 }}
145 />
146 );
147 }
148
149 return <img alt="" aria-hidden className="size-4 shrink-0" src={option.iconUrl} />;
150}
apps/web/src/components/clerk/DesktopClerkWaitlist.tsx deleted-106
......@@ -1,106 +0,0 @@
1import { useClerk } from "@clerk/react";
2import { useState } from "react";
3
4import {
5 DesktopClerkAlert,
6 DesktopClerkCard,
7 DesktopClerkFooterAction,
8 DesktopClerkHeader,
9 DesktopClerkInput,
10 DesktopClerkPrimaryButton,
11} from "./DesktopClerkCard";
12import { DesktopClerkSignIn } from "./DesktopClerkSignIn";
13
14type DesktopClerkScreen = "waitlist" | "sign-in";
15
16// Mirrors Clerk's waitlist card and form, replacing its router transition with the desktop sign-in flow:
17// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/components/Waitlist/index.tsx
18// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/ui/src/components/Waitlist/WaitlistForm.tsx
19export function DesktopClerkWaitlist() {
20 const [screen, setScreen] = useState<DesktopClerkScreen>("waitlist");
21
22 if (screen === "sign-in") {
23 return <DesktopClerkSignIn onJoinWaitlist={() => setScreen("waitlist")} />;
24 }
25
26 return <DesktopClerkWaitlistForm onSignIn={() => setScreen("sign-in")} />;
27}
28
29function DesktopClerkWaitlistForm({ onSignIn }: { onSignIn: () => void }) {
30 const clerk = useClerk();
31 const [emailAddress, setEmailAddress] = useState("");
32 const [error, setError] = useState<string | null>(null);
33 const [isSubmitting, setIsSubmitting] = useState(false);
34 const [didJoin, setDidJoin] = useState(false);
35
36 const submitWaitlist = async (event: React.FormEvent<HTMLFormElement>) => {
37 event.preventDefault();
38 setError(null);
39 setIsSubmitting(true);
40 try {
41 await clerk.joinWaitlist({ emailAddress });
42 setDidJoin(true);
43 } catch (cause) {
44 setError(getClerkErrorMessage(cause));
45 } finally {
46 setIsSubmitting(false);
47 }
48 };
49
50 if (didJoin) {
51 return (
52 <DesktopClerkCard>
53 <DesktopClerkHeader
54 title="Thanks for joining the waitlist!"
55 subtitle="We’ll be in touch when your spot is ready"
56 />
57 </DesktopClerkCard>
58 );
59 }
60
61 return (
62 <DesktopClerkCard
63 footerAction={
64 <DesktopClerkFooterAction actionLabel="Sign in" onAction={onSignIn}>
65 Already have access?
66 </DesktopClerkFooterAction>
67 }
68 >
69 <DesktopClerkHeader
70 title="Join the waitlist"
71 subtitle="Enter your email address and we’ll let you know when your spot is ready"
72 />
73 <DesktopClerkAlert>{error}</DesktopClerkAlert>
74 <form className="space-y-8 text-left" onSubmit={submitWaitlist}>
75 <label className="block space-y-2" htmlFor="desktop-clerk-waitlist-email">
76 <span className="text-sm font-semibold text-foreground">Email address</span>
77 <DesktopClerkInput
78 required
79 autoComplete="email"
80 id="desktop-clerk-waitlist-email"
81 name="emailAddress"
82 placeholder="Enter your email address"
83 type="email"
84 value={emailAddress}
85 onChange={(event) => setEmailAddress(event.currentTarget.value)}
86 />
87 </label>
88 <DesktopClerkPrimaryButton disabled={isSubmitting}>
89 {isSubmitting ? "Joining the waitlist…" : "Join the waitlist"}
90 </DesktopClerkPrimaryButton>
91 </form>
92 </DesktopClerkCard>
93 );
94}
95
96function getClerkErrorMessage(error: unknown): string {
97 if (typeof error === "object" && error !== null && "errors" in error) {
98 const errors = (error as { errors?: Array<{ longMessage?: unknown; message?: unknown }> })
99 .errors;
100 const firstError = errors?.[0];
101 if (typeof firstError?.longMessage === "string") return firstError.longMessage;
102 if (typeof firstError?.message === "string") return firstError.message;
103 }
104 if (error instanceof Error && error.message) return error.message;
105 return "Could not join the waitlist. Please try again.";
106}
apps/web/src/components/clerk/useDesktopClerkSignIn.ts deleted-199
......@@ -1,199 +0,0 @@
1import { useClerk } from "@clerk/react";
2import { useSignIn, useSignUp } from "@clerk/react/legacy";
3import { useCallback, useEffect, useRef, useState } from "react";
4
5import {
6 type DesktopCloudAuthOAuthStrategy,
7 resolveDesktopCloudAuthOAuthOptions,
8} from "../../cloud/desktopAuth";
9import { toastManager } from "../ui/toast";
10
11// Mirrors Clerk Expo's browser-based native SSO flow, with Electron handling the external browser
12// and callback transport:
13// https://github.com/clerk/javascript/blob/52861184477bee99c71552000311a289e91d3b59/packages/expo/src/hooks/useSSO.ts
14class DesktopClerkOperationError extends Error {
15 override readonly cause?: unknown;
16
17 constructor(message: string, cause?: unknown) {
18 super(message);
19 this.name = "DesktopClerkOperationError";
20 this.cause = cause;
21 }
22}
23
24async function runDesktopClerkOperation<T>(
25 operation: () => Promise<T>,
26 message: string,
27): Promise<T> {
28 try {
29 return await operation();
30 } catch (cause) {
31 throw new DesktopClerkOperationError(message, cause);
32 }
33}
34
35function desktopClerkErrorMessage(error: unknown, fallback: string): string {
36 if (error instanceof DesktopClerkOperationError) {
37 const cause = error.cause;
38 if (cause instanceof Error && cause.message && cause.message !== error.message) {
39 return `${error.message}: ${cause.message}`;
40 }
41 return error.message;
42 }
43 return error instanceof Error ? error.message : fallback;
44}
45
46export function useDesktopClerkSignIn() {
47 const clerk = useClerk();
48 const { setActive } = clerk;
49 const { isLoaded: signInLoaded, signIn } = useSignIn();
50 const { isLoaded: signUpLoaded, signUp } = useSignUp();
51 const [startingStrategy, setStartingStrategy] = useState<DesktopCloudAuthOAuthStrategy | null>(
52 null,
53 );
54 const oauthOptions = resolveDesktopCloudAuthOAuthOptions(clerk);
55 const callbackCleanupRef = useRef<(() => void) | null>(null);
56
57 const clearCallbackListener = useCallback(() => {
58 callbackCleanupRef.current?.();
59 callbackCleanupRef.current = null;
60 }, []);
61
62 const completeOAuthCallback = useCallback(
63 async (rawUrl: string) => {
64 if (!signInLoaded || !signIn || !signUpLoaded || !signUp) {
65 toastManager.add({
66 type: "error",
67 title: "Cloud sign-in failed",
68 description: "Clerk is still loading. Try signing in again.",
69 });
70 return;
71 }
72
73 let rotatingTokenNonce: string | null = null;
74 let sessionId: string | null = null;
75 try {
76 const callbackUrl = new URL(rawUrl);
77 rotatingTokenNonce = callbackUrl.searchParams.get("rotating_token_nonce");
78 sessionId = callbackUrl.searchParams.get("created_session_id");
79 } catch {
80 // Handled by the explicit nonce check below.
81 }
82 if (!rotatingTokenNonce) {
83 toastManager.add({
84 type: "error",
85 title: "Cloud sign-in failed",
86 description:
87 "Clerk did not return a native session nonce. Verify this redirect URL is allowlisted for native SSO redirects.",
88 });
89 return;
90 }
91
92 try {
93 await runDesktopClerkOperation(
94 () => signIn.reload({ rotatingTokenNonce }),
95 "Could not reload the desktop sign-in session.",
96 );
97 sessionId = sessionId || signIn.createdSessionId;
98
99 if (!sessionId && signIn.firstFactorVerification.status === "transferable") {
100 const signUpAttempt = await runDesktopClerkOperation(
101 () => signUp.create({ transfer: true }),
102 "Could not transfer the desktop sign-up session.",
103 );
104 sessionId = signUpAttempt.createdSessionId;
105 }
106
107 if (!sessionId) {
108 throw new DesktopClerkOperationError("Clerk did not create a desktop session.");
109 }
110
111 await runDesktopClerkOperation(
112 () => setActive({ session: sessionId! }),
113 "Could not activate the desktop cloud session.",
114 );
115 } catch (error) {
116 toastManager.add({
117 type: "error",
118 title: "Cloud sign-in failed",
119 description: desktopClerkErrorMessage(error, "Could not complete cloud sign-in."),
120 });
121 }
122 },
123 [setActive, signIn, signInLoaded, signUp, signUpLoaded],
124 );
125
126 useEffect(() => {
127 return () => {
128 clearCallbackListener();
129 };
130 }, [clearCallbackListener]);
131
132 const startOAuth = useCallback(
133 async (strategy: DesktopCloudAuthOAuthStrategy) => {
134 if (!signInLoaded || !signIn) {
135 toastManager.add({
136 type: "error",
137 title: "Cloud sign-in failed",
138 description: "Clerk is still loading. Try signing in again.",
139 });
140 return;
141 }
142
143 setStartingStrategy(strategy);
144 clearCallbackListener();
145 try {
146 const redirectUrl = await runDesktopClerkOperation(
147 () => window.desktopBridge?.createCloudAuthRequest() ?? Promise.resolve(undefined),
148 "Desktop auth callback is unavailable.",
149 );
150 if (!redirectUrl) {
151 throw new DesktopClerkOperationError("Desktop auth callback is unavailable.");
152 }
153
154 callbackCleanupRef.current =
155 window.desktopBridge?.onCloudAuthCallback((rawUrl) => {
156 clearCallbackListener();
157 void completeOAuthCallback(rawUrl);
158 }) ?? null;
159
160 await runDesktopClerkOperation(
161 () => signIn.create({ strategy, redirectUrl } as never),
162 "Could not create the desktop OAuth request.",
163 );
164 const externalUrl =
165 signIn.firstFactorVerification.externalVerificationRedirectURL?.toString();
166 if (!externalUrl) {
167 throw new DesktopClerkOperationError(
168 "Clerk did not return an external OAuth redirect URL.",
169 );
170 }
171
172 const opened = await runDesktopClerkOperation(
173 () => window.desktopBridge?.openExternal(externalUrl) ?? Promise.resolve(false),
174 "Could not open the system browser.",
175 );
176 if (!opened) {
177 throw new DesktopClerkOperationError("Could not open the system browser.");
178 }
179 } catch (error) {
180 clearCallbackListener();
181 toastManager.add({
182 type: "error",
183 title: "Cloud sign-in failed",
184 description: desktopClerkErrorMessage(error, "Could not start cloud sign-in."),
185 });
186 } finally {
187 setStartingStrategy(null);
188 }
189 },
190 [clearCallbackListener, completeOAuthCallback, signIn, signInLoaded],
191 );
192
193 return {
194 isStarting: startingStrategy !== null,
195 oauthOptions,
196 startingStrategy,
197 startOAuth,
198 };
199}
apps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx+1-21
......@@ -1,29 +1,9 @@
11import { useClerk } from "@clerk/react";
2import { useState } from "react";
3
4import { isElectron } from "../../env";
5import { Dialog, DialogPopup } from "../ui/dialog";
6import { DesktopClerkWaitlist } from "./DesktopClerkWaitlist";
72
83export function useT3ConnectAuthPrompt() {
94 const clerk = useClerk();
10 const [desktopAuthOpen, setDesktopAuthOpen] = useState(false);
11
125 const openAuthPrompt = () => {
13 if (isElectron) {
14 setDesktopAuthOpen(true);
15 return;
16 }
176 clerk.openWaitlist();
187 };
19
20 const authPrompt = isElectron ? (
21 <Dialog open={desktopAuthOpen} onOpenChange={setDesktopAuthOpen}>
22 <DialogPopup className="max-w-md border-0 bg-transparent shadow-none" showCloseButton={false}>
23 <DesktopClerkWaitlist />
24 </DialogPopup>
25 </Dialog>
26 ) : null;
27
28 return { authPrompt, openAuthPrompt };
8 return { authPrompt: null, openAuthPrompt };
299}
apps/web/src/connection/platform.ts+31-30
......@@ -3,6 +3,7 @@ import {
33 CloudSession,
44 EnvironmentOwnedDataCleanup,
55 PlatformConnectionSource,
6 PrimaryEnvironmentAuth,
67 RelayDeviceIdentity,
78 SshEnvironmentGateway,
89} from "@t3tools/client-runtime/platform";
......@@ -30,9 +31,9 @@ import * as Option from "effect/Option";
3031import * as Queue from "effect/Queue";
3132import * as Schedule from "effect/Schedule";
3233import * as Stream from "effect/Stream";
33import { FetchHttpClient, HttpClient } from "effect/unstable/http";
3434
35import { primaryEnvironmentRequestInit } from "../environments/primary/requestInit";
35import { readDesktopPrimaryBearerToken } from "../environments/primary/desktopAuth";
36import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
3637import { readPrimaryEnvironmentTarget } from "../environments/primary/target";
3738import { clearComposerDraftsEnvironment } from "../composerDraftStore";
3839import { isHostedStaticApp } from "../hostedPairing";
......@@ -193,6 +194,16 @@ const capabilitiesLayer = Layer.effectContext(
193194 const identity = RelayDeviceIdentity.of({
194195 deviceId: Effect.succeed(Option.none()),
195196 });
197 const primaryAuth = PrimaryEnvironmentAuth.of({
198 bearerToken: Effect.tryPromise({
199 try: readDesktopPrimaryBearerToken,
200 catch: (cause) =>
201 new ConnectionTransientError({
202 reason: "remote-unavailable",
203 message: `Could not load the desktop primary credential: ${String(cause)}`,
204 }),
205 }).pipe(Effect.map(Option.fromNullishOr)),
206 });
196207 const ssh = SshEnvironmentGateway.of({
197208 provision: Effect.fn("web.connectionPlatform.ssh.provision")(function* (target) {
198209 const bridge = window.desktopBridge;
......@@ -252,6 +263,7 @@ const capabilitiesLayer = Layer.effectContext(
252263 });
253264
254265 return Context.make(CloudSession, cloudSession).pipe(
266 Context.add(PrimaryEnvironmentAuth, primaryAuth),
255267 Context.add(RelayDeviceIdentity, identity),
256268 Context.add(ClientPresentation, presentation),
257269 Context.add(SshEnvironmentGateway, ssh),
......@@ -271,10 +283,7 @@ const loadPrimaryConnectionRegistration = Effect.fn(
271283 }
272284 const descriptor = yield* fetchRemoteEnvironmentDescriptor({
273285 httpBaseUrl: resolved.target.httpBaseUrl,
274 }).pipe(
275 Effect.provideService(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit),
276 Effect.mapError(mapRemoteEnvironmentError),
277 );
286 }).pipe(Effect.provide(primaryEnvironmentHttpLayer), Effect.mapError(mapRemoteEnvironmentError));
278287 return new PrimaryConnectionRegistration({
279288 target: new PrimaryConnectionTarget({
280289 environmentId: descriptor.environmentId,
......@@ -289,32 +298,24 @@ const primaryRegistrationRetrySchedule = Schedule.exponential("1 second").pipe(
289298 Schedule.either(Schedule.spaced("16 seconds")),
290299);
291300
292const platformConnectionSourceLayer = Layer.effect(
293 PlatformConnectionSource,
294 Effect.gen(function* () {
295 if (isHostedStaticApp()) {
296 return PlatformConnectionSource.of({
297 registrations: Stream.empty,
298 });
299 }
300 const httpClient = yield* HttpClient.HttpClient;
301const platformConnectionSourceLayer = Layer.sync(PlatformConnectionSource, () => {
302 if (isHostedStaticApp()) {
301303 return PlatformConnectionSource.of({
302 registrations: Stream.fromEffect(
303 loadPrimaryConnectionRegistration().pipe(
304 Effect.provideService(HttpClient.HttpClient, httpClient),
305 ),
306 ).pipe(
307 Stream.tapError((error) =>
308 Effect.logWarning("Could not discover the primary environment.", {
309 error,
310 }),
311 ),
312 Stream.retry(primaryRegistrationRetrySchedule),
313 Stream.catchCause(() => Stream.empty),
314 ),
304 registrations: Stream.empty,
315305 });
316 }),
317);
306 }
307 return PlatformConnectionSource.of({
308 registrations: Stream.fromEffect(loadPrimaryConnectionRegistration()).pipe(
309 Stream.tapError((error) =>
310 Effect.logWarning("Could not discover the primary environment.", {
311 error,
312 }),
313 ),
314 Stream.retry(primaryRegistrationRetrySchedule),
315 Stream.catchCause(() => Stream.empty),
316 ),
317 });
318});
318319
319320const environmentOwnedDataCleanupLayer = Layer.succeed(
320321 EnvironmentOwnedDataCleanup,
apps/web/src/environments/primary/desktopAuth.test.ts created+33
......@@ -0,0 +1,33 @@
1import type { DesktopBridge } from "@t3tools/contracts";
2import { afterEach, beforeEach, describe, expect, it, vi } from "@effect/vitest";
3
4import { __resetDesktopPrimaryAuthForTests, readDesktopPrimaryBearerToken } from "./desktopAuth";
5
6describe("desktop primary auth", () => {
7 beforeEach(() => {
8 Object.defineProperty(globalThis, "window", {
9 configurable: true,
10 value: {},
11 });
12 });
13
14 afterEach(() => {
15 __resetDesktopPrimaryAuthForTests();
16 Reflect.deleteProperty(globalThis, "window");
17 });
18
19 it("reuses the main-process bearer token across renderer requests", async () => {
20 const getLocalEnvironmentBearerToken = vi.fn().mockResolvedValue("desktop-bearer-token");
21 window.desktopBridge = {
22 getLocalEnvironmentBearerToken,
23 } as unknown as DesktopBridge;
24
25 await expect(readDesktopPrimaryBearerToken()).resolves.toBe("desktop-bearer-token");
26 await expect(readDesktopPrimaryBearerToken()).resolves.toBe("desktop-bearer-token");
27 expect(getLocalEnvironmentBearerToken).toHaveBeenCalledTimes(1);
28 });
29
30 it("does not require desktop auth in a browser", async () => {
31 await expect(readDesktopPrimaryBearerToken()).resolves.toBeNull();
32 });
33});
apps/web/src/environments/primary/desktopAuth.ts created+21
......@@ -0,0 +1,21 @@
1let desktopBearerTokenPromise: Promise<string> | null = null;
2
3export function readDesktopPrimaryBearerToken(): Promise<string | null> {
4 if (typeof window === "undefined") {
5 return Promise.resolve(null);
6 }
7 const bridge = window.desktopBridge;
8 if (!bridge) {
9 return Promise.resolve(null);
10 }
11
12 desktopBearerTokenPromise ??= bridge.getLocalEnvironmentBearerToken().catch((error) => {
13 desktopBearerTokenPromise = null;
14 throw error;
15 });
16 return desktopBearerTokenPromise;
17}
18
19export function __resetDesktopPrimaryAuthForTests(): void {
20 desktopBearerTokenPromise = null;
21}
apps/web/src/environments/primary/httpLayer.test.ts created+65
......@@ -0,0 +1,65 @@
1import type { DesktopBridge } from "@t3tools/contracts";
2import { afterEach, describe, expect, it, vi } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import { HttpClient } from "effect/unstable/http";
5
6import { __resetDesktopPrimaryAuthForTests } from "./desktopAuth";
7import { makePrimaryEnvironmentHttpLayer } from "./httpLayer";
8
9describe.sequential("primary environment HTTP layer", () => {
10 afterEach(() => {
11 __resetDesktopPrimaryAuthForTests();
12 Reflect.deleteProperty(globalThis, "window");
13 vi.unstubAllGlobals();
14 });
15
16 it.effect("uses cookie credentials for browser primary environments", () => {
17 const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
18 vi.stubGlobal("fetch", fetchMock);
19 Object.defineProperty(globalThis, "window", {
20 configurable: true,
21 value: {
22 location: {
23 href: "http://127.0.0.1:3773/settings",
24 origin: "http://127.0.0.1:3773",
25 },
26 },
27 });
28
29 return Effect.gen(function* () {
30 yield* HttpClient.get("http://127.0.0.1:3773/api/auth/session");
31
32 const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]);
33 expect(request.credentials).toBe("include");
34 expect(request.headers.get("authorization")).toBeNull();
35 }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer()));
36 });
37
38 it.effect("uses bearer auth without cookies for desktop-managed primaries", () => {
39 const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
40 vi.stubGlobal("fetch", fetchMock);
41 Object.defineProperty(globalThis, "window", {
42 configurable: true,
43 value: {
44 location: { origin: "t3code://app" },
45 desktopBridge: {
46 getLocalEnvironmentBootstrap: () => ({
47 label: "Local environment",
48 httpBaseUrl: "http://127.0.0.1:3773",
49 wsBaseUrl: "ws://127.0.0.1:3773",
50 bootstrapToken: "desktop-bootstrap-token",
51 }),
52 getLocalEnvironmentBearerToken: vi.fn().mockResolvedValue("desktop-bearer-token"),
53 } as unknown as DesktopBridge,
54 },
55 });
56
57 return Effect.gen(function* () {
58 yield* HttpClient.get("http://127.0.0.1:3773/api/connect/link-state");
59
60 const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]);
61 expect(request.credentials).not.toBe("include");
62 expect(request.headers.get("authorization")).toBe("Bearer desktop-bearer-token");
63 }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer()));
64 });
65});
apps/web/src/environments/primary/httpLayer.ts created+58
......@@ -0,0 +1,58 @@
1import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
5
6import { readDesktopPrimaryBearerToken } from "./desktopAuth";
7import { resolvePrimaryEnvironmentHttpUrl } from "./target";
8
9function isSameOriginBrowserPrimary(): boolean {
10 if (
11 typeof window === "undefined" ||
12 window.desktopBridge !== undefined ||
13 window.nativeApi !== undefined ||
14 !window.location.origin.startsWith("http")
15 ) {
16 return false;
17 }
18
19 return new URL(resolvePrimaryEnvironmentHttpUrl("/")).origin === window.location.origin;
20}
21
22function withPrimaryBearerToken(client: HttpClient.HttpClient): HttpClient.HttpClient {
23 return client.pipe(
24 HttpClient.mapRequestEffect((request) =>
25 Effect.promise(readDesktopPrimaryBearerToken).pipe(
26 Effect.map((bearerToken) =>
27 bearerToken ? HttpClientRequest.bearerToken(request, bearerToken) : request,
28 ),
29 ),
30 ),
31 );
32}
33
34export function makePrimaryEnvironmentHttpLayer() {
35 return Layer.unwrap(
36 Effect.sync(() => {
37 const baseLayer = remoteHttpClientLayer(globalThis.fetch);
38 if (isSameOriginBrowserPrimary()) {
39 return Layer.merge(
40 baseLayer,
41 Layer.succeed(FetchHttpClient.RequestInit, { credentials: "include" }),
42 );
43 }
44
45 const bearerClientLayer = Layer.effect(
46 HttpClient.HttpClient,
47 Effect.map(HttpClient.HttpClient, withPrimaryBearerToken),
48 ).pipe(Layer.provide(baseLayer));
49
50 return Layer.merge(
51 bearerClientLayer,
52 Layer.succeed(FetchHttpClient.RequestInit, { credentials: "omit" }),
53 );
54 }),
55 );
56}
57
58export const primaryEnvironmentHttpLayer = makePrimaryEnvironmentHttpLayer();
apps/web/src/environments/primary/index.ts+2
......@@ -33,6 +33,8 @@ export {
3333
3434export { refreshPrimarySessionState, usePrimarySessionState } from "./sessionState";
3535
36export { PrimaryEnvironmentHttpClient } from "./httpClient";
37
3638export {
3739 readPrimaryEnvironmentTarget,
3840 resolvePrimaryEnvironmentHttpUrl,
apps/web/src/environments/primary/requestInit.ts deleted-7
......@@ -1,7 +0,0 @@
1import * as Effect from "effect/Effect";
2import { FetchHttpClient } from "effect/unstable/http";
3
4export const primaryEnvironmentRequestInit = { credentials: "include" } as const;
5
6export const withPrimaryEnvironmentRequestInit = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
7 effect.pipe(Effect.provideService(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit));
apps/web/src/lib/runtime.ts+2-12
......@@ -1,17 +1,15 @@
11import * as ManagedRuntime from "effect/ManagedRuntime";
22import type * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
4import { FetchHttpClient } from "effect/unstable/http";
54import * as Socket from "effect/unstable/socket/Socket";
65
76import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
8import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability";
97import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
108import {
119 PrimaryEnvironmentHttpClient,
1210 primaryEnvironmentHttpClientLive,
1311} from "../environments/primary/httpClient";
14import { primaryEnvironmentRequestInit } from "../environments/primary/requestInit";
12import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
1513
1614import { browserCryptoLayer } from "../cloud/dpop";
1715import { managedRelayClientLayer } from "../cloud/managedRelayLayer";
......@@ -32,15 +30,7 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(
3230export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer);
3331
3432const primaryHttpRuntime = ManagedRuntime.make(
35 primaryEnvironmentHttpClientLive.pipe(
36 Layer.provide(
37 Layer.mergeAll(
38 remoteHttpClientLayer((input, init) => globalThis.fetch(input, init)),
39 Layer.succeed(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit),
40 httpHeaderRedactionLayer,
41 ),
42 ),
43 ),
33 primaryEnvironmentHttpClientLive.pipe(Layer.provide(primaryEnvironmentHttpLayer)),
4434);
4535
4636export type PrimaryHttpEffectRunner = <A, E>(
apps/web/src/main.tsx+4-3
......@@ -1,6 +1,8 @@
11import React from "react";
22import ReactDOM from "react-dom/client";
33import { ClerkProvider } from "@clerk/react";
4import { passkeys } from "@clerk/electron/passkeys";
5import { ClerkProvider as ElectronClerkProvider } from "@clerk/electron/react";
46import { createHashHistory, createBrowserHistory } from "@tanstack/react-router";
57
68import "@fontsource-variable/dm-sans/index.css";
......@@ -10,7 +12,6 @@ import "@xterm/xterm/css/xterm.css";
1012import "./index.css";
1113
1214import { isElectron } from "./env";
13import { DesktopClerkProvider } from "./cloud/desktopClerk";
1415import { ManagedRelayAuthProvider } from "./cloud/managedAuth";
1516import { hasCloudPublicConfig } from "./cloud/publicConfig";
1617import { getRouter } from "./router";
......@@ -37,9 +38,9 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
3738 <React.StrictMode>
3839 {clerkPublishableKey && hasCloudPublicConfig() ? (
3940 isElectron ? (
40 <DesktopClerkProvider publishableKey={clerkPublishableKey}>
41 <ElectronClerkProvider publishableKey={clerkPublishableKey} passkeys={passkeys}>
4142 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>
42 </DesktopClerkProvider>
43 </ElectronClerkProvider>
4344 ) : (
4445 <ClerkProvider publishableKey={clerkPublishableKey}>
4546 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>
apps/web/src/observability/clientTracing.ts+3-2
......@@ -3,11 +3,12 @@ import * as Layer from "effect/Layer";
33import * as ManagedRuntime from "effect/ManagedRuntime";
44import * as Scope from "effect/Scope";
55import * as Tracer from "effect/Tracer";
6import { FetchHttpClient, HttpClient } from "effect/unstable/http";
6import { HttpClient } from "effect/unstable/http";
77import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
88
99import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
1010import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary";
11import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
1112import { isElectron } from "../env";
1213import { APP_VERSION } from "~/branding";
1314
......@@ -22,7 +23,7 @@ const CLIENT_TRACING_RESOURCE = {
2223} as const;
2324
2425const delegateRuntimeLayer = Layer.mergeAll(
25 FetchHttpClient.layer,
26 primaryEnvironmentHttpLayer,
2627 OtlpSerialization.layerJson,
2728 Layer.succeed(HttpClient.TracerDisabledWhen, () => true),
2829);
apps/web/vite.config.ts+1
......@@ -126,6 +126,7 @@ export default defineConfig(() => {
126126 },
127127 resolve: {
128128 tsconfigPaths: true,
129 dedupe: ["react", "react-dom"],
129130 },
130131 server: {
131132 host,
docs/cloud/t3-connect-clerk.md+76-10
......@@ -120,20 +120,86 @@ selects the concrete relay deployment, but changing that URL does not require a
120120## Desktop OAuth Redirect Allowlist
121121
122122The desktop app opens OAuth in the system browser and returns to the app with a custom URL scheme.
123In **Clerk Dashboard > Native applications**, enable native application support and add these
124entries under the mobile SSO redirect allowlist:
123In **Clerk Dashboard > Native applications**, enable the Native API and add these entries under the
124mobile SSO redirect allowlist:
125125
126126```text
127t3code-dev://auth/callback
128t3code://auth/callback
127t3code-dev://app/
128t3code://app/
129129```
130130
131The first entry is for local desktop development. The second is for packaged desktop builds.
132The app also adds a request-scoped `t3_state` query parameter and validates it on callback. Initial
133sign-in and linked-account OAuth flows both return through this bridge. The desktop provider keeps
134Clerk's stock profile component, replaces its renderer-page callback with the custom-scheme callback,
135and opens the provider URL in the system browser. Do not add the local renderer URL as an OAuth
136redirect: an external browser cannot use it to reopen the packaged app.
131Local desktop development uses `t3code-dev://app`, while packaged builds use `t3code://app`. Add the
132matching origin to each Clerk instance's Backend API `allowed_origins` array as well. The development
133Clerk instance should only need `t3code-dev://app`; the production Clerk instance should only need
134`t3code://app`. `@clerk/electron` owns the native request adapter, encrypted Clerk token persistence,
135external-browser OAuth transport, and callback delivery for initial sign-in and linked-account flows.
136
137There is currently no Dashboard UI for `allowed_origins`. Preserve any existing entries and update
138the instance through the Backend API:
139
140```sh
141curl -X PATCH https://api.clerk.com/v1/instance \
142 -H "Content-Type: application/json" \
143 -H "Authorization: Bearer $CLERK_SECRET_KEY" \
144 -d '{"allowed_origins":["t3code://app"]}'
145```
146
147Never put `CLERK_SECRET_KEY` in the desktop app, a client-facing environment file, or a build
148artifact.
149
150## Desktop Passkeys
151
152The production macOS bundle ID is `com.t3tools.t3code`. To enable native passkeys:
153
1541. Create an explicit macOS App ID for `com.t3tools.t3code` in the Apple Developer portal and enable
155 **Associated Domains**.
1562. Create a compatible macOS provisioning profile for that App ID and the certificate used to sign
157 the distributed app.
1583. In Clerk's Native API settings, add an iOS app with the same Apple Team ID and bundle ID. This is
159 also the configuration point for Electron/macOS passkeys.
1604. Confirm Clerk serves `https://<frontend-api>/.well-known/apple-app-site-association` and that
161 `webcredentials.apps` contains `<TEAM_ID>.com.t3tools.t3code`.
1625. Set the local or CI signing configuration described below.
163
164For a local signed build, add these values to `.env.local` or export them before invoking the
165desktop artifact command:
166
167```dotenv
168T3CODE_APPLE_TEAM_ID=ABC1234567
169T3CODE_MACOS_PROVISIONING_PROFILE=/absolute/path/to/t3code.provisionprofile
170# Optional: comma-separated override when Clerk's RP ID differs from the Frontend API hostname.
171T3CODE_CLERK_PASSKEY_RP_DOMAINS=example.clerk.accounts.dev,clerk.example.com
172```
173
174When `T3CODE_CLERK_PASSKEY_RP_DOMAINS` is absent, the build derives the RP domain from
175`T3CODE_CLERK_PUBLISHABLE_KEY`. Signed macOS builds fail early if the Team ID, provisioning profile,
176or RP-domain configuration is missing. The generated main-app entitlements include every configured
177`webcredentials:<domain>` entry; helper apps keep Electron's minimal default entitlements.
178
179The normal `dev:desktop` launcher is unsigned and cannot complete macOS passkey ceremonies. For
180renderer HMR, build and install a signed app first, run the renderer dev server, then launch the
181installed app executable with `VITE_DEV_SERVER_URL` and `T3CODE_PORT` set. Rebuild the signed app
182after native dependency, main-process, preload, entitlement, provisioning, or signing changes;
183renderer-only changes can reuse the installed app.
184
185For the default development ports, run `pnpm dev:web` in one terminal and launch the installed
186binary from another:
187
188```sh
189VITE_DEV_SERVER_URL=http://127.0.0.1:5733 \
190T3CODE_PORT=13773 \
191 "/Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)"
192```
193
194After changing Associated Domains, bump the build version before rebuilding; macOS may otherwise
195reuse stale Shared Web Credentials metadata for the same app/version pair.
196
197Verify the installed bundle before testing:
198
199```sh
200codesign --verify --deep --strict "/Applications/T3 Code (Alpha).app"
201codesign -d --entitlements :- "/Applications/T3 Code (Alpha).app"
202```
137203
138204The current mobile UI uses Clerk's native authentication view. If a future mobile browser OAuth
139205flow uses a custom redirect URI, add that exact URI to the same allowlist.
docs/operations/ci.md+1-1
......@@ -2,5 +2,5 @@
22
33- `.github/workflows/ci.yml` runs `bun run lint`, `bun run typecheck`, and `bun run test` on pull requests and pushes to `main`.
44- `.github/workflows/release.yml` builds macOS (`arm64` and `x64`), Linux (`x64`), and Windows (`x64`) desktop artifacts from a single `v*.*.*` tag and publishes one GitHub release.
5- The release workflow auto-enables signing only when secrets are present: Apple credentials for macOS and Azure Trusted Signing credentials for Windows. Without secrets, it still releases unsigned artifacts.
5- The release workflow auto-enables signing only when platform credentials are present. macOS passkey builds additionally require `APPLE_TEAM_ID` and the `MACOS_PROVISIONING_PROFILE` secret; Windows uses Azure Trusted Signing. Without the core signing credentials, it still releases unsigned artifacts.
66- See [Release Checklist](./release.md) for the full release/signing setup checklist.
docs/operations/release.md+28-8
......@@ -219,26 +219,44 @@ Required secrets used by the workflow:
219219- `APPLE_API_KEY`
220220- `APPLE_API_KEY_ID`
221221- `APPLE_API_ISSUER`
222- `MACOS_PROVISIONING_PROFILE` (base64-encoded provisioning profile with Associated Domains)
223
224Required repository variables:
225
226- `APPLE_TEAM_ID`
227
228Optional repository variables:
229
230- `CLERK_PASSKEY_RP_DOMAINS`: comma-separated RP-domain override. By default, the build derives the
231 domain from the production Clerk publishable key.
222232
223233Checklist:
224234
2252351. Apple Developer account access:
226236 - Team has rights to create Developer ID certificates.
2272. Create `Developer ID Application` certificate.
2283. Export certificate + private key as `.p12` from Keychain.
2294. Base64-encode the `.p12` and store as `CSC_LINK`.
2305. Store the `.p12` export password as `CSC_KEY_PASSWORD`.
2316. In App Store Connect, create an API key (Team key).
2327. Add API key values:
2372. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains.
2383. Create a `Developer ID Application` certificate and a compatible provisioning profile for that
239 App ID with Associated Domains enabled.
2404. Export the certificate + private key as `.p12` from Keychain.
2415. Base64-encode the `.p12` and store as `CSC_LINK`.
2426. Base64-encode the provisioning profile and store it as `MACOS_PROVISIONING_PROFILE`.
2437. Store the `.p12` export password as `CSC_KEY_PASSWORD`, and set `APPLE_TEAM_ID` to the
244 10-character Apple Developer Team ID.
2458. In App Store Connect, create an API key (Team key).
2469. Add API key values:
233247 - `APPLE_API_KEY`: contents of the downloaded `.p8`
234248 - `APPLE_API_KEY_ID`: Key ID
235249 - `APPLE_API_ISSUER`: Issuer ID
2368. Re-run a tag release and confirm macOS artifacts are signed/notarized.
25010. Complete the Clerk Native API and AASA setup in [T3 Connect Clerk Setup](../cloud/t3-connect-clerk.md#desktop-passkeys).
25111. Re-run a tag release and confirm macOS artifacts are signed/notarized and contain the expected
252 `com.apple.developer.associated-domains` entitlement.
237253
238254Notes:
239255
240256- `APPLE_API_KEY` is stored as raw key text in secrets.
241257- The workflow writes it to a temporary `AuthKey_<id>.p8` file at runtime.
258- The workflow decodes `MACOS_PROVISIONING_PROFILE`, validates it with `security cms`, and passes it
259 to the desktop packager.
242260
243261## 3) Azure Trusted Signing setup (Windows)
244262
......@@ -281,7 +299,9 @@ Checklist:
281299## 5) Troubleshooting
282300
283301- macOS build unsigned when expected signed:
284 - Check all Apple secrets are populated and non-empty.
302 - Check all Apple secrets plus `APPLE_TEAM_ID` are populated and non-empty.
303 - Confirm the provisioning profile belongs to `APPLE_TEAM_ID.com.t3tools.t3code` and includes
304 Associated Domains.
285305- Windows build unsigned when expected signed:
286306 - Check all Azure ATS and auth secrets are populated and non-empty.
287307- Build fails with signing error:
docs/reference/scripts.md+4-1
......@@ -20,11 +20,14 @@
2020
2121- Default build is unsigned/not notarized for local sharing.
2222- The DMG build uses `assets/macos-icon-1024.png` as the production app icon source.
23- Desktop production windows load the bundled UI from `t3://app/index.html` (not a `127.0.0.1` document URL).
23- Desktop production windows load the bundled UI from `t3code://app/index.html` (not a `127.0.0.1` document URL).
2424- Desktop packaging includes `apps/server/dist` (the `t3` backend) and starts it on loopback with an auth token for WebSocket/API traffic.
2525- Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch.
2626- To keep staging files for debugging package contents, run: `bun run dist:desktop:dmg -- --keep-stage`
2727- To allow code-signing/notarization when configured in CI/secrets, add: `--signed`.
28- Signed macOS builds also require `T3CODE_APPLE_TEAM_ID` and
29 `T3CODE_MACOS_PROVISIONING_PROFILE`. The passkey RP domain is derived from
30 `T3CODE_CLERK_PUBLISHABLE_KEY` unless `T3CODE_CLERK_PASSKEY_RP_DOMAINS` overrides it.
2831- Windows `--signed` uses Azure Trusted Signing and expects:
2932 `AZURE_TRUSTED_SIGNING_ENDPOINT`, `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME`,
3033 `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME`, and `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME`.
infra/relay/package.json+1-1
......@@ -9,7 +9,7 @@
99 "typecheck": "tsgo --noEmit"
1010 },
1111 "dependencies": {
12 "@clerk/backend": "3.6.1",
12 "@clerk/backend": "catalog:",
1313 "@effect/sql-pg": "catalog:",
1414 "@t3tools/client-runtime": "workspace:*",
1515 "@t3tools/contracts": "workspace:*",
packages/client-runtime/src/connection/resolver.test.ts+44
......@@ -17,6 +17,7 @@ import { ConnectionResolver } from "./resolver.ts";
1717import { connectionResolverLayer } from "./resolver.ts";
1818import {
1919 CloudSession,
20 PrimaryEnvironmentAuth,
2021 RelayDeviceIdentity,
2122 SshEnvironmentGateway,
2223} from "../platform/capabilities.ts";
......@@ -101,6 +102,7 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
101102 readonly connectEnvironment?: ManagedRelayClient["Service"]["connectEnvironment"];
102103 readonly authorizeBearer?: RemoteEnvironmentAuthorization["Service"]["authorizeBearer"];
103104 readonly authorizeDpop?: RemoteEnvironmentAuthorization["Service"]["authorizeDpop"];
105 readonly primaryBearerToken?: string;
104106 readonly prepareSsh?: SshEnvironmentGateway["Service"]["prepare"];
105107}) => {
106108 const profiles = new Map(
......@@ -170,6 +172,12 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
170172 Layer.succeed(ConnectionProfileStore, profileStore),
171173 Layer.succeed(ConnectionCredentialStore, credentialStore),
172174 Layer.succeed(CloudSession, CloudSession.of({ clerkToken: Effect.succeed("clerk-session") })),
175 Layer.succeed(
176 PrimaryEnvironmentAuth,
177 PrimaryEnvironmentAuth.of({
178 bearerToken: Effect.succeed(Option.fromNullishOr(options?.primaryBearerToken)),
179 }),
180 ),
173181 Layer.succeed(
174182 RelayDeviceIdentity,
175183 RelayDeviceIdentity.of({ deviceId: Effect.succeed(Option.some("device-1")) }),
......@@ -217,6 +225,42 @@ describe("ConnectionResolver", () => {
217225 }),
218226 );
219227
228 it.effect("authorizes a desktop primary environment with its platform bearer token", () =>
229 Effect.gen(function* () {
230 const bearerInputs = yield* Ref.make<ReadonlyArray<string>>([]);
231 const brokerLayer = yield* makeDependencies({
232 primaryBearerToken: "desktop-bearer",
233 authorizeBearer: (input) =>
234 Ref.update(bearerInputs, (values) => [...values, input.bearerToken]).pipe(
235 Effect.as({
236 environmentId: input.expectedEnvironmentId,
237 label: "Primary",
238 httpBaseUrl: input.httpBaseUrl,
239 socketUrl: "ws://127.0.0.1:3777/ws?wsTicket=desktop",
240 httpAuthorization: {
241 _tag: "Bearer" as const,
242 token: input.bearerToken,
243 },
244 }),
245 ),
246 });
247 const broker = yield* ConnectionResolver.pipe(Effect.provide(brokerLayer));
248 const target = new PrimaryConnectionTarget({
249 environmentId: ENVIRONMENT_ID,
250 label: "Primary",
251 httpBaseUrl: "http://127.0.0.1:3777",
252 wsBaseUrl: "ws://127.0.0.1:3777",
253 });
254
255 expect(yield* broker.prepare(catalogEntry(target))).toMatchObject({
256 socketUrl: "ws://127.0.0.1:3777/ws?wsTicket=desktop",
257 httpAuthorization: { _tag: "Bearer", token: "desktop-bearer" },
258 target,
259 });
260 expect(yield* Ref.get(bearerInputs)).toEqual(["desktop-bearer"]);
261 }),
262 );
263
220264 it.effect("uses the registered bearer profile without re-reading the profile store", () =>
221265 Effect.gen(function* () {
222266 const bearerInputs = yield* Ref.make<ReadonlyArray<string>>([]);
packages/client-runtime/src/connection/resolver.ts+32-10
......@@ -10,6 +10,7 @@ import { RemoteEnvironmentAuthorization } from "../authorization/service.ts";
1010import { ManagedRelayClient } from "../relay/managedRelay.ts";
1111import {
1212 CloudSession,
13 PrimaryEnvironmentAuth,
1314 RelayDeviceIdentity,
1415 SshEnvironmentGateway,
1516} from "../platform/capabilities.ts";
......@@ -58,17 +59,37 @@ function primarySocketUrl(target: PrimaryConnectionTarget): string {
5859 return url.toString();
5960}
6061
61const primaryBroker = Effect.fn("clientRuntime.connection.broker.primary")(
62 (target: PrimaryConnectionTarget) =>
63 Effect.succeed({
64 environmentId: target.environmentId,
65 label: target.label,
62const makePrimaryBroker = Effect.fn("clientRuntime.connection.broker.makePrimary")(function* () {
63 const auth = yield* PrimaryEnvironmentAuth;
64 const remote = yield* RemoteEnvironmentAuthorization;
65
66 return Effect.fn("clientRuntime.connection.broker.primary")(function* (
67 target: PrimaryConnectionTarget,
68 ) {
69 const bearerToken = yield* auth.bearerToken;
70 if (Option.isNone(bearerToken)) {
71 return {
72 environmentId: target.environmentId,
73 label: target.label,
74 httpBaseUrl: target.httpBaseUrl,
75 socketUrl: primarySocketUrl(target),
76 httpAuthorization: null,
77 target,
78 } satisfies PreparedConnection;
79 }
80
81 const authorized = yield* remote.authorizeBearer({
82 expectedEnvironmentId: target.environmentId,
6683 httpBaseUrl: target.httpBaseUrl,
67 socketUrl: primarySocketUrl(target),
68 httpAuthorization: null,
84 wsBaseUrl: target.wsBaseUrl,
85 bearerToken: bearerToken.value,
86 });
87 return {
88 ...authorized,
6989 target,
70 } satisfies PreparedConnection),
71);
90 } satisfies PreparedConnection;
91 });
92});
7293
7394const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () {
7495 const credentials = yield* ConnectionCredentialStore;
......@@ -228,6 +249,7 @@ const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(funct
228249export const connectionResolverLayer = Layer.effect(
229250 ConnectionResolver,
230251 Effect.gen(function* () {
252 const primary = yield* makePrimaryBroker();
231253 const bearer = yield* makeBearerBroker();
232254 const relay = yield* makeRelayBroker();
233255 const ssh = yield* makeSshBroker();
......@@ -242,7 +264,7 @@ export const connectionResolverLayer = Layer.effect(
242264 });
243265 switch (target._tag) {
244266 case "PrimaryConnectionTarget":
245 return yield* primaryBroker(target);
267 return yield* primary(target);
246268 case "BearerConnectionTarget":
247269 return yield* bearer({ ...entry, target });
248270 case "RelayConnectionTarget":
packages/client-runtime/src/platform/capabilities.ts+7
......@@ -43,6 +43,13 @@ export class ClientPresentation extends Context.Service<
4343 }
4444>()("@t3tools/client-runtime/platform/capabilities/ClientPresentation") {}
4545
46export class PrimaryEnvironmentAuth extends Context.Service<
47 PrimaryEnvironmentAuth,
48 {
49 readonly bearerToken: Effect.Effect<Option.Option<string>, ConnectionAttemptError>;
50 }
51>()("@t3tools/client-runtime/platform/capabilities/PrimaryEnvironmentAuth") {}
52
4653export class SshEnvironmentGateway extends Context.Service<
4754 SshEnvironmentGateway,
4855 {
packages/contracts/src/ipc.ts+1-23
......@@ -415,23 +415,6 @@ export const PickFolderOptionsSchema = Schema.Struct({
415415 initialPath: Schema.optionalKey(Schema.NullOr(Schema.String)),
416416});
417417
418export const DesktopCloudAuthFetchInputSchema = Schema.Struct({
419 url: Schema.String,
420 method: Schema.optionalKey(Schema.String),
421 headers: Schema.Record(Schema.String, Schema.String),
422 body: Schema.optionalKey(Schema.String),
423});
424export type DesktopCloudAuthFetchInput = typeof DesktopCloudAuthFetchInputSchema.Type;
425
426export const DesktopCloudAuthFetchResultSchema = Schema.Struct({
427 ok: Schema.Boolean,
428 status: Schema.Number,
429 statusText: Schema.String,
430 headers: Schema.Record(Schema.String, Schema.String),
431 body: Schema.String,
432});
433export type DesktopCloudAuthFetchResult = typeof DesktopCloudAuthFetchResultSchema.Type;
434
435418/**
436419 * Renderer-facing snapshot of a desktop preview tab. Mirrors the main-process
437420 * PreviewTabState shape but uses serialisable primitives only.
......@@ -897,6 +880,7 @@ export const DesktopPreviewAutomationWaitForInputSchema = Schema.Struct({
897880export interface DesktopBridge {
898881 getAppBranding: () => DesktopAppBranding | null;
899882 getLocalEnvironmentBootstrap: () => DesktopEnvironmentBootstrap | null;
883 getLocalEnvironmentBearerToken: () => Promise<string>;
900884 getClientSettings: () => Promise<ClientSettings | null>;
901885 setClientSettings: (settings: ClientSettings) => Promise<void>;
902886 getConnectionCatalog?: () => Promise<string | null>;
......@@ -935,12 +919,6 @@ export interface DesktopBridge {
935919 position?: { x: number; y: number },
936920 ) => Promise<T | null>;
937921 openExternal: (url: string) => Promise<boolean>;
938 createCloudAuthRequest: () => Promise<string>;
939 getCloudAuthToken: () => Promise<string | null>;
940 setCloudAuthToken: (token: string) => Promise<boolean>;
941 clearCloudAuthToken: () => Promise<void>;
942 fetchCloudAuth: (input: DesktopCloudAuthFetchInput) => Promise<DesktopCloudAuthFetchResult>;
943 onCloudAuthCallback: (listener: (rawUrl: string) => void) => () => void;
944922 onMenuAction: (listener: (action: string) => void) => () => void;
945923 getUpdateState: () => Promise<DesktopUpdateState>;
946924 setUpdateChannel: (channel: DesktopUpdateChannel) => Promise<DesktopUpdateState>;
pnpm-lock.yaml+510-284
......@@ -35,6 +35,13 @@ catalogs:
3535 version: 0.1.24
3636
3737overrides:
38 '@clerk/backend': 3.8.2-snapshot.v20260619001138
39 '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138
40 '@clerk/electron': 0.0.1-snapshot.v20260619001138
41 '@clerk/electron-passkeys': 0.0.1-snapshot.v20260619001138
42 '@clerk/expo': 3.4.8-snapshot.v20260619001138
43 '@clerk/react': 6.10.4-snapshot.v20260619001138
44 '@clerk/shared': 4.19.2-snapshot.v20260619001138
3845 '@clerk/clerk-js>@base-org/account': '-'
3946 '@clerk/clerk-js>@coinbase/wallet-sdk': '-'
4047 '@clerk/clerk-js>@solana/wallet-adapter-base': '-'
......@@ -105,6 +112,12 @@ importers:
105112
106113 apps/desktop:
107114 dependencies:
115 '@clerk/electron':
116 specifier: 0.0.1-snapshot.v20260619001138
117 version: 0.0.1-snapshot.v20260619001138(@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
118 '@clerk/electron-passkeys':
119 specifier: 0.0.1-snapshot.v20260619001138
120 version: 0.0.1-snapshot.v20260619001138
108121 '@effect/platform-node':
109122 specifier: 4.0.0-beta.78
110123 version: 4.0.0-beta.78(bufferutil@4.1.0)(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(ioredis@5.11.0)(utf-8-validate@6.0.6)
......@@ -129,6 +142,9 @@ importers:
129142 electron:
130143 specifier: 41.5.0
131144 version: 41.5.0
145 electron-store:
146 specifier: ^8.2.0
147 version: 8.2.0
132148 electron-updater:
133149 specifier: ^6.6.2
134150 version: 6.8.3
......@@ -178,10 +194,10 @@ importers:
178194 dependencies:
179195 '@callstack/liquid-glass':
180196 specifier: ^0.7.1
181 version: 0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
197 version: 0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
182198 '@clerk/expo':
183 specifier: ^3.4.1
184 version: 3.4.1(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
199 specifier: 3.4.8-snapshot.v20260619001138
200 version: 3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
185201 '@effect/atom-react':
186202 specifier: 4.0.0-beta.78
187203 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(react@19.2.3)(scheduler@0.27.0)
......@@ -190,10 +206,10 @@ importers:
190206 version: 0.4.2
191207 '@expo/ui':
192208 specifier: ~56.0.8
193 version: 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
209 version: 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
194210 '@legendapp/list':
195211 specifier: 3.0.0-beta.44
196 version: 3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
212 version: 3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
197213 '@noble/curves':
198214 specifier: 'catalog:'
199215 version: 1.9.1
......@@ -205,7 +221,7 @@ importers:
205221 version: 1.3.0-beta.5(patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b345081e285a6c53a)(@shikijs/themes@4.2.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
206222 '@react-native-menu/menu':
207223 specifier: ^2.0.0
208 version: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
224 version: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
209225 '@shikijs/core':
210226 specifier: 4.2.0
211227 version: 4.2.0
......@@ -226,7 +242,7 @@ importers:
226242 version: link:../../packages/contracts
227243 '@t3tools/mobile-markdown-text':
228244 specifier: file:./modules/t3-markdown-text
229 version: file:apps/mobile/modules/t3-markdown-text(e909ee9a8f7fbe234da80c739fca4984)
245 version: file:apps/mobile/modules/t3-markdown-text(f0ff241d48c23db461fff5d47e450578)
230246 '@t3tools/mobile-review-diff-native':
231247 specifier: file:./modules/t3-review-diff
232248 version: file:apps/mobile/modules/t3-review-diff
......@@ -247,40 +263,40 @@ importers:
247263 version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)
248264 expo:
249265 specifier: ^56.0.0
250 version: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
266 version: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
251267 expo-asset:
252268 specifier: ~56.0.15
253 version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
269 version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
254270 expo-auth-session:
255271 specifier: ~56.0.12
256 version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
272 version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
257273 expo-build-properties:
258274 specifier: ~56.0.15
259275 version: 56.0.16(expo@56.0.8)
260276 expo-camera:
261277 specifier: ~56.0.7
262 version: 56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
278 version: 56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
263279 expo-clipboard:
264280 specifier: ~56.0.3
265 version: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
281 version: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
266282 expo-constants:
267283 specifier: ~56.0.16
268 version: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
284 version: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
269285 expo-crypto:
270286 specifier: ~56.0.4
271287 version: 56.0.4(expo@56.0.8)
272288 expo-dev-client:
273289 specifier: ~56.0.16
274 version: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
290 version: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
275291 expo-file-system:
276292 specifier: ~56.0.7
277 version: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
293 version: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
278294 expo-font:
279295 specifier: ~56.0.5
280 version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
296 version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
281297 expo-glass-effect:
282298 specifier: ~56.0.4
283 version: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
299 version: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
284300 expo-haptics:
285301 specifier: ~56.0.3
286302 version: 56.0.3(expo@56.0.8)
......@@ -289,19 +305,19 @@ importers:
289305 version: 56.0.15(expo@56.0.8)
290306 expo-linking:
291307 specifier: ~56.0.12
292 version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
308 version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
293309 expo-network:
294310 specifier: ~56.0.5
295311 version: 56.0.5(expo@56.0.8)(react@19.2.3)
296312 expo-notifications:
297313 specifier: ~56.0.14
298 version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
314 version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
299315 expo-paste-input:
300316 specifier: ^0.1.15
301 version: 0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
317 version: 0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
302318 expo-router:
303319 specifier: ~56.2.7
304 version: 56.2.8(c021de11d02907bd585610408f5252e8)
320 version: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
305321 expo-secure-store:
306322 specifier: ~56.0.4
307323 version: 56.0.4(expo@56.0.8)
......@@ -310,16 +326,16 @@ importers:
310326 version: 56.0.10(expo@56.0.8)(typescript@6.0.3)
311327 expo-symbols:
312328 specifier: ~56.0.5
313 version: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
329 version: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
314330 expo-updates:
315331 specifier: ~56.0.17
316 version: 56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
332 version: 56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
317333 expo-web-browser:
318334 specifier: ~56.0.5
319 version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
335 version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
320336 expo-widgets:
321337 specifier: ~56.0.15
322 version: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)
338 version: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
323339 punycode:
324340 specifier: ^2.3.1
325341 version: 2.3.1
......@@ -331,43 +347,43 @@ importers:
331347 version: 19.2.3(react@19.2.3)
332348 react-native:
333349 specifier: 0.85.3
334 version: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
350 version: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
335351 react-native-gesture-handler:
336352 specifier: ~2.31.1
337 version: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
353 version: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
338354 react-native-image-viewing:
339355 specifier: ^0.2.2
340 version: 0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
356 version: 0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
341357 react-native-keyboard-controller:
342358 specifier: 1.21.6
343 version: 1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
359 version: 1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
344360 react-native-nitro-markdown:
345361 specifier: ^0.5.0
346 version: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
362 version: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
347363 react-native-nitro-modules:
348364 specifier: ^0.35.4
349 version: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
365 version: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
350366 react-native-reanimated:
351367 specifier: 4.3.1
352 version: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
368 version: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
353369 react-native-safe-area-context:
354370 specifier: ~5.7.0
355 version: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
371 version: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
356372 react-native-screens:
357373 specifier: 4.25.2
358 version: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
374 version: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
359375 react-native-shiki-engine:
360376 specifier: ^0.3.12
361 version: 0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
377 version: 0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
362378 react-native-svg:
363379 specifier: 15.15.4
364 version: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
380 version: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
365381 react-native-webview:
366382 specifier: ^13.16.1
367 version: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
383 version: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
368384 react-native-worklets:
369385 specifier: 0.8.3
370 version: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
386 version: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
371387 shiki:
372388 specifier: 4.2.0
373389 version: 4.2.0
......@@ -376,7 +392,7 @@ importers:
376392 version: 3.6.0
377393 uniwind:
378394 specifier: ^1.6.2
379 version: 1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0)
395 version: 1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0)
380396 devDependencies:
381397 '@effect/vitest':
382398 specifier: 4.0.0-beta.78
......@@ -466,12 +482,12 @@ importers:
466482 '@base-ui/react':
467483 specifier: ^1.4.1
468484 version: 1.5.0(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
469 '@clerk/clerk-js':
470 specifier: ^6.16.0
471 version: 6.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
485 '@clerk/electron':
486 specifier: 0.0.1-snapshot.v20260619001138
487 version: 0.0.1-snapshot.v20260619001138(@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
472488 '@clerk/react':
473 specifier: ^6.9.0
474 version: 6.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
489 specifier: 6.10.4-snapshot.v20260619001138
490 version: 6.10.4-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
475491 '@dnd-kit/core':
476492 specifier: ^6.3.1
477493 version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
......@@ -621,8 +637,8 @@ importers:
621637 infra/relay:
622638 dependencies:
623639 '@clerk/backend':
624 specifier: 3.6.1
625 version: 3.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
640 specifier: 3.8.2-snapshot.v20260619001138
641 version: 3.8.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
626642 '@effect/sql-pg':
627643 specifier: 4.0.0-beta.78
628644 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))
......@@ -1516,19 +1532,60 @@ packages:
15161532 resolution: {integrity: sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA==}
15171533 engines: {node: '>= 20.12.0'}
15181534
1519 '@clerk/backend@3.6.1':
1520 resolution: {integrity: sha512-LkfekzF/0UMXacX+17xy3ExRraO0mm+thXejC8Q32gWHd1wLdxK3YXDsLDF00E1r1InWBKIt2ZOxs6hTwZPJjA==}
1535 '@clerk/backend@3.8.2-snapshot.v20260619001138':
1536 resolution: {integrity: sha512-nT6M7rKTuvoDnSZwO3Th2NMjcWZy/0ZfXYyqd/o/lFpUFcQO0J4fM2e2wF9kNCl99EPvDQQUAR8APNNy/j40rg==}
15211537 engines: {node: '>=20.9.0'}
15221538
1523 '@clerk/clerk-js@6.16.0':
1524 resolution: {integrity: sha512-8xv/XDsxhOZd1n4DNIRJ2EehIRUg6UiqKAnfd0L88R2t1g6sVnLi1FInJ5i8Qyx5oY/creXx6X1AZ1V5PobRkA==}
1539 '@clerk/clerk-js@6.18.2-snapshot.v20260619001138':
1540 resolution: {integrity: sha512-BpRSi2QXdfR5nnzC7/YCCqK40m1M4A/rN5unau7QKHj6V7xChl2fOvxYjekpH+DEyw6NAe/2jdqQv35iv3T5oA==}
15251541 engines: {node: '>=20.9.0'}
15261542
1527 '@clerk/expo@3.4.1':
1528 resolution: {integrity: sha512-gpAXsuUnsUdUD0/2XjyxaC9quF5rT+2umkmV74nBLVAFurGMMMMHvnHqrQEtZ7tH5GHNXYw5+pgmnzd1HiMQbQ==}
1543 '@clerk/electron-passkeys-darwin-arm64@0.0.1-snapshot.v20260619001138':
1544 resolution: {integrity: sha512-dbQ/0ZtfDQgYKCSzu3AMxTnGSrdZxulurZMT4Jpvin48Etc27PdcT7VvwOGIa7R+Ab8yMeaoLJbfwHTZv35F+Q==}
1545 cpu: [arm64]
1546 os: [darwin]
1547
1548 '@clerk/electron-passkeys-darwin-x64@0.0.1-snapshot.v20260619001138':
1549 resolution: {integrity: sha512-g9tbni7yKIJ/Xpollm25Gf7YLyFP24VyqRHcGDnEsHC1tIffG41spjLr10NgsBoRvFLMlaQJMSrfpjVOpBhAjw==}
1550 cpu: [x64]
1551 os: [darwin]
1552
1553 '@clerk/electron-passkeys-win32-arm64-msvc@0.0.1-snapshot.v20260619001138':
1554 resolution: {integrity: sha512-mFDQ1vQ9dLIUrnjNGhIGxqyK0iiym919gYDPO3orYEcICdEiE8xZyEbCtKSVaeX6RWGJAcqFzCxbLVG6V+1k4Q==}
1555 cpu: [arm64]
1556 os: [win32]
1557
1558 '@clerk/electron-passkeys-win32-x64-msvc@0.0.1-snapshot.v20260619001138':
1559 resolution: {integrity: sha512-IwkLw+d73bd7YJPRR8LAkqP42VIIpbJXCCbs5eVKbmam9M0vSUyeLWEtSiWUEypmLK13xv+7316K4GIA2tmDGQ==}
1560 cpu: [x64]
1561 os: [win32]
1562
1563 '@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138':
1564 resolution: {integrity: sha512-diU5Q9Nx+30mesrLFOmr0OCnmxE0ogUHXktZBTQx2nQvZb/n2UGOpGNLbY1p9edKMcSc5LfJEsQ8NogfLBBvPg==}
1565 engines: {node: '>=20.9.0'}
1566
1567 '@clerk/electron@0.0.1-snapshot.v20260619001138':
1568 resolution: {integrity: sha512-wrBEdMMRqhMF4a7aQpZaBXKJfONIpaYLcgBl0m1b2r+Xg4yuZ47YUoDxhI2Ksvbx2KQPd4i9HP0enL6gEcoqfA==}
15291569 engines: {node: '>=20.9.0'}
15301570 peerDependencies:
1531 '@clerk/expo-passkeys': '>=0.0.6'
1571 '@clerk/electron-passkeys': 0.0.1-snapshot.v20260619001138
1572 electron: '>=28'
1573 electron-store: ^8.2.0
1574 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
1575 react-dom: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
1576 peerDependenciesMeta:
1577 '@clerk/electron-passkeys':
1578 optional: true
1579 electron-store:
1580 optional: true
1581 react-dom:
1582 optional: true
1583
1584 '@clerk/expo@3.4.8-snapshot.v20260619001138':
1585 resolution: {integrity: sha512-E6q4p5ded45aO3y/+f7APfy5pFj2Y/BEpe/1gzdr6UGxj9kJxkiZQV29hcpjYEFMEJK60f7XbOHZTQEZRB5OwQ==}
1586 engines: {node: '>=20.9.0'}
1587 peerDependencies:
1588 '@clerk/expo-passkeys': 1.1.8-snapshot.v20260619001138
15321589 expo: '>=53 <57'
15331590 expo-apple-authentication: '>=7.0.0'
15341591 expo-auth-session: '>=5'
......@@ -1560,15 +1617,15 @@ packages:
15601617 react-dom:
15611618 optional: true
15621619
1563 '@clerk/react@6.9.0':
1564 resolution: {integrity: sha512-M0QGyGS732tYBXeG+28UgElXM2TfoSZ+4mWGisC8yxJX8NjH4hEPJTAQuZmYRLNaCyGQCuzjYVQiQRC+GbDtmA==}
1620 '@clerk/react@6.10.4-snapshot.v20260619001138':
1621 resolution: {integrity: sha512-Z7Otjly14SoxadMmk8d9ZdbaXU0me9B1zGdCtnNIcQ7X2GCbeyKm/lOM27IzWYXZKO6o+sXlqsq9A9tcxet5nA==}
15651622 engines: {node: '>=20.9.0'}
15661623 peerDependencies:
15671624 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
15681625 react-dom: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
15691626
1570 '@clerk/shared@4.17.0':
1571 resolution: {integrity: sha512-YeQ+6zDmqyor1mPHjZx18j+LssL6Pobvid8hb7HQMioSo8sGDBEVi/Z12bs+gUhe9KbdP+ygHsKOqqeGAPuPZA==}
1627 '@clerk/shared@4.19.2-snapshot.v20260619001138':
1628 resolution: {integrity: sha512-NAIz0L6+CaRrYn0DoXclUP1R0g6C2ljOzHogQ3rx9/SWUVpfaYX2lxPhURU89xvPoOGBZQb2xwk6xFh/7cjJfQ==}
15721629 engines: {node: '>=20.9.0'}
15731630 peerDependencies:
15741631 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
......@@ -4962,6 +5019,14 @@ packages:
49625019 ajv:
49635020 optional: true
49645021
5022 ajv-formats@2.1.1:
5023 resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
5024 peerDependencies:
5025 ajv: ^8.0.0
5026 peerDependenciesMeta:
5027 ajv:
5028 optional: true
5029
49655030 ajv-formats@3.0.1:
49665031 resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
49675032 peerDependencies:
......@@ -5127,6 +5192,10 @@ packages:
51275192 resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
51285193 engines: {node: '>= 4.0.0'}
51295194
5195 atomically@1.7.0:
5196 resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
5197 engines: {node: '>=10.12.0'}
5198
51305199 auto-bind@5.0.1:
51315200 resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==}
51325201 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
......@@ -5535,6 +5604,10 @@ packages:
55355604 concat-map@0.0.1:
55365605 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
55375606
5607 conf@10.2.0:
5608 resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
5609 engines: {node: '>=12'}
5610
55385611 connect@3.7.0:
55395612 resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
55405613 engines: {node: '>= 0.10.0'}
......@@ -5643,6 +5716,10 @@ packages:
56435716 resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==}
56445717 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
56455718
5719 debounce-fn@4.0.0:
5720 resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
5721 engines: {node: '>=10'}
5722
56465723 debug@2.6.9:
56475724 resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
56485725 peerDependencies:
......@@ -5782,6 +5859,10 @@ packages:
57825859 domutils@3.2.2:
57835860 resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
57845861
5862 dot-prop@6.0.1:
5863 resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
5864 engines: {node: '>=10'}
5865
57855866 dotenv-expand@11.0.7:
57865867 resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
57875868 engines: {node: '>=12'}
......@@ -5940,6 +6021,9 @@ packages:
59406021 electron-publish@26.8.1:
59416022 resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==}
59426023
6024 electron-store@8.2.0:
6025 resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==}
6026
59436027 electron-to-chromium@1.5.364:
59446028 resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
59456029
......@@ -6488,6 +6572,10 @@ packages:
64886572 find-my-way-ts@0.1.6:
64896573 resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==}
64906574
6575 find-up@3.0.0:
6576 resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
6577 engines: {node: '>=6'}
6578
64916579 flattie@1.1.1:
64926580 resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
64936581 engines: {node: '>=8'}
......@@ -6917,6 +7005,10 @@ packages:
69177005 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
69187006 engines: {node: '>=0.12.0'}
69197007
7008 is-obj@2.0.0:
7009 resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
7010 engines: {node: '>=8'}
7011
69207012 is-plain-obj@4.1.0:
69217013 resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
69227014 engines: {node: '>=12'}
......@@ -7040,6 +7132,9 @@ packages:
70407132 json-schema-traverse@1.0.0:
70417133 resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
70427134
7135 json-schema-typed@7.0.3:
7136 resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
7137
70437138 json-schema-typed@8.0.2:
70447139 resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
70457140
......@@ -7323,6 +7418,10 @@ packages:
73237418 resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
73247419 engines: {node: '>= 12.0.0'}
73257420
7421 locate-path@3.0.0:
7422 resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
7423 engines: {node: '>=6'}
7424
73267425 lodash.debounce@4.0.8:
73277426 resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
73287427
......@@ -7677,6 +7776,10 @@ packages:
76777776 resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
76787777 engines: {node: '>=6'}
76797778
7779 mimic-fn@3.1.0:
7780 resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
7781 engines: {node: '>=8'}
7782
76807783 mimic-function@5.0.1:
76817784 resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
76827785 engines: {node: '>=18'}
......@@ -7985,6 +8088,10 @@ packages:
79858088 resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
79868089 engines: {node: '>=8'}
79878090
8091 p-limit@2.3.0:
8092 resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
8093 engines: {node: '>=6'}
8094
79888095 p-limit@3.1.0:
79898096 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
79908097 engines: {node: '>=10'}
......@@ -7993,6 +8100,10 @@ packages:
79938100 resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==}
79948101 engines: {node: '>=20'}
79958102
8103 p-locate@3.0.0:
8104 resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
8105 engines: {node: '>=6'}
8106
79968107 p-queue@9.3.0:
79978108 resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==}
79988109 engines: {node: '>=20'}
......@@ -8001,6 +8112,10 @@ packages:
80018112 resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}
80028113 engines: {node: '>=20'}
80038114
8115 p-try@2.2.0:
8116 resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
8117 engines: {node: '>=6'}
8118
80048119 package-manager-detector@1.6.0:
80058120 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
80068121
......@@ -8031,6 +8146,10 @@ packages:
80318146 path-browserify@1.0.1:
80328147 resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
80338148
8149 path-exists@3.0.0:
8150 resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
8151 engines: {node: '>=4'}
8152
80348153 path-expression-matcher@1.5.0:
80358154 resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
80368155 engines: {node: '>=14.0.0'}
......@@ -8141,6 +8260,10 @@ packages:
81418260 resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
81428261 engines: {node: '>=16.20.0'}
81438262
8263 pkg-up@3.1.0:
8264 resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
8265 engines: {node: '>=8'}
8266
81448267 playwright-core@1.60.0:
81458268 resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
81468269 engines: {node: '>=18'}
......@@ -9211,6 +9334,10 @@ packages:
92119334 resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
92129335 engines: {node: '>=8'}
92139336
9337 type-fest@2.19.0:
9338 resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
9339 engines: {node: '>=12.20'}
9340
92149341 type-fest@5.7.0:
92159342 resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==}
92169343 engines: {node: '>=20'}
......@@ -10712,10 +10839,10 @@ snapshots:
1071210839 optionalDependencies:
1071310840 '@types/react': 19.2.16
1071410841
10715 '@callstack/liquid-glass@0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
10842 '@callstack/liquid-glass@0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1071610843 dependencies:
1071710844 react: 19.2.3
10718 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10845 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1071910846
1072010847 '@capsizecss/unpack@4.0.0':
1072110848 dependencies:
......@@ -10744,18 +10871,18 @@ snapshots:
1074410871 fast-wrap-ansi: 0.2.2
1074510872 sisteransi: 1.0.5
1074610873
10747 '@clerk/backend@3.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
10874 '@clerk/backend@3.8.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1074810875 dependencies:
10749 '@clerk/shared': 4.17.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10876 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1075010877 standardwebhooks: 1.0.0
1075110878 tslib: 2.8.1
1075210879 transitivePeerDependencies:
1075310880 - react
1075410881 - react-dom
1075510882
10756 '@clerk/clerk-js@6.16.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
10883 '@clerk/clerk-js@6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1075710884 dependencies:
10758 '@clerk/shared': 4.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10885 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1075910886 '@stripe/stripe-js': 5.6.0
1076010887 '@swc/helpers': 0.5.21
1076110888 '@tanstack/query-core': 5.100.14
......@@ -10770,9 +10897,9 @@ snapshots:
1077010897 - react
1077110898 - react-dom
1077210899
10773 '@clerk/clerk-js@6.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
10900 '@clerk/clerk-js@6.18.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1077410901 dependencies:
10775 '@clerk/shared': 4.17.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10902 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1077610903 '@stripe/stripe-js': 5.6.0
1077710904 '@swc/helpers': 0.5.21
1077810905 '@tanstack/query-core': 5.100.14
......@@ -10787,40 +10914,72 @@ snapshots:
1078710914 - react
1078810915 - react-dom
1078910916
10790 '@clerk/expo@3.4.1(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
10917 '@clerk/electron-passkeys-darwin-arm64@0.0.1-snapshot.v20260619001138':
10918 optional: true
10919
10920 '@clerk/electron-passkeys-darwin-x64@0.0.1-snapshot.v20260619001138':
10921 optional: true
10922
10923 '@clerk/electron-passkeys-win32-arm64-msvc@0.0.1-snapshot.v20260619001138':
10924 optional: true
10925
10926 '@clerk/electron-passkeys-win32-x64-msvc@0.0.1-snapshot.v20260619001138':
10927 optional: true
10928
10929 '@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138':
10930 optionalDependencies:
10931 '@clerk/electron-passkeys-darwin-arm64': 0.0.1-snapshot.v20260619001138
10932 '@clerk/electron-passkeys-darwin-x64': 0.0.1-snapshot.v20260619001138
10933 '@clerk/electron-passkeys-win32-arm64-msvc': 0.0.1-snapshot.v20260619001138
10934 '@clerk/electron-passkeys-win32-x64-msvc': 0.0.1-snapshot.v20260619001138
10935
10936 '@clerk/electron@0.0.1-snapshot.v20260619001138(@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
10937 dependencies:
10938 '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10939 '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10940 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10941 electron: 41.5.0
10942 react: 19.2.6
10943 tslib: 2.8.1
10944 optionalDependencies:
10945 '@clerk/electron-passkeys': 0.0.1-snapshot.v20260619001138
10946 electron-store: 8.2.0
10947 react-dom: 19.2.6(react@19.2.6)
10948
10949 '@clerk/expo@3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1079110950 dependencies:
10792 '@clerk/clerk-js': 6.16.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10793 '@clerk/react': 6.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10794 '@clerk/shared': 4.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10951 '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10952 '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10953 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1079510954 base-64: 1.0.0
10796 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
10955 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1079710956 react: 19.2.3
10798 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10799 react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10957 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10958 react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1080010959 tslib: 2.8.1
1080110960 optionalDependencies:
10802 expo-auth-session: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
10803 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10961 expo-auth-session: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
10962 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1080410963 expo-crypto: 56.0.4(expo@56.0.8)
1080510964 expo-secure-store: 56.0.4(expo@56.0.8)
10806 expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10965 expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1080710966 react-dom: 19.2.3(react@19.2.3)
1080810967
10809 '@clerk/react@6.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
10968 '@clerk/react@6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1081010969 dependencies:
10811 '@clerk/shared': 4.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
10970 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1081210971 react: 19.2.3
1081310972 react-dom: 19.2.3(react@19.2.3)
1081410973 tslib: 2.8.1
1081510974
10816 '@clerk/react@6.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
10975 '@clerk/react@6.10.4-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1081710976 dependencies:
10818 '@clerk/shared': 4.17.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
10977 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1081910978 react: 19.2.6
1082010979 react-dom: 19.2.6(react@19.2.6)
1082110980 tslib: 2.8.1
1082210981
10823 '@clerk/shared@4.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
10982 '@clerk/shared@4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1082410983 dependencies:
1082510984 '@tanstack/query-core': 5.100.14
1082610985 dequal: 2.0.3
......@@ -10830,7 +10989,7 @@ snapshots:
1083010989 react: 19.2.3
1083110990 react-dom: 19.2.3(react@19.2.3)
1083210991
10833 '@clerk/shared@4.17.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
10992 '@clerk/shared@4.19.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1083410993 dependencies:
1083510994 '@tanstack/query-core': 5.100.14
1083610995 dequal: 2.0.3
......@@ -11370,7 +11529,7 @@ snapshots:
1137011529
1137111530 '@expo-google-fonts/material-symbols@0.4.38': {}
1137211531
11373 '@expo/cli@56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)':
11532 '@expo/cli@56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)':
1137411533 dependencies:
1137511534 '@expo/code-signing-certificates': 0.0.6
1137611535 '@expo/config': 56.0.9(typescript@6.0.3)
......@@ -11380,7 +11539,7 @@ snapshots:
1138011539 '@expo/image-utils': 0.10.1(typescript@6.0.3)
1138111540 '@expo/inline-modules': 0.0.10(typescript@6.0.3)
1138211541 '@expo/json-file': 10.2.0
11383 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11542 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1138411543 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1138511544 '@expo/metro-config': 56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)
1138611545 '@expo/metro-file-map': 56.0.3
......@@ -11405,7 +11564,7 @@ snapshots:
1140511564 connect: 3.7.0
1140611565 debug: 4.4.3
1140711566 dnssd-advertise: 1.1.4
11408 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11567 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1140911568 expo-server: 56.0.4
1141011569 fetch-nodeshim: 0.4.10
1141111570 getenv: 2.0.0
......@@ -11431,8 +11590,8 @@ snapshots:
1143111590 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1143211591 zod: 3.25.76
1143311592 optionalDependencies:
11434 expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)
11435 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11593 expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
11594 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1143611595 transitivePeerDependencies:
1143711596 - '@expo/dom-webview'
1143811597 - '@expo/metro-runtime'
......@@ -11494,18 +11653,18 @@ snapshots:
1149411653 transitivePeerDependencies:
1149511654 - supports-color
1149611655
11497 '@expo/devtools@56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11656 '@expo/devtools@56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1149811657 dependencies:
1149911658 chalk: 4.1.2
1150011659 optionalDependencies:
1150111660 react: 19.2.3
11502 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11661 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1150311662
11504 '@expo/dom-webview@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11663 '@expo/dom-webview@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1150511664 dependencies:
11506 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11665 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1150711666 react: 19.2.3
11508 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11667 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1150911668
1151011669 '@expo/env@2.3.0':
1151111670 dependencies:
......@@ -11566,13 +11725,13 @@ snapshots:
1156611725 - supports-color
1156711726 - typescript
1156811727
11569 '@expo/log-box@56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11728 '@expo/log-box@56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1157011729 dependencies:
11571 '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11730 '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1157211731 anser: 1.4.10
11573 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11732 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1157411733 react: 19.2.3
11575 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11734 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1157611735 stacktrace-parser: 0.1.11
1157711736
1157811737 '@expo/metro-config@56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)':
......@@ -11602,7 +11761,7 @@ snapshots:
1160211761 postcss: 8.5.15
1160311762 resolve-from: 5.0.0
1160411763 optionalDependencies:
11605 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11764 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1160611765 transitivePeerDependencies:
1160711766 - bufferutil
1160811767 - supports-color
......@@ -11620,14 +11779,14 @@ snapshots:
1162011779 transitivePeerDependencies:
1162111780 - supports-color
1162211781
11623 '@expo/metro-runtime@56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11782 '@expo/metro-runtime@56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1162411783 dependencies:
11625 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11784 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1162611785 anser: 1.4.10
11627 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11786 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1162811787 pretty-format: 29.7.0
1162911788 react: 19.2.3
11630 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11789 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1163111790 stacktrace-parser: 0.1.11
1163211791 whatwg-fetch: 3.6.20
1163311792 optionalDependencies:
......@@ -11702,14 +11861,14 @@ snapshots:
1170211861 '@expo/router-server@56.0.12(@expo/metro-runtime@56.0.13)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo-server@56.0.4)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1170311862 dependencies:
1170411863 debug: 4.4.3
11705 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11706 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
11707 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11864 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11865 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
11866 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1170811867 expo-server: 56.0.4
1170911868 react: 19.2.3
1171011869 optionalDependencies:
11711 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11712 expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)
11870 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11871 expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
1171311872 react-dom: 19.2.3(react@19.2.3)
1171411873 transitivePeerDependencies:
1171511874 - supports-color
......@@ -11724,18 +11883,18 @@ snapshots:
1172411883
1172511884 '@expo/sudo-prompt@9.3.2': {}
1172611885
11727 '@expo/ui@56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)':
11886 '@expo/ui@56.0.15(961c4aa6f32829b318e3c87ef20ad401)':
1172811887 dependencies:
11729 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11888 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1173011889 react: 19.2.3
11731 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11890 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1173211891 sf-symbols-typescript: 2.2.0
1173311892 vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1173411893 optionalDependencies:
1173511894 '@babel/core': 7.29.7
1173611895 react-dom: 19.2.3(react@19.2.3)
11737 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11738 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11896 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11897 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1173911898 transitivePeerDependencies:
1174011899 - '@types/react'
1174111900 - '@types/react-dom'
......@@ -11996,13 +12155,13 @@ snapshots:
1199612155 dependencies:
1199712156 jsbi: 4.3.2
1199812157
11999 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12158 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1200012159 dependencies:
1200112160 react: 19.2.3
1200212161 use-sync-external-store: 1.6.0(react@19.2.3)
1200312162 optionalDependencies:
1200412163 react-dom: 19.2.3(react@19.2.3)
12005 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12164 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1200612165
1200712166 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1200812167 dependencies:
......@@ -12825,15 +12984,15 @@ snapshots:
1282512984 prompts: 2.4.2
1282612985 tinyexec: 1.2.4
1282712986
12828 '@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12987 '@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1282912988 dependencies:
1283012989 react: 19.2.3
12831 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12990 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1283212991
12833 '@react-native-menu/menu@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12992 '@react-native-menu/menu@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1283412993 dependencies:
1283512994 react: 19.2.3
12836 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12995 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1283712996
1283812997 '@react-native/assets-registry@0.85.3': {}
1283912998
......@@ -12893,7 +13052,7 @@ snapshots:
1289313052 tinyglobby: 0.2.17
1289413053 yargs: 17.7.2
1289513054
12896 '@react-native/community-cli-plugin@0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
13055 '@react-native/community-cli-plugin@0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
1289713056 dependencies:
1289813057 '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1289913058 debug: 4.4.3
......@@ -12903,7 +13062,7 @@ snapshots:
1290313062 metro-core: 0.84.4
1290413063 semver: 7.8.1
1290513064 optionalDependencies:
12906 '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)
13065 '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1290713066 transitivePeerDependencies:
1290813067 - bufferutil
1290913068 - supports-color
......@@ -12951,7 +13110,7 @@ snapshots:
1295113110 transitivePeerDependencies:
1295213111 - supports-color
1295313112
12954 '@react-native/metro-config@0.85.3(@babel/core@7.29.7)':
13113 '@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
1295513114 dependencies:
1295613115 '@react-native/js-polyfills': 0.85.3
1295713116 '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7)
......@@ -12959,16 +13118,18 @@ snapshots:
1295913118 metro-runtime: 0.84.4
1296013119 transitivePeerDependencies:
1296113120 - '@babel/core'
13121 - bufferutil
1296213122 - supports-color
13123 - utf-8-validate
1296313124
1296413125 '@react-native/normalize-colors@0.85.3': {}
1296513126
12966 '@react-native/virtualized-lists@0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
13127 '@react-native/virtualized-lists@0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1296713128 dependencies:
1296813129 invariant: 2.2.4
1296913130 nullthrows: 1.1.1
1297013131 react: 19.2.3
12971 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13132 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1297213133 optionalDependencies:
1297313134 '@types/react': 19.2.16
1297413135
......@@ -13381,15 +13542,15 @@ snapshots:
1338113542 dependencies:
1338213543 defer-to-connect: 2.0.1
1338313544
13384 '@t3tools/mobile-markdown-text@file:apps/mobile/modules/t3-markdown-text(e909ee9a8f7fbe234da80c739fca4984)':
13545 '@t3tools/mobile-markdown-text@file:apps/mobile/modules/t3-markdown-text(f0ff241d48c23db461fff5d47e450578)':
1338513546 dependencies:
13386 expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
13387 expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13547 expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
13548 expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1338813549 expo-haptics: 56.0.3(expo@56.0.8)
13389 expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13550 expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1339013551 react: 19.2.3
13391 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13392 react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13552 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13553 react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1339313554
1339413555 '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {}
1339513556
......@@ -14060,6 +14221,10 @@ snapshots:
1406014221 optionalDependencies:
1406114222 ajv: 8.20.0
1406214223
14224 ajv-formats@2.1.1(ajv@8.20.0):
14225 optionalDependencies:
14226 ajv: 8.20.0
14227
1406314228 ajv-formats@3.0.1(ajv@8.20.0):
1406414229 optionalDependencies:
1406514230 ajv: 8.20.0
......@@ -14355,6 +14520,8 @@ snapshots:
1435514520
1435614521 at-least-node@1.0.0: {}
1435714522
14523 atomically@1.7.0: {}
14524
1435814525 auto-bind@5.0.1: {}
1435914526
1436014527 aws-ssl-profiles@1.1.2: {}
......@@ -14459,8 +14626,8 @@ snapshots:
1445914626 react-refresh: 0.14.2
1446014627 optionalDependencies:
1446114628 '@babel/runtime': 7.29.7
14462 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
14463 expo-widgets: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)
14629 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
14630 expo-widgets: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
1446414631 transitivePeerDependencies:
1446514632 - '@babel/core'
1446614633 - supports-color
......@@ -14821,6 +14988,19 @@ snapshots:
1482114988
1482214989 concat-map@0.0.1: {}
1482314990
14991 conf@10.2.0:
14992 dependencies:
14993 ajv: 8.20.0
14994 ajv-formats: 2.1.1(ajv@8.20.0)
14995 atomically: 1.7.0
14996 debounce-fn: 4.0.0
14997 dot-prop: 6.0.1
14998 env-paths: 2.2.1
14999 json-schema-typed: 7.0.3
15000 onetime: 5.1.2
15001 pkg-up: 3.1.0
15002 semver: 7.8.1
15003
1482415004 connect@3.7.0:
1482515005 dependencies:
1482615006 debug: 2.6.9
......@@ -14923,6 +15103,10 @@ snapshots:
1492315103
1492415104 culori@4.0.2: {}
1492515105
15106 debounce-fn@4.0.0:
15107 dependencies:
15108 mimic-fn: 3.1.0
15109
1492615110 debug@2.6.9:
1492715111 dependencies:
1492815112 ms: 2.0.0
......@@ -15052,6 +15236,10 @@ snapshots:
1505215236 domelementtype: 2.3.0
1505315237 domhandler: 5.0.3
1505415238
15239 dot-prop@6.0.1:
15240 dependencies:
15241 is-obj: 2.0.0
15242
1505515243 dotenv-expand@11.0.7:
1505615244 dependencies:
1505715245 dotenv: 16.6.1
......@@ -15142,6 +15330,11 @@ snapshots:
1514215330 transitivePeerDependencies:
1514315331 - supports-color
1514415332
15333 electron-store@8.2.0:
15334 dependencies:
15335 conf: 10.2.0
15336 type-fest: 2.19.0
15337
1514515338 electron-to-chromium@1.5.364: {}
1514615339
1514715340 electron-updater@6.8.3:
......@@ -15325,29 +15518,29 @@ snapshots:
1532515518
1532615519 expo-application@56.0.3(expo@56.0.8):
1532715520 dependencies:
15328 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15521 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1532915522
15330 expo-asset@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
15523 expo-asset@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
1533115524 dependencies:
1533215525 '@expo/image-utils': 0.10.1(typescript@6.0.3)
15333 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15334 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15526 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15527 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1533515528 react: 19.2.3
15336 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15529 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1533715530 transitivePeerDependencies:
1533815531 - supports-color
1533915532 - typescript
1534015533
15341 expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15534 expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1534215535 dependencies:
1534315536 expo-application: 56.0.3(expo@56.0.8)
15344 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15537 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1534515538 expo-crypto: 56.0.4(expo@56.0.8)
15346 expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15347 expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15539 expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15540 expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1534815541 invariant: 2.2.4
1534915542 react: 19.2.3
15350 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15543 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1535115544 transitivePeerDependencies:
1535215545 - expo
1535315546 - supports-color
......@@ -15355,119 +15548,119 @@ snapshots:
1535515548 expo-build-properties@56.0.16(expo@56.0.8):
1535615549 dependencies:
1535715550 '@expo/schema-utils': 56.0.1
15358 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15551 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1535915552 resolve-from: 5.0.0
1536015553 semver: 7.8.1
1536115554
15362 expo-camera@56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15555 expo-camera@56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1536315556 dependencies:
1536415557 barcode-detector: 3.2.0(@types/emscripten@1.41.5)
15365 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15558 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1536615559 react: 19.2.3
15367 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15560 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1536815561 transitivePeerDependencies:
1536915562 - '@types/emscripten'
1537015563
15371 expo-clipboard@56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15564 expo-clipboard@56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1537215565 dependencies:
15373 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15566 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1537415567 react: 19.2.3
15375 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15568 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1537615569
15377 expo-constants@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15570 expo-constants@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1537815571 dependencies:
1537915572 '@expo/env': 2.3.0
15380 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15381 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15573 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15574 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1538215575 transitivePeerDependencies:
1538315576 - supports-color
1538415577
1538515578 expo-crypto@56.0.4(expo@56.0.8):
1538615579 dependencies:
15387 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15580 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1538815581
15389 expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15582 expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1539015583 dependencies:
15391 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15392 expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15393 expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15584 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15585 expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15586 expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1539415587 expo-dev-menu-interface: 56.0.1(expo@56.0.8)
1539515588 expo-manifests: 56.0.4(expo@56.0.8)
1539615589 expo-updates-interface: 56.0.2(expo@56.0.8)
1539715590 transitivePeerDependencies:
1539815591 - react-native
1539915592
15400 expo-dev-launcher@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15593 expo-dev-launcher@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1540115594 dependencies:
1540215595 '@expo/schema-utils': 56.0.1
15403 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15404 expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15596 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15597 expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1540515598 expo-manifests: 56.0.4(expo@56.0.8)
15406 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15599 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1540715600
1540815601 expo-dev-menu-interface@56.0.1(expo@56.0.8):
1540915602 dependencies:
15410 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15603 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1541115604
15412 expo-dev-menu@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15605 expo-dev-menu@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1541315606 dependencies:
15414 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15607 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1541515608 expo-dev-menu-interface: 56.0.1(expo@56.0.8)
15416 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15609 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1541715610
1541815611 expo-eas-client@56.0.1: {}
1541915612
15420 expo-file-system@56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15613 expo-file-system@56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1542115614 dependencies:
15422 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15423 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15615 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15616 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1542415617
15425 expo-font@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15618 expo-font@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1542615619 dependencies:
15427 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15620 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1542815621 fontfaceobserver: 2.3.0
1542915622 react: 19.2.3
15430 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15623 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1543115624
15432 expo-glass-effect@56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15625 expo-glass-effect@56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1543315626 dependencies:
15434 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15627 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1543515628 react: 19.2.3
15436 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15629 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1543715630
1543815631 expo-haptics@56.0.3(expo@56.0.8):
1543915632 dependencies:
15440 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15633 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1544115634
1544215635 expo-image-loader@56.0.3(expo@56.0.8):
1544315636 dependencies:
15444 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15637 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1544515638
1544615639 expo-image-picker@56.0.15(expo@56.0.8):
1544715640 dependencies:
15448 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15641 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1544915642 expo-image-loader: 56.0.3(expo@56.0.8)
1545015643
1545115644 expo-json-utils@56.0.0: {}
1545215645
1545315646 expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3):
1545415647 dependencies:
15455 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15648 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1545615649 react: 19.2.3
1545715650
15458 expo-linking@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15651 expo-linking@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1545915652 dependencies:
15460 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15653 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1546115654 invariant: 2.2.4
1546215655 react: 19.2.3
15463 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15656 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1546415657 transitivePeerDependencies:
1546515658 - expo
1546615659 - supports-color
1546715660
1546815661 expo-manifests@56.0.4(expo@56.0.8):
1546915662 dependencies:
15470 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15663 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1547115664 expo-json-utils: 56.0.0
1547215665
1547315666 expo-modules-autolinking@56.0.14(typescript@6.0.3):
......@@ -15480,66 +15673,66 @@ snapshots:
1548015673 - supports-color
1548115674 - typescript
1548215675
15483 expo-modules-core@56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15676 expo-modules-core@56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1548415677 dependencies:
1548515678 '@expo/expo-modules-macros-plugin': 0.0.9
15486 expo-modules-jsi: 56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15679 expo-modules-jsi: 56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1548715680 invariant: 2.2.4
1548815681 react: 19.2.3
15489 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15682 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1549015683 optionalDependencies:
15491 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15684 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1549215685
15493 expo-modules-jsi@56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15686 expo-modules-jsi@56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1549415687 dependencies:
15495 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15688 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1549615689
1549715690 expo-network@56.0.5(expo@56.0.8)(react@19.2.3):
1549815691 dependencies:
15499 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15692 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1550015693 react: 19.2.3
1550115694
15502 expo-notifications@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
15695 expo-notifications@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
1550315696 dependencies:
1550415697 '@expo/image-utils': 0.10.1(typescript@6.0.3)
1550515698 abort-controller: 3.0.0
1550615699 badgin: 1.2.3
15507 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15700 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1550815701 expo-application: 56.0.3(expo@56.0.8)
15509 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15702 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1551015703 react: 19.2.3
15511 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15704 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1551215705 transitivePeerDependencies:
1551315706 - supports-color
1551415707 - typescript
1551515708
15516 expo-paste-input@0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15709 expo-paste-input@0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1551715710 dependencies:
15518 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15711 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1551915712 react: 19.2.3
15520 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15713 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1552115714
15522 expo-router@56.2.8(c021de11d02907bd585610408f5252e8):
15715 expo-router@56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310):
1552315716 dependencies:
15524 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15525 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15717 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15718 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1552615719 '@expo/schema-utils': 56.0.1
15527 '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
15720 '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
1552815721 '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3)
1552915722 '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
15530 '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15723 '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1553115724 '@testing-library/jest-dom': 6.9.1
1553215725 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
1553315726 client-only: 0.0.1
1553415727 color: 4.2.3
1553515728 debug: 4.4.3
1553615729 escape-string-regexp: 4.0.0
15537 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15538 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15539 expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15540 expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15730 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15731 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15732 expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15733 expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1554115734 expo-server: 56.0.4
15542 expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15735 expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1554315736 fast-deep-equal: 3.1.3
1554415737 invariant: 2.2.4
1554515738 nanoid: 3.3.12
......@@ -15547,18 +15740,18 @@ snapshots:
1554715740 react: 19.2.3
1554815741 react-fast-compare: 3.2.2
1554915742 react-is: 19.2.7
15550 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15551 react-native-drawer-layout: 4.2.4(react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15552 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15553 react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15743 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15744 react-native-drawer-layout: 4.2.4(0e9729601f58a7a7ae26c76fe6017455)
15745 react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15746 react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1555415747 server-only: 0.0.1
1555515748 sf-symbols-typescript: 2.2.0
1555615749 shallowequal: 1.1.0
1555715750 vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1555815751 optionalDependencies:
1555915752 react-dom: 19.2.3(react@19.2.3)
15560 react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15561 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15753 react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15754 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1556215755 transitivePeerDependencies:
1556315756 - '@babel/core'
1556415757 - '@testing-library/dom'
......@@ -15570,7 +15763,7 @@ snapshots:
1557015763
1557115764 expo-secure-store@56.0.4(expo@56.0.8):
1557215765 dependencies:
15573 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15766 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1557415767
1557515768 expo-server@56.0.4: {}
1557615769
......@@ -15578,7 +15771,7 @@ snapshots:
1557815771 dependencies:
1557915772 '@expo/config-plugins': 56.0.8(typescript@6.0.3)
1558015773 '@expo/image-utils': 0.10.1(typescript@6.0.3)
15581 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15774 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1558215775 xml2js: 0.6.0
1558315776 transitivePeerDependencies:
1558415777 - supports-color
......@@ -15586,20 +15779,20 @@ snapshots:
1558615779
1558715780 expo-structured-headers@56.0.0: {}
1558815781
15589 expo-symbols@56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15782 expo-symbols@56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1559015783 dependencies:
1559115784 '@expo-google-fonts/material-symbols': 0.4.38
15592 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15593 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15785 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15786 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1559415787 react: 19.2.3
15595 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15788 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1559615789 sf-symbols-typescript: 2.2.0
1559715790
1559815791 expo-updates-interface@56.0.2(expo@56.0.8):
1559915792 dependencies:
15600 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15793 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1560115794
15602 expo-updates@56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15795 expo-updates@56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1560315796 dependencies:
1560415797 '@expo/code-signing-certificates': 0.0.6
1560515798 '@expo/plist': 0.7.0
......@@ -15607,7 +15800,7 @@ snapshots:
1560715800 arg: 4.1.3
1560815801 chalk: 4.1.2
1560915802 debug: 4.4.3
15610 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15803 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1561115804 expo-eas-client: 56.0.1
1561215805 expo-manifests: 56.0.4(expo@56.0.8)
1561315806 expo-structured-headers: 56.0.0
......@@ -15617,25 +15810,25 @@ snapshots:
1561715810 ignore: 5.3.2
1561815811 nullthrows: 1.1.1
1561915812 react: 19.2.3
15620 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15813 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1562115814 resolve-from: 5.0.0
1562215815 optionalDependencies:
15623 expo-dev-client: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15816 expo-dev-client: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1562415817 transitivePeerDependencies:
1562515818 - supports-color
1562615819
15627 expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15820 expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1562815821 dependencies:
15629 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15630 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15822 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15823 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1563115824
15632 expo-widgets@56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584):
15825 expo-widgets@56.0.16(961c4aa6f32829b318e3c87ef20ad401):
1563315826 dependencies:
1563415827 '@expo/plist': 0.7.0
15635 '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
15636 expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15828 '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
15829 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
1563715830 react: 19.2.3
15638 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15831 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1563915832 transitivePeerDependencies:
1564015833 - '@babel/core'
1564115834 - '@types/react'
......@@ -15644,37 +15837,37 @@ snapshots:
1564415837 - react-native-reanimated
1564515838 - react-native-worklets
1564615839
15647 expo@56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6):
15840 expo@56.0.8(63f7aade424ad9e7b1154b679fa2a14d):
1564815841 dependencies:
1564915842 '@babel/runtime': 7.29.7
15650 '@expo/cli': 56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15843 '@expo/cli': 56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1565115844 '@expo/config': 56.0.9(typescript@6.0.3)
1565215845 '@expo/config-plugins': 56.0.8(typescript@6.0.3)
15653 '@expo/devtools': 56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15846 '@expo/devtools': 56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1565415847 '@expo/fingerprint': 0.19.3
1565515848 '@expo/local-build-cache-provider': 56.0.8(typescript@6.0.3)
15656 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15849 '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1565715850 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1565815851 '@expo/metro-config': 56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)
1565915852 '@ungap/structured-clone': 1.3.1
1566015853 babel-preset-expo: 56.0.14(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo-widgets@56.0.16)(expo@56.0.8)(react-refresh@0.14.2)
15661 expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
15662 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15663 expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15664 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15854 expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
15855 expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15856 expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15857 expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1566515858 expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3)
1566615859 expo-modules-autolinking: 56.0.14(typescript@6.0.3)
15667 expo-modules-core: 56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15860 expo-modules-core: 56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1566815861 pretty-format: 29.7.0
1566915862 react: 19.2.3
15670 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15863 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1567115864 react-refresh: 0.14.2
1567215865 whatwg-url-minimum: 0.1.2
1567315866 optionalDependencies:
15674 '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15675 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15867 '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15868 '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1567615869 react-dom: 19.2.3(react@19.2.3)
15677 react-native-webview: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15870 react-native-webview: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1567815871 transitivePeerDependencies:
1567915872 - '@babel/core'
1568015873 - bufferutil
......@@ -15862,6 +16055,10 @@ snapshots:
1586216055
1586316056 find-my-way-ts@0.1.6: {}
1586416057
16058 find-up@3.0.0:
16059 dependencies:
16060 locate-path: 3.0.0
16061
1586516062 flattie@1.1.1: {}
1586616063
1586716064 flow-enums-runtime@0.0.6: {}
......@@ -16388,6 +16585,8 @@ snapshots:
1638816585
1638916586 is-number@7.0.0: {}
1639016587
16588 is-obj@2.0.0: {}
16589
1639116590 is-plain-obj@4.1.0: {}
1639216591
1639316592 is-promise@4.0.0: {}
......@@ -16488,6 +16687,8 @@ snapshots:
1648816687
1648916688 json-schema-traverse@1.0.0: {}
1649016689
16690 json-schema-typed@7.0.3: {}
16691
1649116692 json-schema-typed@8.0.2: {}
1649216693
1649316694 json-stringify-safe@5.0.1:
......@@ -16715,6 +16916,11 @@ snapshots:
1671516916 lightningcss-win32-arm64-msvc: 1.32.0
1671616917 lightningcss-win32-x64-msvc: 1.32.0
1671716918
16919 locate-path@3.0.0:
16920 dependencies:
16921 p-locate: 3.0.0
16922 path-exists: 3.0.0
16923
1671816924 lodash.debounce@4.0.8: {}
1671916925
1672016926 lodash.escaperegexp@4.1.2: {}
......@@ -17363,6 +17569,8 @@ snapshots:
1736317569
1736417570 mimic-fn@2.1.0: {}
1736517571
17572 mimic-fn@3.1.0: {}
17573
1736617574 mimic-function@5.0.1: {}
1736717575
1736817576 mimic-response@1.0.1: {}
......@@ -17703,6 +17911,10 @@ snapshots:
1770317911
1770417912 p-cancelable@2.1.1: {}
1770517913
17914 p-limit@2.3.0:
17915 dependencies:
17916 p-try: 2.2.0
17917
1770617918 p-limit@3.1.0:
1770717919 dependencies:
1770817920 yocto-queue: 0.1.0
......@@ -17711,6 +17923,10 @@ snapshots:
1771117923 dependencies:
1771217924 yocto-queue: 1.2.2
1771317925
17926 p-locate@3.0.0:
17927 dependencies:
17928 p-limit: 2.3.0
17929
1771417930 p-queue@9.3.0:
1771517931 dependencies:
1771617932 eventemitter3: 5.0.4
......@@ -17718,6 +17934,8 @@ snapshots:
1771817934
1771917935 p-timeout@7.0.1: {}
1772017936
17937 p-try@2.2.0: {}
17938
1772117939 package-manager-detector@1.6.0: {}
1772217940
1772317941 pako@1.0.11: {}
......@@ -17755,6 +17973,8 @@ snapshots:
1775517973
1775617974 path-browserify@1.0.1: {}
1775717975
17976 path-exists@3.0.0: {}
17977
1775817978 path-expression-matcher@1.5.0: {}
1775917979
1776017980 path-is-absolute@1.0.1: {}
......@@ -17847,6 +18067,10 @@ snapshots:
1784718067
1784818068 pkce-challenge@5.0.1: {}
1784918069
18070 pkg-up@3.1.0:
18071 dependencies:
18072 find-up: 3.0.0
18073
1785018074 playwright-core@1.60.0: {}
1785118075
1785218076 plist@3.1.0:
......@@ -18061,102 +18285,102 @@ snapshots:
1806118285 transitivePeerDependencies:
1806218286 - supports-color
1806318287
18064 react-native-drawer-layout@4.2.4(react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18288 react-native-drawer-layout@4.2.4(0e9729601f58a7a7ae26c76fe6017455):
1806518289 dependencies:
1806618290 color: 4.2.3
1806718291 react: 19.2.3
18068 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18069 react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18070 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18292 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18293 react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18294 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1807118295 use-latest-callback: 0.2.6(react@19.2.3)
1807218296
18073 react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18297 react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1807418298 dependencies:
1807518299 '@egjs/hammerjs': 2.0.17
1807618300 '@types/react-test-renderer': 19.1.0
1807718301 hoist-non-react-statics: 3.3.2
1807818302 invariant: 2.2.4
1807918303 react: 19.2.3
18080 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18304 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1808118305
18082 react-native-image-viewing@0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18306 react-native-image-viewing@0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1808318307 dependencies:
1808418308 react: 19.2.3
18085 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18309 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1808618310
18087 react-native-is-edge-to-edge@1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18311 react-native-is-edge-to-edge@1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1808818312 dependencies:
1808918313 react: 19.2.3
18090 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18314 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1809118315
18092 react-native-keyboard-controller@1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18316 react-native-keyboard-controller@1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1809318317 dependencies:
1809418318 react: 19.2.3
18095 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18096 react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18097 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18319 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18320 react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18321 react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1809818322
18099 react-native-nitro-markdown@0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18323 react-native-nitro-markdown@0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1810018324 dependencies:
1810118325 react: 19.2.3
18102 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18103 react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18326 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18327 react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1810418328 optionalDependencies:
18105 react-native-svg: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18329 react-native-svg: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1810618330
18107 react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18331 react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1810818332 dependencies:
1810918333 react: 19.2.3
18110 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18334 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1811118335
18112 react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18336 react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1811318337 dependencies:
1811418338 react: 19.2.3
18115 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18116 react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18117 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18339 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18340 react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18341 react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1811818342 semver: 7.8.1
1811918343
18120 react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18344 react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1812118345 dependencies:
1812218346 react: 19.2.3
18123 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18347 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1812418348
18125 react-native-screens@4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18349 react-native-screens@4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1812618350 dependencies:
1812718351 react: 19.2.3
1812818352 react-freeze: 1.0.4(react@19.2.3)
18129 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18353 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1813018354 warn-once: 0.1.1
1813118355
18132 react-native-shiki-engine@0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18356 react-native-shiki-engine@0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1813318357 dependencies:
1813418358 '@shikijs/types': 4.2.0
1813518359 '@shikijs/vscode-textmate': 10.0.2
1813618360 react: 19.2.3
18137 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18361 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1813818362
18139 react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18363 react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1814018364 dependencies:
1814118365 css-select: 5.2.2
1814218366 css-tree: 1.1.3
1814318367 react: 19.2.3
18144 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18368 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1814518369 warn-once: 0.1.1
1814618370
18147 react-native-url-polyfill@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
18371 react-native-url-polyfill@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1814818372 dependencies:
18149 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18373 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1815018374 whatwg-url-without-unicode: 8.0.0-3
1815118375
18152 react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18376 react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1815318377 dependencies:
1815418378 escape-string-regexp: 4.0.0
1815518379 invariant: 2.2.4
1815618380 react: 19.2.3
18157 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18381 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1815818382
18159 react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18383 react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1816018384 dependencies:
1816118385 '@babel/core': 7.29.7
1816218386 '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7)
......@@ -18168,23 +18392,23 @@ snapshots:
1816818392 '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7)
1816918393 '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7)
1817018394 '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7)
18171 '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)
18395 '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1817218396 convert-source-map: 2.0.0
1817318397 react: 19.2.3
18174 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18398 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1817518399 semver: 7.8.1
1817618400 transitivePeerDependencies:
1817718401 - supports-color
1817818402
18179 react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6):
18403 react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6):
1818018404 dependencies:
1818118405 '@react-native/assets-registry': 0.85.3
1818218406 '@react-native/codegen': 0.85.3(@babel/core@7.29.7)
18183 '@react-native/community-cli-plugin': 0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6)
18407 '@react-native/community-cli-plugin': 0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1818418408 '@react-native/gradle-plugin': 0.85.3
1818518409 '@react-native/js-polyfills': 0.85.3
1818618410 '@react-native/normalize-colors': 0.85.3
18187 '@react-native/virtualized-lists': 0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18411 '@react-native/virtualized-lists': 0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1818818412 abort-controller: 3.0.0
1818918413 anser: 1.4.10
1819018414 ansi-regex: 5.0.1
......@@ -19100,6 +19324,8 @@ snapshots:
1910019324
1910119325 type-fest@0.7.1: {}
1910219326
19327 type-fest@2.19.0: {}
19328
1910319329 type-fest@5.7.0:
1910419330 dependencies:
1910519331 tagged-tag: 1.0.0
......@@ -19211,14 +19437,14 @@ snapshots:
1921119437
1921219438 universalify@2.0.1: {}
1921319439
19214 uniwind@1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0):
19440 uniwind@1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0):
1921519441 dependencies:
1921619442 '@tailwindcss/node': 4.2.1
1921719443 '@tailwindcss/oxide': 4.2.1
1921819444 culori: 4.0.2
1921919445 lightningcss: 1.30.1
1922019446 react: 19.2.3
19221 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
19447 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1922219448 tailwindcss: 4.3.0
1922319449
1922419450 unpipe@1.0.0: {}
pnpm-workspace.yaml+17
......@@ -6,6 +6,13 @@ packages:
66 - scripts
77
88catalog:
9 "@clerk/backend": 3.8.2-snapshot.v20260619001138
10 "@clerk/clerk-js": 6.18.2-snapshot.v20260619001138
11 "@clerk/electron": 0.0.1-snapshot.v20260619001138
12 "@clerk/electron-passkeys": 0.0.1-snapshot.v20260619001138
13 "@clerk/expo": 3.4.8-snapshot.v20260619001138
14 "@clerk/react": 6.10.4-snapshot.v20260619001138
15 "@clerk/shared": 4.19.2-snapshot.v20260619001138
916 "@effect/atom-react": 4.0.0-beta.78
1017 "@effect/openapi-generator": 4.0.0-beta.78
1118 "@effect/platform-bun": 4.0.0-beta.78
......@@ -40,6 +47,16 @@ onlyBuiltDependencies:
4047 - sharp
4148
4249overrides:
50 # Keep every Clerk consumer on the same snapshot train. Clerk publishes wallet
51 # auth integrations as required dependencies, but T3 Code does not support
52 # wallet auth, so keep that unused dependency tree out of installs.
53 "@clerk/backend": "catalog:"
54 "@clerk/clerk-js": "catalog:"
55 "@clerk/electron": "catalog:"
56 "@clerk/electron-passkeys": "catalog:"
57 "@clerk/expo": "catalog:"
58 "@clerk/react": "catalog:"
59 "@clerk/shared": "catalog:"
4360 "@clerk/clerk-js>@base-org/account": "-"
4461 "@clerk/clerk-js>@coinbase/wallet-sdk": "-"
4562 "@clerk/clerk-js>@solana/wallet-adapter-base": "-"
scripts/build-desktop-artifact.test.ts+104
......@@ -8,7 +8,11 @@ import * as Option from "effect/Option";
88import {
99 createStageWorkspaceConfig,
1010 createStagePnpmConfig,
11 createBuildConfig,
1112 DESKTOP_ASAR_UNPACK,
13 renderMacPasskeyEntitlements,
14 resolveClerkPasskeyNativeArtifacts,
15 resolveMacPasskeySigningConfiguration,
1216 resolveDesktopRuntimeDependencies,
1317 resolveFffNativeDependencies,
1418 resolveBuildOptions,
......@@ -175,6 +179,86 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
175179 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
176180 });
177181
182 it("derives macOS passkey signing configuration from the Clerk publishable key", () => {
183 const configuration = resolveMacPasskeySigningConfiguration({
184 T3CODE_APPLE_TEAM_ID: "abc1234567",
185 T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile",
186 T3CODE_CLERK_PUBLISHABLE_KEY: `pk_test_${btoa("example.clerk.accounts.dev$")}`,
187 });
188
189 assert.deepStrictEqual(configuration, {
190 appId: "com.t3tools.t3code",
191 teamId: "ABC1234567",
192 rpDomains: ["example.clerk.accounts.dev"],
193 provisioningProfilePath: "/tmp/t3code.provisionprofile",
194 });
195 });
196
197 it("normalizes explicit macOS passkey RP domains and renders required entitlements", () => {
198 const configuration = resolveMacPasskeySigningConfiguration({
199 T3CODE_APPLE_TEAM_ID: "ABC1234567",
200 T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile",
201 T3CODE_CLERK_PASSKEY_RP_DOMAINS:
202 " Clerk.Example.com,example.clerk.accounts.dev,clerk.example.com ",
203 });
204 const entitlements = renderMacPasskeyEntitlements(configuration);
205
206 assert.deepStrictEqual(configuration.rpDomains, [
207 "clerk.example.com",
208 "example.clerk.accounts.dev",
209 ]);
210 assert.include(entitlements, "<string>ABC1234567.com.t3tools.t3code</string>");
211 assert.include(entitlements, "<string>webcredentials:clerk.example.com</string>");
212 assert.include(entitlements, "<string>webcredentials:example.clerk.accounts.dev</string>");
213 assert.include(entitlements, "<key>com.apple.security.cs.allow-jit</key>");
214 });
215
216 it("rejects incomplete macOS passkey signing configuration", () => {
217 assert.throws(
218 () =>
219 resolveMacPasskeySigningConfiguration({
220 T3CODE_APPLE_TEAM_ID: "ABC1234567",
221 T3CODE_CLERK_PASSKEY_RP_DOMAINS: "example.clerk.accounts.dev",
222 }),
223 /T3CODE_MACOS_PROVISIONING_PROFILE/u,
224 );
225 assert.throws(
226 () =>
227 resolveMacPasskeySigningConfiguration({
228 T3CODE_APPLE_TEAM_ID: "ABC1234567",
229 T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile",
230 T3CODE_CLERK_PASSKEY_RP_DOMAINS: "https://example.clerk.accounts.dev/path",
231 }),
232 /Invalid passkey RP domain/u,
233 );
234 assert.throws(
235 () =>
236 resolveMacPasskeySigningConfiguration({
237 T3CODE_APPLE_TEAM_ID: "ABC1234567",
238 T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile",
239 T3CODE_CLERK_PASSKEY_RP_DOMAINS: "example.clerk.accounts.dev:8443",
240 }),
241 /Invalid passkey RP domain/u,
242 );
243 });
244
245 it.effect("adds passkey entitlements and both renderer protocols to signed macOS builds", () =>
246 Effect.gen(function* () {
247 const config = yield* createBuildConfig("mac", "dmg", "1.2.3", true, false, undefined, {
248 entitlementsPath: "/tmp/entitlements.mac.plist",
249 provisioningProfilePath: "/tmp/t3code.provisionprofile",
250 });
251
252 const mac = config.mac as Record<string, unknown>;
253 assert.equal(config.appId, "com.t3tools.t3code");
254 assert.equal(mac.entitlements, "/tmp/entitlements.mac.plist");
255 assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile");
256 assert.deepStrictEqual(mac.protocols, [
257 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },
258 ]);
259 }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
260 );
261
178262 it("promotes target fff binaries to direct staged dependencies", () => {
179263 assert.deepStrictEqual(resolveFffNativeDependencies("mac", "arm64", "0.9.4"), {
180264 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",
......@@ -192,6 +276,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
192276 });
193277 });
194278
279 it("resolves target Clerk passkey native artifacts", () => {
280 assert.deepStrictEqual(resolveClerkPasskeyNativeArtifacts("mac", "universal"), [
281 {
282 packageName: "@clerk/electron-passkeys-darwin-arm64",
283 binaryFileName: "electron-passkeys.darwin-arm64.node",
284 },
285 {
286 packageName: "@clerk/electron-passkeys-darwin-x64",
287 binaryFileName: "electron-passkeys.darwin-x64.node",
288 },
289 ]);
290 assert.deepStrictEqual(resolveClerkPasskeyNativeArtifacts("win", "x64"), [
291 {
292 packageName: "@clerk/electron-passkeys-win32-x64-msvc",
293 binaryFileName: "electron-passkeys.win32-x64-msvc.node",
294 },
295 ]);
296 assert.deepStrictEqual(resolveClerkPasskeyNativeArtifacts("linux", "x64"), []);
297 });
298
195299 it("falls back to the default mock update port when the configured port is blank", () => {
196300 assert.equal(resolveMockUpdateServerUrl(undefined), "http://localhost:3000");
197301 assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");
scripts/build-desktop-artifact.ts+232-3
......@@ -1,7 +1,10 @@
11#!/usr/bin/env node
22
3import { createRequire } from "node:module";
4
35import { fromYaml } from "@t3tools/shared/schemaYaml";
46import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
7import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
58import { resolveSpawnCommand } from "@t3tools/shared/shell";
69import rootPackageJson from "../package.json" with { type: "json" };
710import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
......@@ -9,6 +12,7 @@ import serverPackageJson from "../apps/server/package.json" with { type: "json"
912
1013import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
1114import { getDefaultBuildArch } from "./lib/build-target-arch.ts";
15import { loadRepoEnv } from "./lib/public-config.ts";
1216import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
1317
1418import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
......@@ -27,6 +31,8 @@ import { Command, Flag } from "effect/unstable/cli";
2731import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2832
2933const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const;
34const DESKTOP_APP_ID = "com.t3tools.t3code";
35const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u;
3036
3137const BuildPlatform = Schema.Literals(["mac", "linux", "win"]);
3238const BuildArch = Schema.Literals(["arm64", "x64", "universal"]);
......@@ -293,6 +299,121 @@ interface StagePackageJson {
293299export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;
294300export const DESKTOP_ASAR_UNPACK = ["node_modules/@ff-labs/fff-bin-*/**/*"] as const;
295301
302export interface MacPasskeySigningConfiguration {
303 readonly appId: string;
304 readonly teamId: string;
305 readonly rpDomains: readonly string[];
306 readonly provisioningProfilePath: string;
307}
308
309function normalizePasskeyRpDomain(value: string): string {
310 const normalized = value.trim().toLowerCase();
311 let parsed: URL;
312 try {
313 parsed = new URL(`https://${normalized}`);
314 } catch {
315 throw new Error(`Invalid passkey RP domain: ${value}`);
316 }
317
318 if (
319 normalized.length === 0 ||
320 parsed.host !== normalized ||
321 parsed.username.length > 0 ||
322 parsed.password.length > 0 ||
323 parsed.port.length > 0 ||
324 parsed.pathname !== "/" ||
325 parsed.search.length > 0 ||
326 parsed.hash.length > 0
327 ) {
328 throw new Error(`Invalid passkey RP domain: ${value}`);
329 }
330
331 return parsed.hostname;
332}
333
334export function resolveMacPasskeySigningConfiguration(
335 env: Readonly<Record<string, string | undefined>>,
336): MacPasskeySigningConfiguration {
337 const teamId = env.T3CODE_APPLE_TEAM_ID?.trim().toUpperCase() ?? "";
338 if (!APPLE_TEAM_ID_PATTERN.test(teamId)) {
339 throw new Error("T3CODE_APPLE_TEAM_ID must be a 10-character Apple Developer Team ID.");
340 }
341
342 const provisioningProfilePath = env.T3CODE_MACOS_PROVISIONING_PROFILE?.trim() ?? "";
343 if (provisioningProfilePath.length === 0) {
344 throw new Error(
345 "T3CODE_MACOS_PROVISIONING_PROFILE must point to an Associated Domains provisioning profile.",
346 );
347 }
348
349 const configuredRpDomains = env.T3CODE_CLERK_PASSKEY_RP_DOMAINS?.trim();
350 let rpDomains: readonly string[];
351 if (configuredRpDomains) {
352 rpDomains = configuredRpDomains.split(",").map(normalizePasskeyRpDomain);
353 } else {
354 const publishableKey = env.T3CODE_CLERK_PUBLISHABLE_KEY?.trim();
355 if (!publishableKey) {
356 throw new Error(
357 "T3CODE_CLERK_PUBLISHABLE_KEY or T3CODE_CLERK_PASSKEY_RP_DOMAINS is required for signed macOS passkey builds.",
358 );
359 }
360 rpDomains = [
361 normalizePasskeyRpDomain(clerkFrontendApiHostnameFromPublishableKey(publishableKey)),
362 ];
363 }
364
365 const uniqueRpDomains = [...new Set(rpDomains)];
366 if (uniqueRpDomains.length === 0) {
367 throw new Error("At least one Clerk passkey RP domain is required.");
368 }
369
370 return {
371 appId: DESKTOP_APP_ID,
372 teamId,
373 rpDomains: uniqueRpDomains,
374 provisioningProfilePath,
375 };
376}
377
378function escapeXml(value: string): string {
379 return value
380 .replaceAll("&", "&amp;")
381 .replaceAll("<", "&lt;")
382 .replaceAll(">", "&gt;")
383 .replaceAll('"', "&quot;")
384 .replaceAll("'", "&apos;");
385}
386
387export function renderMacPasskeyEntitlements(
388 configuration: MacPasskeySigningConfiguration,
389): string {
390 const associatedDomains = configuration.rpDomains
391 .map((domain) => ` <string>webcredentials:${escapeXml(domain)}</string>`)
392 .join("\n");
393
394 return `<?xml version="1.0" encoding="UTF-8"?>
395<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
396<plist version="1.0">
397 <dict>
398 <key>com.apple.application-identifier</key>
399 <string>${escapeXml(`${configuration.teamId}.${configuration.appId}`)}</string>
400 <key>com.apple.developer.team-identifier</key>
401 <string>${escapeXml(configuration.teamId)}</string>
402 <key>com.apple.developer.associated-domains</key>
403 <array>
404${associatedDomains}
405 </array>
406 <key>com.apple.security.cs.allow-jit</key>
407 <true/>
408 <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
409 <true/>
410 <key>com.apple.security.cs.disable-library-validation</key>
411 <true/>
412 </dict>
413</plist>
414`;
415}
416
296417export function resolveFffNativeDependencies(
297418 platform: typeof BuildPlatform.Type,
298419 arch: typeof BuildArch.Type,
......@@ -319,6 +440,63 @@ export function resolveFffNativeDependencies(
319440 );
320441}
321442
443export interface ClerkPasskeyNativeArtifact {
444 readonly packageName: string;
445 readonly binaryFileName: string;
446}
447
448export function resolveClerkPasskeyNativeArtifacts(
449 platform: typeof BuildPlatform.Type,
450 arch: typeof BuildArch.Type,
451): readonly ClerkPasskeyNativeArtifact[] {
452 const architectures = arch === "universal" ? (["arm64", "x64"] as const) : [arch];
453
454 if (platform === "mac") {
455 return architectures.map((architecture) => ({
456 packageName: `@clerk/electron-passkeys-darwin-${architecture}`,
457 binaryFileName: `electron-passkeys.darwin-${architecture}.node`,
458 }));
459 }
460
461 if (platform === "win") {
462 return architectures.map((architecture) => ({
463 packageName: `@clerk/electron-passkeys-win32-${architecture}-msvc`,
464 binaryFileName: `electron-passkeys.win32-${architecture}-msvc.node`,
465 }));
466 }
467
468 return [];
469}
470
471// pnpm nests the architecture package under @clerk/electron-passkeys, while electron-builder only
472// retains collected top-level dependencies. The SDK loader checks beside index.js first, so stage
473// the binary there and let electron-builder's native-addon handling unpack it from the ASAR.
474const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinaries")(function* (
475 stageAppDir: string,
476 platform: typeof BuildPlatform.Type,
477 arch: typeof BuildArch.Type,
478) {
479 const fs = yield* FileSystem.FileSystem;
480 const path = yield* Path.Path;
481 const packageEntryPath = yield* fs.realPath(
482 path.join(stageAppDir, "node_modules", "@clerk", "electron-passkeys", "index.js"),
483 );
484 const packageDir = path.dirname(packageEntryPath);
485 const packageRequire = createRequire(packageEntryPath);
486
487 for (const artifact of resolveClerkPasskeyNativeArtifacts(platform, arch)) {
488 const sourcePath = yield* Effect.try({
489 try: () => packageRequire.resolve(artifact.packageName),
490 catch: (cause) =>
491 new BuildScriptError({
492 message: `Clerk passkey native package is missing: ${artifact.packageName}`,
493 cause,
494 }),
495 });
496 yield* fs.copyFile(sourcePath, path.join(packageDir, artifact.binaryFileName));
497 }
498});
499
322500export function createStageWorkspaceConfig(
323501 platform: typeof BuildPlatform.Type,
324502 arch: typeof BuildArch.Type,
......@@ -739,16 +917,22 @@ export function resolveDesktopProductName(version: string): string {
739917 : (desktopPackageJson.productName ?? "T3 Code");
740918}
741919
742const createBuildConfig = Effect.fn("createBuildConfig")(function* (
920export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
743921 platform: typeof BuildPlatform.Type,
744922 target: string,
745923 version: string,
746924 signed: boolean,
747925 mockUpdates: boolean,
748926 mockUpdateServerPort: number | undefined,
927 macPasskeySigning:
928 | {
929 readonly entitlementsPath: string;
930 readonly provisioningProfilePath: string;
931 }
932 | undefined,
749933) {
750934 const buildConfig: Record<string, unknown> = {
751 appId: "com.t3tools.t3code",
935 appId: DESKTOP_APP_ID,
752936 productName: resolveDesktopProductName(version),
753937 artifactName: "T3-Code-${version}-${arch}.${ext}",
754938 asarUnpack: [...DESKTOP_ASAR_UNPACK],
......@@ -777,9 +961,15 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (
777961 protocols: [
778962 {
779963 name: "T3 Code",
780 schemes: ["t3code"],
964 schemes: ["t3code", "t3code-dev"],
781965 },
782966 ],
967 ...(macPasskeySigning
968 ? {
969 entitlements: macPasskeySigning.entitlementsPath,
970 provisioningProfile: macPasskeySigning.provisioningProfilePath,
971 }
972 : {}),
783973 };
784974 }
785975
......@@ -956,6 +1146,38 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
9561146 // electron-builder is filtering out stageResourcesDir directory in the AppImage for production
9571147 yield* fs.copy(stageResourcesDir, path.join(stageAppDir, "apps/desktop/prod-resources"));
9581148
1149 const configuredMacPasskeySigning =
1150 options.platform === "mac" && options.signed
1151 ? yield* Effect.try({
1152 try: () => resolveMacPasskeySigningConfiguration(loadRepoEnv({ repoRoot })),
1153 catch: (cause) =>
1154 new BuildScriptError({
1155 message: cause instanceof Error ? cause.message : String(cause),
1156 cause,
1157 }),
1158 })
1159 : undefined;
1160 const macPasskeySigning = configuredMacPasskeySigning
1161 ? {
1162 ...configuredMacPasskeySigning,
1163 provisioningProfilePath: path.resolve(
1164 repoRoot,
1165 configuredMacPasskeySigning.provisioningProfilePath,
1166 ),
1167 }
1168 : undefined;
1169 const macEntitlementsPath = macPasskeySigning
1170 ? path.join(stageAppDir, "entitlements.mac.plist")
1171 : undefined;
1172 if (macPasskeySigning && macEntitlementsPath) {
1173 if (!(yield* fs.exists(macPasskeySigning.provisioningProfilePath))) {
1174 return yield* new BuildScriptError({
1175 message: `macOS provisioning profile not found: ${macPasskeySigning.provisioningProfilePath}`,
1176 });
1177 }
1178 yield* fs.writeFileString(macEntitlementsPath, renderMacPasskeyEntitlements(macPasskeySigning));
1179 }
1180
9591181 const stageDependencies = {
9601182 ...resolvedServerDependencies,
9611183 ...resolvedDesktopRuntimeDependencies,
......@@ -983,6 +1205,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
9831205 options.signed,
9841206 options.mockUpdates,
9851207 options.mockUpdateServerPort,
1208 macPasskeySigning && macEntitlementsPath
1209 ? {
1210 entitlementsPath: macEntitlementsPath,
1211 provisioningProfilePath: macPasskeySigning.provisioningProfilePath,
1212 }
1213 : undefined,
9861214 ),
9871215 dependencies: stageDependencies,
9881216 devDependencies: {
......@@ -1014,6 +1242,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
10141242 }),
10151243 { label: "vp install --prod", verbose: options.verbose },
10161244 );
1245 yield* stageClerkPasskeyNativeBinaries(stageAppDir, options.platform, options.arch);
10171246
10181247 // electron-builder treats several set-but-empty variables (e.g. CSC_LINK="")
10191248 // as enabled, so copy the host env and scrub empty values instead of relying