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 @@...@@ -8,6 +8,13 @@
8# T3CODE_CLERK_JWT_TEMPLATE=t3-relay8# T3CODE_CLERK_JWT_TEMPLATE=t3-relay
9# T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauthapp_...9# 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
11# Get this from your relay deployment. `infra/relay` deploys update it automatically.18# Get this from your relay deployment. `infra/relay` deploys update it automatically.
12# T3CODE_RELAY_URL=https://relay.example.com19# T3CODE_RELAY_URL=https://relay.example.com
1320
.github/workflows/ci.yml+1
...@@ -38,6 +38,7 @@ jobs:...@@ -38,6 +38,7 @@ jobs:
38 run: |38 run: |
39 test -f apps/desktop/dist-electron/preload.cjs39 test -f apps/desktop/dist-electron/preload.cjs
40 grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.cjs40 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
42 test:43 test:
43 name: Test44 name: Test
.github/workflows/release.yml+15
...@@ -425,6 +425,9 @@ jobs:...@@ -425,6 +425,9 @@ jobs:
425 APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}425 APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
426 APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}426 APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
427 APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}427 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 }}
428 AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}431 AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
429 AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}432 AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
430 AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}433 AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
...@@ -452,9 +455,21 @@ jobs:...@@ -452,9 +455,21 @@ jobs:
452455
453 if [[ "${{ matrix.platform }}" == "mac" ]]; then456 if [[ "${{ matrix.platform }}" == "mac" ]]; then
454 if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then457 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
455 key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"463 key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
456 printf '%s' "$APPLE_API_KEY" > "$key_path"464 printf '%s' "$APPLE_API_KEY" > "$key_path"
457 export APPLE_API_KEY="$key_path"465 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
458 echo "macOS signing enabled."473 echo "macOS signing enabled."
459 args+=(--signed)474 args+=(--signed)
460 else475 else
apps/desktop/package.json+3
...@@ -12,6 +12,8 @@...@@ -12,6 +12,8 @@
12 "smoke-test": "node scripts/smoke-test.mjs"12 "smoke-test": "node scripts/smoke-test.mjs"
13 },13 },
14 "dependencies": {14 "dependencies": {
15 "@clerk/electron": "catalog:",
16 "@clerk/electron-passkeys": "catalog:",
15 "@effect/platform-node": "catalog:",17 "@effect/platform-node": "catalog:",
16 "@t3tools/client-runtime": "workspace:*",18 "@t3tools/client-runtime": "workspace:*",
17 "@t3tools/contracts": "workspace:*",19 "@t3tools/contracts": "workspace:*",
...@@ -20,6 +22,7 @@...@@ -20,6 +22,7 @@
20 "@t3tools/tailscale": "workspace:*",22 "@t3tools/tailscale": "workspace:*",
21 "effect": "catalog:",23 "effect": "catalog:",
22 "electron": "41.5.0",24 "electron": "41.5.0",
25 "electron-store": "^8.2.0",
23 "electron-updater": "^6.6.2",26 "electron-updater": "^6.6.2",
24 "playwright-core": "1.60.0",27 "playwright-core": "1.60.0",
25 "react-grab": "^0.1.32"28 "react-grab": "^0.1.32"
apps/desktop/scripts/electron-launcher.mjs+1-21
...@@ -31,20 +31,12 @@ export const APP_BUNDLE_ID = isDevelopment...@@ -31,20 +31,12 @@ export const APP_BUNDLE_ID = isDevelopment
31 ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`31 ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
32 : "com.t3tools.t3code";32 : "com.t3tools.t3code";
33const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];33const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
34const LAUNCHER_VERSION = 11;34const LAUNCHER_VERSION = 12;
35const defaultIconPath = join(desktopDir, "resources", "icon.icns");35const defaultIconPath = join(desktopDir, "resources", "icon.icns");
36const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png");36const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png");
37// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.37// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.
38const hostPlatform = NodeOS.platform();38const 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
48function setPlistString(plistPath, key, value) {40function setPlistString(plistPath, key, value) {
49 const replaceResult = spawnSync("plutil", ["-replace", key, "-string", value, plistPath], {41 const replaceResult = spawnSync("plutil", ["-replace", key, "-string", value, plistPath], {
50 encoding: "utf8",42 encoding: "utf8",
...@@ -100,7 +92,6 @@ function shellSingleQuote(value) {...@@ -100,7 +92,6 @@ function shellSingleQuote(value) {
10092
101function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {93function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
102 const mainEntryPath = join(desktopDir, "dist-electron", "main.cjs");94 const mainEntryPath = join(desktopDir, "dist-electron", "main.cjs");
103 const protocolCallbackUrl = `http://127.0.0.1:${resolveDevelopmentProtocolCallbackPort()}/auth/callback`;
104 const envEntries = [95 const envEntries = [
105 ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL],96 ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL],
106 ["T3CODE_PORT", process.env.T3CODE_PORT],97 ["T3CODE_PORT", process.env.T3CODE_PORT],
...@@ -109,23 +100,12 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {...@@ -109,23 +100,12 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
109 ["T3CODE_OTLP_TRACES_URL", process.env.T3CODE_OTLP_TRACES_URL],100 ["T3CODE_OTLP_TRACES_URL", process.env.T3CODE_OTLP_TRACES_URL],
110 ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS],101 ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS],
111 ["T3CODE_DESKTOP_APP_USER_MODEL_ID", APP_BUNDLE_ID],102 ["T3CODE_DESKTOP_APP_USER_MODEL_ID", APP_BUNDLE_ID],
112 ["T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED", "1"],
113 ["T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL", protocolCallbackUrl],
114 ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);103 ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);
115 writeFileSync(104 writeFileSync(
116 targetBinaryPath,105 targetBinaryPath,
117 [106 [
118 "#!/bin/sh",107 "#!/bin/sh",
119 ...envEntries.map(([name, value]) => `export ${name}=${shellSingleQuote(value)}`),108 ...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",
129 `exec ${shellSingleQuote(electronBinaryPath)} --t3code-dev-root=${shellSingleQuote(desktopDir)} ${shellSingleQuote(mainEntryPath)} "$@"`,109 `exec ${shellSingleQuote(electronBinaryPath)} --t3code-dev-root=${shellSingleQuote(desktopDir)} ${shellSingleQuote(mainEntryPath)} "$@"`,
130 "",110 "",
131 ].join("\n"),111 ].join("\n"),
apps/desktop/src/app/DesktopApp.ts+13-5
...@@ -11,7 +11,7 @@ import * as ElectronDialog from "../electron/ElectronDialog.ts";...@@ -11,7 +11,7 @@ import * as ElectronDialog from "../electron/ElectronDialog.ts";
11import * as ElectronProtocol from "../electron/ElectronProtocol.ts";11import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
12import { installDesktopIpcHandlers } from "../ipc/DesktopIpcHandlers.ts";12import { installDesktopIpcHandlers } from "../ipc/DesktopIpcHandlers.ts";
13import * as DesktopAppIdentity from "./DesktopAppIdentity.ts";13import * as DesktopAppIdentity from "./DesktopAppIdentity.ts";
14import * as DesktopCloudAuth from "./DesktopCloudAuth.ts";14import * as DesktopClerk from "./DesktopClerk.ts";
15import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts";15import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts";
16import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts";16import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts";
17import * as DesktopEnvironment from "./DesktopEnvironment.ts";17import * as DesktopEnvironment from "./DesktopEnvironment.ts";
...@@ -163,6 +163,16 @@ const bootstrap = Effect.gen(function* () {...@@ -163,6 +163,16 @@ const bootstrap = Effect.gen(function* () {
163 }163 }
164 const serverExposureState = yield* serverExposure.configureFromSettings({ port: backendPort });164 const serverExposureState = yield* serverExposure.configureFromSettings({ port: backendPort });
165 const backendConfig = yield* serverExposure.backendConfig;165 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 });
166 yield* logBootstrapInfo("bootstrap resolved backend endpoint", {176 yield* logBootstrapInfo("bootstrap resolved backend endpoint", {
167 baseUrl: backendConfig.httpBaseUrl.href,177 baseUrl: backendConfig.httpBaseUrl.href,
168 });178 });
...@@ -189,9 +199,8 @@ const startup = Effect.gen(function* () {...@@ -189,9 +199,8 @@ const startup = Effect.gen(function* () {
189 const appIdentity = yield* DesktopAppIdentity.DesktopAppIdentity;199 const appIdentity = yield* DesktopAppIdentity.DesktopAppIdentity;
190 const applicationMenu = yield* DesktopApplicationMenu.DesktopApplicationMenu;200 const applicationMenu = yield* DesktopApplicationMenu.DesktopApplicationMenu;
191 const electronApp = yield* ElectronApp.ElectronApp;201 const electronApp = yield* ElectronApp.ElectronApp;
192 const electronProtocol = yield* ElectronProtocol.ElectronProtocol;
193 const lifecycle = yield* DesktopLifecycle.DesktopLifecycle;202 const lifecycle = yield* DesktopLifecycle.DesktopLifecycle;
194 const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;203 const clerk = yield* DesktopClerk.DesktopClerk;
195 const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment;204 const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment;
196 const desktopSettings = yield* DesktopAppSettings.DesktopAppSettings;205 const desktopSettings = yield* DesktopAppSettings.DesktopAppSettings;
197 const updates = yield* DesktopUpdates.DesktopUpdates;206 const updates = yield* DesktopUpdates.DesktopUpdates;
...@@ -209,7 +218,7 @@ const startup = Effect.gen(function* () {...@@ -209,7 +218,7 @@ const startup = Effect.gen(function* () {
209218
210 yield* appIdentity.configure;219 yield* appIdentity.configure;
211 yield* lifecycle.register;220 yield* lifecycle.register;
212 yield* cloudAuth.configure;221 yield* clerk.configure;
213222
214 yield* electronApp.whenReady.pipe(223 yield* electronApp.whenReady.pipe(
215 Effect.withSpan("desktop.electron.whenReady"),224 Effect.withSpan("desktop.electron.whenReady"),
...@@ -218,7 +227,6 @@ const startup = Effect.gen(function* () {...@@ -218,7 +227,6 @@ const startup = Effect.gen(function* () {
218 yield* logStartupInfo("app ready");227 yield* logStartupInfo("app ready");
219 yield* appIdentity.configure;228 yield* appIdentity.configure;
220 yield* applicationMenu.configure;229 yield* applicationMenu.configure;
221 yield* electronProtocol.registerDesktopFileProtocol;
222 yield* updates.configure;230 yield* updates.configure;
223 yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause)));231 yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause)));
224}).pipe(Effect.withSpan("desktop.startup"));232}).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 @@...@@ -1,105 +1,132 @@
1import { assert, describe, it } from "@effect/vitest";1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5import type * as Electron from "electron";
6import { beforeEach, vi } from "vite-plus/test";3import { beforeEach, vi } from "vite-plus/test";
74
8const { registerFileProtocolMock, registerSchemesAsPrivilegedMock, unregisterProtocolMock } =5const { handleMock, netFetchMock, unhandleMock } = vi.hoisted(() => ({
9 vi.hoisted(() => ({6 handleMock: vi.fn(),
10 registerFileProtocolMock: vi.fn(),7 netFetchMock: vi.fn(),
11 registerSchemesAsPrivilegedMock: vi.fn(),8 unhandleMock: vi.fn(),
12 unregisterProtocolMock: vi.fn(),9}));
13 }));
1410
15vi.mock("electron", () => ({11vi.mock("electron", () => ({
16 protocol: {12 net: { fetch: netFetchMock },
17 registerFileProtocol: registerFileProtocolMock,13 protocol: { handle: handleMock, unhandle: unhandleMock },
18 registerSchemesAsPrivileged: registerSchemesAsPrivilegedMock,
19 unregisterProtocol: unregisterProtocolMock,
20 },
21}));14}));
2215
23import * as ElectronProtocol from "./ElectronProtocol.ts";16import * as ElectronProtocol from "./ElectronProtocol.ts";
2417
25describe("ElectronProtocol", () => {18describe("ElectronProtocol", () => {
26 beforeEach(() => {19 beforeEach(() => {
27 registerFileProtocolMock.mockReset();20 handleMock.mockReset();
28 registerSchemesAsPrivilegedMock.mockReset();21 netFetchMock.mockReset();
29 unregisterProtocolMock.mockReset();22 unhandleMock.mockReset();
30 });23 });
3124
32 it("normalizes safe desktop protocol pathnames", () => {25 it.effect("proxies the stable renderer origin to the current app server", () =>
33 assert.equal(26 Effect.gen(function* () {
34 Option.getOrNull(ElectronProtocol.normalizeDesktopProtocolPathname("/settings/./general")),27 let handler: ((request: Request) => Promise<Response>) | undefined;
35 "settings/general",28 handleMock.mockImplementation((_scheme, nextHandler) => {
36 );29 handler = nextHandler;
37 assert.isTrue(Option.isNone(ElectronProtocol.normalizeDesktopProtocolPathname("/../secret")));30 });
38 });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", () =>67 assert.deepEqual(
41 Effect.scoped(68 handleMock.mock.calls.map((call) => call[0]),
42 Layer.build(ElectronProtocol.layerSchemePrivileges).pipe(69 ["t3code-dev"],
43 Effect.andThen(70 );
44 Effect.sync(() => {71 assert.equal(netFetchMock.mock.calls[0]?.[0], "http://127.0.0.1:3773/api/health?verbose=1");
45 assert.deepEqual(registerSchemesAsPrivilegedMock.mock.calls, [72 assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]);
46 [73 }).pipe(Effect.provide(ElectronProtocol.layer)),
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 ),
64 );74 );
6575
66 it.effect("scopes registered file protocols", () =>76 it.effect("rejects custom protocol requests for another host", () =>
67 Effect.gen(function* () {77 Effect.gen(function* () {
68 let capturedHandler:78 let handler: ((request: Request) => Promise<Response>) | undefined;
69 | ((79 handleMock.mockImplementation((_scheme, nextHandler) => {
70 request: Electron.ProtocolRequest,80 handler = nextHandler;
71 callback: (response: Electron.ProtocolResponse) => void,
72 ) => void)
73 | undefined;
74
75 registerFileProtocolMock.mockImplementation((_scheme, handler) => {
76 capturedHandler = handler;
77 return true;
78 });81 });
7982
80 const response = yield* Effect.scoped(83 const response = yield* Effect.scoped(
81 Effect.gen(function* () {84 Effect.gen(function* () {
82 const electronProtocol = yield* ElectronProtocol.ElectronProtocol;85 const protocol = yield* ElectronProtocol.ElectronProtocol;
83 yield* electronProtocol.registerFileProtocol({86 yield* protocol.registerDesktopProtocol({
84 scheme: "t3",87 scheme: "t3code",
85 handler: () => Effect.succeed({ path: "/app/index.html" }),88 targetOrigin: new URL("http://127.0.0.1:3773/"),
86 });89 backendOrigin: new URL("http://127.0.0.1:3773/"),
8790 clerkFrontendApiHostname: undefined,
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 );
93 });91 });
92 return yield* Effect.promise(() => handler!(new Request("t3code://other/")));
94 }),93 }),
95 );94 );
9695
97 assert.deepEqual(response, { path: "/app/index.html" });96 assert.equal(response.status, 404);
98 assert.deepEqual(97 assert.equal(netFetchMock.mock.calls.length, 0);
99 registerFileProtocolMock.mock.calls.map((call) => call[0]),
100 ["t3"],
101 );
102 assert.deepEqual(unregisterProtocolMock.mock.calls, [["t3"]]);
103 }).pipe(Effect.provide(ElectronProtocol.layer)),98 }).pipe(Effect.provide(ElectronProtocol.layer)),
104 );99 );
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 });
105});132});
apps/desktop/src/electron/ElectronProtocol.ts+90-216
...@@ -1,18 +1,27 @@...@@ -1,18 +1,27 @@
1import * as Cause from "effect/Cause";
2import * as Context from "effect/Context";1import * as Context from "effect/Context";
3import * as Data from "effect/Data";2import * as Data from "effect/Data";
4import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Layer from "effect/Layer";4import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
8import * as Ref from "effect/Ref";5import * as Ref from "effect/Ref";
9import * as Scope from "effect/Scope";6import * as Scope from "effect/Scope";
107
11import * as Electron from "electron";8import * 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
17export class ElectronProtocolRegistrationError extends Data.TaggedError(26export class ElectronProtocolRegistrationError extends Data.TaggedError(
18 "ElectronProtocolRegistrationError",27 "ElectronProtocolRegistrationError",
...@@ -21,252 +30,117 @@ export class ElectronProtocolRegistrationError extends Data.TaggedError(...@@ -21,252 +30,117 @@ export class ElectronProtocolRegistrationError extends Data.TaggedError(
21 readonly cause: unknown;30 readonly cause: unknown;
22}> {31}> {
23 override get message() {32 override get message() {
24 return `Failed to register ${this.scheme}: file protocol.`;33 return `Failed to register ${this.scheme}: protocol.`;
25 }34 }
26}35}
2736
28export class ElectronProtocolStaticBundleMissingError extends Data.TaggedError(37export interface DesktopProtocolRegistrationInput {
29 "ElectronProtocolStaticBundleMissingError",38 readonly scheme: string;
30)<{}> {39 readonly targetOrigin: URL;
31 override get message() {40 readonly backendOrigin: URL;
32 return "Desktop static bundle missing. Build apps/server (with bundled client) first.";41 readonly clerkFrontendApiHostname: string | undefined;
33 }
34}42}
3543
36export interface ElectronProtocolShape {44export interface ElectronProtocolShape {
37 readonly registerFileProtocol: <E, R>(input: {45 readonly registerDesktopProtocol: (
38 readonly scheme: string;46 input: DesktopProtocolRegistrationInput,
39 readonly handler: (47 ) => Effect.Effect<void, ElectronProtocolRegistrationError, Scope.Scope>;
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 >;
52}48}
5349
54export class ElectronProtocol extends Context.Service<ElectronProtocol, ElectronProtocolShape>()(50export class ElectronProtocol extends Context.Service<ElectronProtocol, ElectronProtocolShape>()(
55 "@t3tools/desktop/electron/ElectronProtocol",51 "@t3tools/desktop/electron/ElectronProtocol",
56) {}52) {}
5753
58export function normalizeDesktopProtocolPathname(rawPath: string): Option.Option<string> {54export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrationInput): string {
59 const segments: string[] = [];55 const clerkOrigin = input.clerkFrontendApiHostname
60 for (const segment of rawPath.split("/")) {56 ? `https://${input.clerkFrontendApiHostname}`
61 if (segment.length === 0 || segment === ".") {57 : undefined;
62 continue;58 const scriptSources = [
63 }59 "'self'",
64 if (segment === "..") {60 "'unsafe-inline'",
65 return Option.none();61 ...(clerkOrigin ? [clerkOrigin] : []),
66 }62 "https://challenges.cloudflare.com",
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"),
98 ];63 ];
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")(65 // The renderer connects directly to user-configured environments in addition to
111 function* (66 // the build-configured Clerk, relay, and OTLP endpoints. Those environment
112 staticRoot: string,67 // origins are not known when this response policy is created, so restrict
113 requestUrl: string,68 // connections by the network schemes the client supports instead of by host.
114 ): Effect.fn.Return<string, never, FileSystem.FileSystem | DesktopEnvironment> {69 const connectSources = ["'self'", "http:", "https:", "ws:", "wss:"];
115 const fileSystem = yield* FileSystem.FileSystem;70
116 const environment = yield* DesktopEnvironment;71 return [
117 const url = new URL(requestUrl);72 "default-src 'self'",
118 const rawPath = decodeURIComponent(url.pathname);73 `script-src ${scriptSources.join(" ")}`,
119 const normalizedPath = normalizeDesktopProtocolPathname(rawPath);74 `connect-src ${connectSources.join(" ")}`,
120 if (Option.isNone(normalizedPath)) {75 `img-src 'self' ${input.scheme}: blob: data: http: https:`,
121 return environment.path.join(staticRoot, "index.html");76 "style-src 'self' 'unsafe-inline'",
122 }77 `font-src 'self' ${input.scheme}: data:`,
12378 "worker-src 'self' blob:",
124 const requestedPath = normalizedPath.value.length > 0 ? normalizedPath.value : "index.html";79 "frame-src 'self' https://challenges.cloudflare.com",
125 const resolvedPath = environment.path.join(staticRoot, requestedPath);80 "form-action 'self'",
12681 ].join("; ");
127 if (environment.path.extname(resolvedPath)) {82}
128 return resolvedPath;
129 }
13083
131 const nestedIndex = environment.path.join(resolvedPath, "index.html");84function withContentSecurityPolicy(response: Response, policy: string): Response {
132 const nestedIndexExists = yield* fileSystem85 const headers = new Headers(response.headers);
133 .exists(nestedIndex)86 headers.set("Content-Security-Policy", policy);
134 .pipe(Effect.orElseSucceed(() => false));87 return new Response(response.body, {
135 if (nestedIndexExists) {88 status: response.status,
136 return nestedIndex;89 statusText: response.statusText,
137 }90 headers,
91 });
92}
13893
139 return environment.path.join(staticRoot, "index.html");94async function proxyRequest(
140 },95 request: Request,
141);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 {104 const targetUrl = new URL(`${requestUrl.pathname}${requestUrl.search}`, targetOrigin);
144 try {105 const init: RequestInit = {
145 const url = new URL(requestUrl);106 method: request.method,
146 return environment.path.extname(url.pathname).length > 0;107 headers: request.headers,
147 } catch {108 };
148 return false;109 if (request.method !== "GET" && request.method !== "HEAD") {
110 init.body = request.body;
111 (init as RequestInit & { duplex: "half" }).duplex = "half";
149 }112 }
113 const response = await Electron.net.fetch(targetUrl.toString(), init);
114 return withContentSecurityPolicy(response, contentSecurityPolicy);
150}115}
151116
152const make = Effect.gen(function* () {117const 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")(120 const registerDesktopProtocol = Effect.fn("desktop.electron.protocol.registerDesktopProtocol")(
156 function* <E, R>({121 function* (input: DesktopProtocolRegistrationInput) {
157 scheme,122 if (yield* Ref.get(registered)) return;
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 }
177123
178 const context = yield* Effect.context<R>();124 const contentSecurityPolicy = makeDesktopContentSecurityPolicy(input);
179 const runPromise = Effect.runPromiseWith(context);
180125
181 yield* Effect.acquireRelease(126 yield* Effect.acquireRelease(
182 Effect.try({127 Effect.try({
183 try: () => {128 try: () => {
184 const registered = Electron.protocol.registerFileProtocol(129 Electron.protocol.handle(input.scheme, (request) =>
185 scheme,130 proxyRequest(request, input.targetOrigin, contentSecurityPolicy),
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 },
196 );131 );
197 if (!registered) {
198 throw new ElectronProtocolRegistrationError({
199 scheme,
200 cause: "registerFileProtocol returned false",
201 });
202 }
203 },132 },
204 catch: (cause) =>133 catch: (cause) => new ElectronProtocolRegistrationError({ scheme: input.scheme, cause }),
205 cause instanceof ElectronProtocolRegistrationError134 }).pipe(Effect.andThen(Ref.set(registered, true))),
206 ? cause
207 : new ElectronProtocolRegistrationError({ scheme, cause }),
208 }).pipe(
209 Effect.andThen(
210 Ref.update(registeredProtocols, (protocols) => new Set(protocols).add(scheme)),
211 ),
212 ),
213 () =>135 () =>
214 Effect.sync(() => {136 Effect.sync(() => {
215 Electron.protocol.unregisterProtocol(scheme);137 Electron.protocol.unhandle(input.scheme);
216 }).pipe(138 }).pipe(Effect.andThen(Ref.set(registered, false))),
217 Effect.andThen(
218 Ref.update(registeredProtocols, (protocols) => {
219 const next = new Set(protocols);
220 next.delete(scheme);
221 return next;
222 }),
223 ),
224 ),
225 );139 );
226 },140 },
227 );141 );
228142
229 const registerDesktopFileProtocol = Effect.gen(function* () {143 return ElectronProtocol.of({ registerDesktopProtocol });
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 });
270});144});
271145
272export const layer = Layer.effect(ElectronProtocol, make);146export const layer = Layer.effect(ElectronProtocol, make);
apps/desktop/src/ipc/DesktopIpcHandlers.ts+2-12
...@@ -1,13 +1,6 @@...@@ -1,13 +1,6 @@
1import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
22
3import * as DesktopIpc from "./DesktopIpc.ts";3import * as DesktopIpc from "./DesktopIpc.ts";
4import {
5 clearCloudAuthToken,
6 createCloudAuthRequest,
7 fetchCloudAuth,
8 getCloudAuthToken,
9 setCloudAuthToken,
10} from "./methods/cloudAuth.ts";
11import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts";4import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts";
12import {5import {
13 clearConnectionCatalog,6 clearConnectionCatalog,
...@@ -40,6 +33,7 @@ import {...@@ -40,6 +33,7 @@ import {
40import {33import {
41 confirm,34 confirm,
42 getAppBranding,35 getAppBranding,
36 getLocalEnvironmentBearerToken,
43 getLocalEnvironmentBootstrap,37 getLocalEnvironmentBootstrap,
44 openExternal,38 openExternal,
45 pickFolder,39 pickFolder,
...@@ -54,6 +48,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"...@@ -54,6 +48,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
5448
55 yield* ipc.handleSync(getAppBranding);49 yield* ipc.handleSync(getAppBranding);
56 yield* ipc.handleSync(getLocalEnvironmentBootstrap);50 yield* ipc.handleSync(getLocalEnvironmentBootstrap);
51 yield* ipc.handle(getLocalEnvironmentBearerToken);
5752
58 yield* ipc.handle(getClientSettings);53 yield* ipc.handle(getClientSettings);
59 yield* ipc.handle(setClientSettings);54 yield* ipc.handle(setClientSettings);
...@@ -80,11 +75,6 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"...@@ -80,11 +75,6 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
80 yield* ipc.handle(setTheme);75 yield* ipc.handle(setTheme);
81 yield* ipc.handle(showContextMenu);76 yield* ipc.handle(showContextMenu);
82 yield* ipc.handle(openExternal);77 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);
88 yield* ipc.handle(getUpdateState);78 yield* ipc.handle(getUpdateState);
89 yield* ipc.handle(setUpdateChannel);79 yield* ipc.handle(setUpdateChannel);
90 yield* ipc.handle(downloadUpdate);80 yield* ipc.handle(downloadUpdate);
apps/desktop/src/ipc/channels.ts+2-6
...@@ -3,12 +3,6 @@ export const CONFIRM_CHANNEL = "desktop:confirm";...@@ -3,12 +3,6 @@ export const CONFIRM_CHANNEL = "desktop:confirm";
3export const SET_THEME_CHANNEL = "desktop:set-theme";3export const SET_THEME_CHANNEL = "desktop:set-theme";
4export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";4export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
5export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";5export 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";
12export const MENU_ACTION_CHANNEL = "desktop:menu-action";6export const MENU_ACTION_CHANNEL = "desktop:menu-action";
13export const UPDATE_STATE_CHANNEL = "desktop:update-state";7export const UPDATE_STATE_CHANNEL = "desktop:update-state";
14export const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";8export const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
...@@ -18,6 +12,8 @@ export const UPDATE_INSTALL_CHANNEL = "desktop:update-install";...@@ -18,6 +12,8 @@ export const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
18export const UPDATE_CHECK_CHANNEL = "desktop:update-check";12export const UPDATE_CHECK_CHANNEL = "desktop:update-check";
19export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";13export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";
20export const GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL = "desktop:get-local-environment-bootstrap";14export 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";
21export const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";17export const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";
22export const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings";18export const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings";
23export const GET_CONNECTION_CATALOG_CHANNEL = "desktop:get-connection-catalog";19export 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";...@@ -10,6 +10,7 @@ import * as Option from "effect/Option";
10import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
1111
12import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts";12import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts";
13import * as DesktopLocalEnvironmentAuth from "../../backend/DesktopLocalEnvironmentAuth.ts";
13import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts";14import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts";
14import * as ElectronDialog from "../../electron/ElectronDialog.ts";15import * as ElectronDialog from "../../electron/ElectronDialog.ts";
15import * as ElectronMenu from "../../electron/ElectronMenu.ts";16import * as ElectronMenu from "../../electron/ElectronMenu.ts";
...@@ -64,6 +65,16 @@ export const getLocalEnvironmentBootstrap = makeSyncIpcMethod({...@@ -64,6 +65,16 @@ export const getLocalEnvironmentBootstrap = makeSyncIpcMethod({
64 }),65 }),
65});66});
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
67export const pickFolder = makeIpcMethod({78export const pickFolder = makeIpcMethod({
68 channel: IpcChannels.PICK_FOLDER_CHANNEL,79 channel: IpcChannels.PICK_FOLDER_CHANNEL,
69 payload: Schema.UndefinedOr(PickFolderOptionsSchema),80 payload: Schema.UndefinedOr(PickFolderOptionsSchema),
apps/desktop/src/main.ts+19-7
...@@ -27,13 +27,13 @@ import * as ElectronUpdater from "./electron/ElectronUpdater.ts";...@@ -27,13 +27,13 @@ import * as ElectronUpdater from "./electron/ElectronUpdater.ts";
27import * as ElectronWindow from "./electron/ElectronWindow.ts";27import * as ElectronWindow from "./electron/ElectronWindow.ts";
28import * as DesktopApp from "./app/DesktopApp.ts";28import * as DesktopApp from "./app/DesktopApp.ts";
29import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts";29import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts";
30import * as DesktopCloudAuth from "./app/DesktopCloudAuth.ts";
31import * as DesktopCloudAuthTokenStore from "./app/DesktopCloudAuthTokenStore.ts";
32import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts";30import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts";
31import * as DesktopClerk from "./app/DesktopClerk.ts";
33import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts";32import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts";
34import * as DesktopAssets from "./app/DesktopAssets.ts";33import * as DesktopAssets from "./app/DesktopAssets.ts";
35import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts";34import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts";
36import * as DesktopBackendManager from "./backend/DesktopBackendManager.ts";35import * as DesktopBackendManager from "./backend/DesktopBackendManager.ts";
36import * as DesktopLocalEnvironmentAuth from "./backend/DesktopLocalEnvironmentAuth.ts";
37import * as DesktopEnvironment from "./app/DesktopEnvironment.ts";37import * as DesktopEnvironment from "./app/DesktopEnvironment.ts";
38import * as DesktopLifecycle from "./app/DesktopLifecycle.ts";38import * as DesktopLifecycle from "./app/DesktopLifecycle.ts";
39import * as DesktopObservability from "./app/DesktopObservability.ts";39import * as DesktopObservability from "./app/DesktopObservability.ts";
...@@ -119,7 +119,6 @@ const desktopFoundationLayer = Layer.mergeAll(...@@ -119,7 +119,6 @@ const desktopFoundationLayer = Layer.mergeAll(
119 DesktopAppSettings.layer,119 DesktopAppSettings.layer,
120 DesktopClientSettings.layer,120 DesktopClientSettings.layer,
121 DesktopConnectionCatalogStore.layer.pipe(Layer.provideMerge(DesktopSavedEnvironments.layer)),121 DesktopConnectionCatalogStore.layer.pipe(Layer.provideMerge(DesktopSavedEnvironments.layer)),
122 DesktopCloudAuthTokenStore.layer,
123 DesktopAssets.layer,122 DesktopAssets.layer,
124 DesktopObservability.layer,123 DesktopObservability.layer,
125).pipe(Layer.provideMerge(desktopEnvironmentLayer));124).pipe(Layer.provideMerge(desktopEnvironmentLayer));
...@@ -149,17 +148,30 @@ const desktopBackendLayer = DesktopBackendManager.layer.pipe(...@@ -149,17 +148,30 @@ const desktopBackendLayer = DesktopBackendManager.layer.pipe(
149 Layer.provideMerge(desktopWindowLayer),148 Layer.provideMerge(desktopWindowLayer),
150);149);
151150
151const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe(
152 Layer.provideMerge(desktopBackendLayer),
153);
154
152const desktopApplicationLayer = Layer.mergeAll(155const desktopApplicationLayer = Layer.mergeAll(
153 DesktopLifecycle.layer,156 DesktopLifecycle.layer,
154 DesktopApplicationMenu.layer,157 DesktopApplicationMenu.layer,
155 DesktopCloudAuth.layer,
156 DesktopShellEnvironment.layer,158 DesktopShellEnvironment.layer,
157 desktopSshLayer,159 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(171const desktopRuntimeLayer = desktopClerkLayer.pipe(
161 Layer.flatMap(() =>172 Layer.flatMap((clerkContext) =>
162 desktopApplicationLayer.pipe(173 desktopApplicationLayer.pipe(
174 Layer.provideMerge(Layer.succeedContext(clerkContext)),
163 Layer.provideMerge(NodeServices.layer),175 Layer.provideMerge(NodeServices.layer),
164 Layer.provideMerge(NodeHttpClient.layerUndici),176 Layer.provideMerge(NodeHttpClient.layerUndici),
165 Layer.provideMerge(NetService.layer),177 Layer.provideMerge(NetService.layer),
apps/desktop/src/preload.ts+5-17
...@@ -4,10 +4,13 @@ import type {...@@ -4,10 +4,13 @@ import type {
4 DesktopPreviewRecordingFrame,4 DesktopPreviewRecordingFrame,
5 DesktopPreviewTabState,5 DesktopPreviewTabState,
6} from "@t3tools/contracts";6} from "@t3tools/contracts";
7import { exposeClerkBridge } from "@clerk/electron/preload";
7import { contextBridge, ipcRenderer } from "electron";8import { contextBridge, ipcRenderer } from "electron";
89
9import * as IpcChannels from "./ipc/channels.ts";10import * as IpcChannels from "./ipc/channels.ts";
1011
12exposeClerkBridge({ passkeys: true });
13
11function unwrapEnsureSshEnvironmentResult(result: unknown) {14function unwrapEnsureSshEnvironmentResult(result: unknown) {
12 if (15 if (
13 typeof result === "object" &&16 typeof result === "object" &&
...@@ -39,6 +42,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {...@@ -39,6 +42,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {
39 }42 }
40 return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>;43 return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>;
41 },44 },
45 getLocalEnvironmentBearerToken: () =>
46 ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL),
42 getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL),47 getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL),
43 setClientSettings: (settings) =>48 setClientSettings: (settings) =>
44 ipcRenderer.invoke(IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, settings),49 ipcRenderer.invoke(IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, settings),
...@@ -95,23 +100,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {...@@ -95,23 +100,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {
95 ...(position === undefined ? {} : { position }),100 ...(position === undefined ? {} : { position }),
96 }),101 }),
97 openExternal: (url: string) => ipcRenderer.invoke(IpcChannels.OPEN_EXTERNAL_CHANNEL, url),102 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 },
115 onMenuAction: (listener) => {103 onMenuAction: (listener) => {
116 const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => {104 const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => {
117 if (typeof action !== "string") return;105 if (typeof action !== "string") return;
apps/desktop/src/window/DesktopWindow.test.ts+5-5
...@@ -191,19 +191,19 @@ describe("DesktopWindow", () => {...@@ -191,19 +191,19 @@ describe("DesktopWindow", () => {
191 it("recognizes only same-origin renderer navigations", () => {191 it("recognizes only same-origin renderer navigations", () => {
192 assert.isTrue(192 assert.isTrue(
193 DesktopWindow.isSameOriginRendererNavigation({193 DesktopWindow.isSameOriginRendererNavigation({
194 applicationUrl: "http://127.0.0.1:3773/",194 applicationUrl: "t3code://app/",
195 navigationUrl: "http://127.0.0.1:3773/settings/connections",195 navigationUrl: "t3code://app/settings/connections",
196 }),196 }),
197 );197 );
198 assert.isFalse(198 assert.isFalse(
199 DesktopWindow.isSameOriginRendererNavigation({199 DesktopWindow.isSameOriginRendererNavigation({
200 applicationUrl: "http://127.0.0.1:3773/",200 applicationUrl: "t3code://app/",
201 navigationUrl: "https://accounts.microsoft.com/oauth",201 navigationUrl: "https://accounts.microsoft.com/oauth",
202 }),202 }),
203 );203 );
204 assert.isFalse(204 assert.isFalse(
205 DesktopWindow.isSameOriginRendererNavigation({205 DesktopWindow.isSameOriginRendererNavigation({
206 applicationUrl: "http://127.0.0.1:3773/",206 applicationUrl: "t3code://app/",
207 navigationUrl: "not a url",207 navigationUrl: "not a url",
208 }),208 }),
209 );209 );
...@@ -231,7 +231,7 @@ describe("DesktopWindow", () => {...@@ -231,7 +231,7 @@ describe("DesktopWindow", () => {
231 assert.equal(yield* Ref.get(createCount), 1);231 assert.equal(yield* Ref.get(createCount), 1);
232 assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);232 assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);
233 assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]);233 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/"]);
235 assert.equal(fakeWindow.openDevTools.mock.calls.length, 1);235 assert.equal(fakeWindow.openDevTools.mock.calls.length, 1);
236 }).pipe(Effect.provide(layer));236 }).pipe(Effect.provide(layer));
237 }),237 }),
apps/desktop/src/window/DesktopWindow.ts+7-30
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1import * as Context from "effect/Context";1import * as Context from "effect/Context";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";3import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";4import * as Option from "effect/Option";
...@@ -16,8 +15,8 @@ import * as ElectronMenu from "../electron/ElectronMenu.ts";...@@ -16,8 +15,8 @@ import * as ElectronMenu from "../electron/ElectronMenu.ts";
16import * as ElectronShell from "../electron/ElectronShell.ts";15import * as ElectronShell from "../electron/ElectronShell.ts";
17import * as ElectronTheme from "../electron/ElectronTheme.ts";16import * as ElectronTheme from "../electron/ElectronTheme.ts";
18import * as ElectronWindow from "../electron/ElectronWindow.ts";17import * as ElectronWindow from "../electron/ElectronWindow.ts";
18import { getDesktopUrl } from "../electron/ElectronProtocol.ts";
19import * as IpcChannels from "../ipc/channels.ts";19import * as IpcChannels from "../ipc/channels.ts";
20import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts";
2120
22const TITLEBAR_HEIGHT = 40;21const TITLEBAR_HEIGHT = 40;
23const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux22const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux
...@@ -32,7 +31,6 @@ type WindowTitleBarOptions = Pick<...@@ -32,7 +31,6 @@ type WindowTitleBarOptions = Pick<
32type DesktopWindowRuntimeServices =31type DesktopWindowRuntimeServices =
33 | DesktopEnvironment.DesktopEnvironment32 | DesktopEnvironment.DesktopEnvironment
34 | DesktopAssets.DesktopAssets33 | DesktopAssets.DesktopAssets
35 | DesktopServerExposure.DesktopServerExposure
36 | DesktopState.DesktopState34 | DesktopState.DesktopState
37 | ElectronMenu.ElectronMenu35 | ElectronMenu.ElectronMenu
38 | ElectronShell.ElectronShell36 | ElectronShell.ElectronShell
...@@ -40,16 +38,7 @@ type DesktopWindowRuntimeServices =...@@ -40,16 +38,7 @@ type DesktopWindowRuntimeServices =
40 | ElectronWindow.ElectronWindow38 | ElectronWindow.ElectronWindow
41 | PreviewManager.PreviewManager;39 | 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
51export type DesktopWindowError =41export type DesktopWindowError =
52 | DesktopWindowDevServerUrlMissingError
53 | ElectronWindow.ElectronWindowCreateError42 | ElectronWindow.ElectronWindowCreateError
54 | PreviewManager.PreviewManagerError;43 | PreviewManager.PreviewManagerError;
5544
...@@ -71,15 +60,6 @@ export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowS...@@ -71,15 +60,6 @@ export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowS
71const { logInfo: logWindowInfo, logWarning: logWindowWarning } =60const { logInfo: logWindowInfo, logWarning: logWindowWarning } =
72 DesktopObservability.makeComponentLogger("desktop-window");61 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
83function getIconOption(63function getIconOption(
84 iconPaths: DesktopAssets.DesktopIconPaths,64 iconPaths: DesktopAssets.DesktopIconPaths,
85 platform: NodeJS.Platform,65 platform: NodeJS.Platform,
...@@ -171,18 +151,16 @@ const make = Effect.gen(function* () {...@@ -171,18 +151,16 @@ const make = Effect.gen(function* () {
171 const electronTheme = yield* ElectronTheme.ElectronTheme;151 const electronTheme = yield* ElectronTheme.ElectronTheme;
172 const electronWindow = yield* ElectronWindow.ElectronWindow;152 const electronWindow = yield* ElectronWindow.ElectronWindow;
173 const previewManager = yield* PreviewManager.PreviewManager;153 const previewManager = yield* PreviewManager.PreviewManager;
174 const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
175 const state = yield* DesktopState.DesktopState;154 const state = yield* DesktopState.DesktopState;
176 const context = yield* Effect.context<DesktopWindowRuntimeServices>();155 const context = yield* Effect.context<DesktopWindowRuntimeServices>();
177 const runPromise = Effect.runPromiseWith(context);156 const runPromise = Effect.runPromiseWith(context);
178157
179 const createWindow = Effect.fn("desktop.window.createWindow")(function* (158 const createWindow = Effect.fn("desktop.window.createWindow")(function* (): Effect.fn.Return<
180 backendHttpUrl: URL,159 Electron.BrowserWindow,
181 ): Effect.fn.Return<Electron.BrowserWindow, DesktopWindowError> {160 DesktopWindowError
161 > {
182 yield* previewManager.getBrowserSession();162 yield* previewManager.getBrowserSession();
183 const applicationUrl = environment.isDevelopment163 const applicationUrl = getDesktopUrl(environment.isDevelopment);
184 ? yield* resolveDesktopDevServerUrl(environment)
185 : backendHttpUrl.href;
186 const iconPaths = yield* assets.iconPaths;164 const iconPaths = yield* assets.iconPaths;
187 const iconOption = getIconOption(iconPaths, environment.platform);165 const iconOption = getIconOption(iconPaths, environment.platform);
188 const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors;166 const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors;
...@@ -350,8 +328,7 @@ const make = Effect.gen(function* () {...@@ -350,8 +328,7 @@ const make = Effect.gen(function* () {
350 });328 });
351329
352 const createMain = Effect.gen(function* () {330 const createMain = Effect.gen(function* () {
353 const backendConfig = yield* serverExposure.backendConfig;331 const window = yield* createWindow();
354 const window = yield* createWindow(backendConfig.httpBaseUrl);
355 yield* electronWindow.setMain(window);332 yield* electronWindow.setMain(window);
356 yield* logWindowInfo("main window created");333 yield* logWindowInfo("main window created");
357 return window;334 return window;
apps/desktop/vite.config.ts+6
...@@ -56,6 +56,12 @@ export default defineConfig({...@@ -56,6 +56,12 @@ export default defineConfig({
56 outExtensions: () => ({ js: ".cjs" }),56 outExtensions: () => ({ js: ".cjs" }),
57 define: publicConfigDefine,57 define: publicConfigDefine,
58 entry: ["src/preload.ts"],58 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 },
59 },65 },
60 {66 {
61 format: "cjs",67 format: "cjs",
apps/mobile/package.json+1-1
...@@ -40,7 +40,7 @@...@@ -40,7 +40,7 @@
40 },40 },
41 "dependencies": {41 "dependencies": {
42 "@callstack/liquid-glass": "^0.7.1",42 "@callstack/liquid-glass": "^0.7.1",
43 "@clerk/expo": "^3.4.1",43 "@clerk/expo": "catalog:",
44 "@effect/atom-react": "catalog:",44 "@effect/atom-react": "catalog:",
45 "@expo-google-fonts/dm-sans": "^0.4.2",45 "@expo-google-fonts/dm-sans": "^0.4.2",
46 "@expo/ui": "~56.0.8",46 "@expo/ui": "~56.0.8",
apps/mobile/src/connection/platform.ts+5
...@@ -3,6 +3,7 @@ import {...@@ -3,6 +3,7 @@ import {
3 CloudSession,3 CloudSession,
4 EnvironmentOwnedDataCleanup,4 EnvironmentOwnedDataCleanup,
5 PlatformConnectionSource,5 PlatformConnectionSource,
6 PrimaryEnvironmentAuth,
6 RelayDeviceIdentity,7 RelayDeviceIdentity,
7 SshEnvironmentGateway,8 SshEnvironmentGateway,
8} from "@t3tools/client-runtime/platform";9} from "@t3tools/client-runtime/platform";
...@@ -119,6 +120,10 @@ const capabilitiesLayer = Layer.succeedContext(...@@ -119,6 +120,10 @@ const capabilitiesLayer = Layer.succeedContext(
119 }),120 }),
120 }),121 }),
121 ).pipe(122 ).pipe(
123 Context.add(
124 PrimaryEnvironmentAuth,
125 PrimaryEnvironmentAuth.of({ bearerToken: Effect.succeed(Option.none()) }),
126 ),
122 Context.add(127 Context.add(
123 RelayDeviceIdentity,128 RelayDeviceIdentity,
124 RelayDeviceIdentity.of({129 RelayDeviceIdentity.of({
apps/server/src/relay/AgentAwarenessRelay.test.ts+5-1
...@@ -625,7 +625,11 @@ describe.sequential("signRelayAgentActivityPublishProof", () => {...@@ -625,7 +625,11 @@ describe.sequential("signRelayAgentActivityPublishProof", () => {
625 } satisfies ExecutionEnvironmentDescriptor;625 } satisfies ExecutionEnvironmentDescriptor;
626626
627 globalThis.fetch = ((input: Parameters<typeof fetch>[0]) => {627 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 );
629 runFork(Deferred.succeed(fetchSeen, url));633 runFork(Deferred.succeed(fetchSeen, url));
630 return Promise.resolve(Response.json({ ok: true, deliveries: [] }));634 return Promise.resolve(Response.json({ ok: true, deliveries: [] }));
631 }) as unknown as typeof fetch;635 }) as unknown as typeof fetch;
apps/web/package.json+2-2
...@@ -12,8 +12,8 @@...@@ -12,8 +12,8 @@
12 },12 },
13 "dependencies": {13 "dependencies": {
14 "@base-ui/react": "^1.4.1",14 "@base-ui/react": "^1.4.1",
15 "@clerk/clerk-js": "^6.16.0",15 "@clerk/electron": "catalog:",
16 "@clerk/react": "^6.9.0",16 "@clerk/react": "catalog:",
17 "@dnd-kit/core": "^6.3.1",17 "@dnd-kit/core": "^6.3.1",
18 "@dnd-kit/modifiers": "^9.0.0",18 "@dnd-kit/modifiers": "^9.0.0",
19 "@dnd-kit/sortable": "^10.0.0",19 "@dnd-kit/sortable": "^10.0.0",
apps/web/src/authBootstrap.test.ts+20-17
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1import {1import {
2 AuthSessionState as AuthSessionStateSchema,
3 EnvironmentAuthInvalidError,2 EnvironmentAuthInvalidError,
4 type AuthBrowserSessionResult,3 type AuthBrowserSessionResult,
5 type AuthCreatePairingCredentialInput,4 type AuthCreatePairingCredentialInput,
...@@ -8,10 +7,11 @@ import {...@@ -8,10 +7,11 @@ import {
8} from "@t3tools/contracts";7} from "@t3tools/contracts";
9import * as DateTime from "effect/DateTime";8import * as DateTime from "effect/DateTime";
10import * as Effect from "effect/Effect";9import * as Effect from "effect/Effect";
11import * as Schema from "effect/Schema";10import { HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
12import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";11import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
1312
14import { installEnvironmentHttpTest } from "../test/environmentHttpTest";13import { installEnvironmentHttpTest } from "../test/environmentHttpTest";
14import { __setPrimaryHttpRunnerForTests, type PrimaryHttpEffectRunner } from "./lib/runtime";
1515
16type TestWindow = {16type TestWindow = {
17 location: URL;17 location: URL;
...@@ -36,8 +36,6 @@ const DESKTOP_AUTH = {...@@ -36,8 +36,6 @@ const DESKTOP_AUTH = {
36} as const;36} as const;
3737
38const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z");38const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z");
39const encodeAuthSessionState = Schema.encodeSync(AuthSessionStateSchema);
40
41const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({39const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({
42 authenticated: false,40 authenticated: false,
43 auth,41 auth,
...@@ -117,6 +115,7 @@ describe("resolveInitialServerAuthGateState", () => {...@@ -117,6 +115,7 @@ describe("resolveInitialServerAuthGateState", () => {
117 disposeHttpTest = undefined;115 disposeHttpTest = undefined;
118 const { __resetServerAuthBootstrapForTests } = await import("./environments/primary");116 const { __resetServerAuthBootstrapForTests } = await import("./environments/primary");
119 __resetServerAuthBootstrapForTests();117 __resetServerAuthBootstrapForTests();
118 __setPrimaryHttpRunnerForTests();
120 vi.unstubAllEnvs();119 vi.unstubAllEnvs();
121 vi.useRealTimers();120 vi.useRealTimers();
122 vi.restoreAllMocks();121 vi.restoreAllMocks();
...@@ -220,18 +219,22 @@ describe("resolveInitialServerAuthGateState", () => {...@@ -220,18 +219,22 @@ describe("resolveInitialServerAuthGateState", () => {
220219
221 it("retries transient auth session bootstrap failures after restart", async () => {220 it("retries transient auth session bootstrap failures after restart", async () => {
222 vi.useFakeTimers();221 vi.useFakeTimers();
223 const fetchMock = vi222 let attempts = 0;
224 .fn<typeof fetch>()223 const request = HttpClientRequest.get("http://localhost/api/auth/session");
225 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))224 const response = HttpClientResponse.fromWeb(
226 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))225 request,
227 .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 }))226 new Response("Bad Gateway", { status: 502 }),
228 .mockResolvedValueOnce(227 );
229 new Response(228 const runner: PrimaryHttpEffectRunner = async <A>() => {
230 JSON.stringify(encodeAuthSessionState(unauthenticatedSession(LOOPBACK_AUTH))),229 attempts += 1;
231 { status: 200, headers: { "content-type": "application/json" } },230 if (attempts < 4) {
232 ),231 throw new HttpClientError.HttpClientError({
233 );232 reason: new HttpClientError.StatusCodeError({ request, response }),
234 vi.stubGlobal("fetch", fetchMock);233 });
234 }
235 return unauthenticatedSession(LOOPBACK_AUTH) as A;
236 };
237 __setPrimaryHttpRunnerForTests(runner);
235238
236 const { resolveInitialServerAuthGateState } = await import("./environments/primary");239 const { resolveInitialServerAuthGateState } = await import("./environments/primary");
237240
...@@ -242,7 +245,7 @@ describe("resolveInitialServerAuthGateState", () => {...@@ -242,7 +245,7 @@ describe("resolveInitialServerAuthGateState", () => {
242 status: "requires-auth",245 status: "requires-auth",
243 auth: LOOPBACK_AUTH,246 auth: LOOPBACK_AUTH,
244 });247 });
245 expect(fetchMock).toHaveBeenCalledTimes(4);248 expect(attempts).toBe(4);
246 });249 });
247250
248 it("takes a pairing token from the location hash and strips it immediately", async () => {251 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 @@...@@ -1,4 +1,5 @@
1import {1import {
2 type DesktopBridge,
2 EnvironmentId,3 EnvironmentId,
3 type RelayClientInstallProgressEvent,4 type RelayClientInstallProgressEvent,
4 WS_METHODS,5 WS_METHODS,
...@@ -28,6 +29,7 @@ import {...@@ -28,6 +29,7 @@ import {
28 ManagedRelayDpopSigner,29 ManagedRelayDpopSigner,
29} from "@t3tools/client-runtime/relay";30} from "@t3tools/client-runtime/relay";
30import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";31import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
32import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth";
3133
32import {34import {
33 collectCloudLinkTargets,35 collectCloudLinkTargets,
...@@ -146,6 +148,7 @@ beforeEach(() => {...@@ -146,6 +148,7 @@ beforeEach(() => {
146});148});
147149
148afterEach(() => {150afterEach(() => {
151 __resetDesktopPrimaryAuthForTests();
149 vi.unstubAllGlobals();152 vi.unstubAllGlobals();
150 vi.unstubAllEnvs();153 vi.unstubAllEnvs();
151 vi.restoreAllMocks();154 vi.restoreAllMocks();
...@@ -224,6 +227,33 @@ describe("web cloud link environment client", () => {...@@ -224,6 +227,33 @@ describe("web cloud link environment client", () => {
224 }),227 }),
225 );228 );
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
227 it.effect("links an available primary environment without invoking installation", () =>257 it.effect("links an available primary environment without invoking installation", () =>
228 Effect.gen(function* () {258 Effect.gen(function* () {
229 const fetchMock = vi259 const fetchMock = vi
apps/web/src/cloud/linkEnvironment.ts+9-22
...@@ -31,7 +31,7 @@ import {...@@ -31,7 +31,7 @@ import {
31 readPrimaryEnvironmentDescriptor,31 readPrimaryEnvironmentDescriptor,
32 readPrimaryEnvironmentTarget,32 readPrimaryEnvironmentTarget,
33} from "../environments/primary";33} from "../environments/primary";
34import { withPrimaryEnvironmentRequestInit } from "../environments/primary/requestInit";34import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
35import { resolveCloudPublicConfig } from "./publicConfig";35import { resolveCloudPublicConfig } from "./publicConfig";
36import {36import {
37 finishRelayClientInstall,37 finishRelayClientInstall,
...@@ -327,11 +327,8 @@ export function readPrimaryCloudLinkState(input: {...@@ -327,11 +327,8 @@ export function readPrimaryCloudLinkState(input: {
327 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);327 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
328 return yield* client.connect328 return yield* client.connect
329 .linkState({ headers: {} })329 .linkState({ headers: {} })
330 .pipe(330 .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state.")));
331 withPrimaryEnvironmentRequestInit,331 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
332 Effect.mapError(environmentApiError("Could not read environment cloud link state.")),
333 );
334 });
335}332}
336333
337export function updatePrimaryCloudPreferences(input: {334export function updatePrimaryCloudPreferences(input: {
...@@ -346,10 +343,9 @@ export function updatePrimaryCloudPreferences(input: {...@@ -346,10 +343,9 @@ export function updatePrimaryCloudPreferences(input: {
346 payload: input,343 payload: input,
347 })344 })
348 .pipe(345 .pipe(
349 withPrimaryEnvironmentRequestInit,
350 Effect.mapError(environmentApiError("Could not update environment cloud preferences.")),346 Effect.mapError(environmentApiError("Could not update environment cloud preferences.")),
351 );347 );
352 });348 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
353}349}
354350
355export function unlinkPrimaryEnvironmentFromCloud(input: {351export function unlinkPrimaryEnvironmentFromCloud(input: {
...@@ -360,10 +356,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {...@@ -360,10 +356,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {
360 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);356 const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
361 yield* client.connect357 yield* client.connect
362 .unlink({ headers: {} })358 .unlink({ headers: {} })
363 .pipe(359 .pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")));
364 withPrimaryEnvironmentRequestInit,
365 Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")),
366 );
367360
368 const configuredRelayUrl = relayUrl();361 const configuredRelayUrl = relayUrl();
369 if (configuredRelayUrl && input.clerkToken) {362 if (configuredRelayUrl && input.clerkToken) {
...@@ -381,7 +374,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {...@@ -381,7 +374,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {
381 ),374 ),
382 );375 );
383 }376 }
384 });377 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
385}378}
386379
387export function linkPrimaryEnvironmentToCloud(input: {380export function linkPrimaryEnvironmentToCloud(input: {
...@@ -433,10 +426,7 @@ export function linkPrimaryEnvironmentToCloud(input: {...@@ -433,10 +426,7 @@ export function linkPrimaryEnvironmentToCloud(input: {
433 origin: endpointOrigin(input.target.httpBaseUrl),426 origin: endpointOrigin(input.target.httpBaseUrl),
434 },427 },
435 })428 })
436 .pipe(429 .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof.")));
437 withPrimaryEnvironmentRequestInit,
438 Effect.mapError(environmentApiError("Could not obtain environment link proof.")),
439 );
440 const link = yield* relayClient430 const link = yield* relayClient
441 .linkEnvironment({431 .linkEnvironment({
442 clerkToken: input.clerkToken,432 clerkToken: input.clerkToken,
...@@ -470,9 +460,6 @@ export function linkPrimaryEnvironmentToCloud(input: {...@@ -470,9 +460,6 @@ export function linkPrimaryEnvironmentToCloud(input: {
470 endpointRuntime: link.endpointRuntime,460 endpointRuntime: link.endpointRuntime,
471 },461 },
472 })462 })
473 .pipe(463 .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access.")));
474 withPrimaryEnvironmentRequestInit,464 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
475 Effect.mapError(environmentApiError("Could not configure environment relay access.")),
476 );
477 });
478}465}
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 @@...@@ -1,29 +1,9 @@
1import { useClerk } from "@clerk/react";1import { useClerk } from "@clerk/react";
2import { useState } from "react";
3
4import { isElectron } from "../../env";
5import { Dialog, DialogPopup } from "../ui/dialog";
6import { DesktopClerkWaitlist } from "./DesktopClerkWaitlist";
72
8export function useT3ConnectAuthPrompt() {3export function useT3ConnectAuthPrompt() {
9 const clerk = useClerk();4 const clerk = useClerk();
10 const [desktopAuthOpen, setDesktopAuthOpen] = useState(false);
11
12 const openAuthPrompt = () => {5 const openAuthPrompt = () => {
13 if (isElectron) {
14 setDesktopAuthOpen(true);
15 return;
16 }
17 clerk.openWaitlist();6 clerk.openWaitlist();
18 };7 };
198 return { authPrompt: null, openAuthPrompt };
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 };
29}9}
apps/web/src/connection/platform.ts+31-30
...@@ -3,6 +3,7 @@ import {...@@ -3,6 +3,7 @@ import {
3 CloudSession,3 CloudSession,
4 EnvironmentOwnedDataCleanup,4 EnvironmentOwnedDataCleanup,
5 PlatformConnectionSource,5 PlatformConnectionSource,
6 PrimaryEnvironmentAuth,
6 RelayDeviceIdentity,7 RelayDeviceIdentity,
7 SshEnvironmentGateway,8 SshEnvironmentGateway,
8} from "@t3tools/client-runtime/platform";9} from "@t3tools/client-runtime/platform";
...@@ -30,9 +31,9 @@ import * as Option from "effect/Option";...@@ -30,9 +31,9 @@ import * as Option from "effect/Option";
30import * as Queue from "effect/Queue";31import * as Queue from "effect/Queue";
31import * as Schedule from "effect/Schedule";32import * as Schedule from "effect/Schedule";
32import * as Stream from "effect/Stream";33import * 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";
36import { readPrimaryEnvironmentTarget } from "../environments/primary/target";37import { readPrimaryEnvironmentTarget } from "../environments/primary/target";
37import { clearComposerDraftsEnvironment } from "../composerDraftStore";38import { clearComposerDraftsEnvironment } from "../composerDraftStore";
38import { isHostedStaticApp } from "../hostedPairing";39import { isHostedStaticApp } from "../hostedPairing";
...@@ -193,6 +194,16 @@ const capabilitiesLayer = Layer.effectContext(...@@ -193,6 +194,16 @@ const capabilitiesLayer = Layer.effectContext(
193 const identity = RelayDeviceIdentity.of({194 const identity = RelayDeviceIdentity.of({
194 deviceId: Effect.succeed(Option.none()),195 deviceId: Effect.succeed(Option.none()),
195 });196 });
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 });
196 const ssh = SshEnvironmentGateway.of({207 const ssh = SshEnvironmentGateway.of({
197 provision: Effect.fn("web.connectionPlatform.ssh.provision")(function* (target) {208 provision: Effect.fn("web.connectionPlatform.ssh.provision")(function* (target) {
198 const bridge = window.desktopBridge;209 const bridge = window.desktopBridge;
...@@ -252,6 +263,7 @@ const capabilitiesLayer = Layer.effectContext(...@@ -252,6 +263,7 @@ const capabilitiesLayer = Layer.effectContext(
252 });263 });
253264
254 return Context.make(CloudSession, cloudSession).pipe(265 return Context.make(CloudSession, cloudSession).pipe(
266 Context.add(PrimaryEnvironmentAuth, primaryAuth),
255 Context.add(RelayDeviceIdentity, identity),267 Context.add(RelayDeviceIdentity, identity),
256 Context.add(ClientPresentation, presentation),268 Context.add(ClientPresentation, presentation),
257 Context.add(SshEnvironmentGateway, ssh),269 Context.add(SshEnvironmentGateway, ssh),
...@@ -271,10 +283,7 @@ const loadPrimaryConnectionRegistration = Effect.fn(...@@ -271,10 +283,7 @@ const loadPrimaryConnectionRegistration = Effect.fn(
271 }283 }
272 const descriptor = yield* fetchRemoteEnvironmentDescriptor({284 const descriptor = yield* fetchRemoteEnvironmentDescriptor({
273 httpBaseUrl: resolved.target.httpBaseUrl,285 httpBaseUrl: resolved.target.httpBaseUrl,
274 }).pipe(286 }).pipe(Effect.provide(primaryEnvironmentHttpLayer), Effect.mapError(mapRemoteEnvironmentError));
275 Effect.provideService(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit),
276 Effect.mapError(mapRemoteEnvironmentError),
277 );
278 return new PrimaryConnectionRegistration({287 return new PrimaryConnectionRegistration({
279 target: new PrimaryConnectionTarget({288 target: new PrimaryConnectionTarget({
280 environmentId: descriptor.environmentId,289 environmentId: descriptor.environmentId,
...@@ -289,32 +298,24 @@ const primaryRegistrationRetrySchedule = Schedule.exponential("1 second").pipe(...@@ -289,32 +298,24 @@ const primaryRegistrationRetrySchedule = Schedule.exponential("1 second").pipe(
289 Schedule.either(Schedule.spaced("16 seconds")),298 Schedule.either(Schedule.spaced("16 seconds")),
290);299);
291300
292const platformConnectionSourceLayer = Layer.effect(301const platformConnectionSourceLayer = Layer.sync(PlatformConnectionSource, () => {
293 PlatformConnectionSource,302 if (isHostedStaticApp()) {
294 Effect.gen(function* () {
295 if (isHostedStaticApp()) {
296 return PlatformConnectionSource.of({
297 registrations: Stream.empty,
298 });
299 }
300 const httpClient = yield* HttpClient.HttpClient;
301 return PlatformConnectionSource.of({303 return PlatformConnectionSource.of({
302 registrations: Stream.fromEffect(304 registrations: Stream.empty,
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 ),
315 });305 });
316 }),306 }
317);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
319const environmentOwnedDataCleanupLayer = Layer.succeed(320const environmentOwnedDataCleanupLayer = Layer.succeed(
320 EnvironmentOwnedDataCleanup,321 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 {...@@ -33,6 +33,8 @@ export {
3333
34export { refreshPrimarySessionState, usePrimarySessionState } from "./sessionState";34export { refreshPrimarySessionState, usePrimarySessionState } from "./sessionState";
3535
36export { PrimaryEnvironmentHttpClient } from "./httpClient";
37
36export {38export {
37 readPrimaryEnvironmentTarget,39 readPrimaryEnvironmentTarget,
38 resolvePrimaryEnvironmentHttpUrl,40 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 @@...@@ -1,17 +1,15 @@
1import * as ManagedRuntime from "effect/ManagedRuntime";1import * as ManagedRuntime from "effect/ManagedRuntime";
2import type * as Effect from "effect/Effect";2import type * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";3import * as Layer from "effect/Layer";
4import { FetchHttpClient } from "effect/unstable/http";
5import * as Socket from "effect/unstable/socket/Socket";4import * as Socket from "effect/unstable/socket/Socket";
65
7import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";6import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
8import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability";
9import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";7import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
10import {8import {
11 PrimaryEnvironmentHttpClient,9 PrimaryEnvironmentHttpClient,
12 primaryEnvironmentHttpClientLive,10 primaryEnvironmentHttpClientLive,
13} from "../environments/primary/httpClient";11} from "../environments/primary/httpClient";
14import { primaryEnvironmentRequestInit } from "../environments/primary/requestInit";12import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
1513
16import { browserCryptoLayer } from "../cloud/dpop";14import { browserCryptoLayer } from "../cloud/dpop";
17import { managedRelayClientLayer } from "../cloud/managedRelayLayer";15import { managedRelayClientLayer } from "../cloud/managedRelayLayer";
...@@ -32,15 +30,7 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(...@@ -32,15 +30,7 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(
32export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer);30export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer);
3331
34const primaryHttpRuntime = ManagedRuntime.make(32const primaryHttpRuntime = ManagedRuntime.make(
35 primaryEnvironmentHttpClientLive.pipe(33 primaryEnvironmentHttpClientLive.pipe(Layer.provide(primaryEnvironmentHttpLayer)),
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 ),
44);34);
4535
46export type PrimaryHttpEffectRunner = <A, E>(36export type PrimaryHttpEffectRunner = <A, E>(
apps/web/src/main.tsx+4-3
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1import React from "react";1import React from "react";
2import ReactDOM from "react-dom/client";2import ReactDOM from "react-dom/client";
3import { ClerkProvider } from "@clerk/react";3import { ClerkProvider } from "@clerk/react";
4import { passkeys } from "@clerk/electron/passkeys";
5import { ClerkProvider as ElectronClerkProvider } from "@clerk/electron/react";
4import { createHashHistory, createBrowserHistory } from "@tanstack/react-router";6import { createHashHistory, createBrowserHistory } from "@tanstack/react-router";
57
6import "@fontsource-variable/dm-sans/index.css";8import "@fontsource-variable/dm-sans/index.css";
...@@ -10,7 +12,6 @@ import "@xterm/xterm/css/xterm.css";...@@ -10,7 +12,6 @@ import "@xterm/xterm/css/xterm.css";
10import "./index.css";12import "./index.css";
1113
12import { isElectron } from "./env";14import { isElectron } from "./env";
13import { DesktopClerkProvider } from "./cloud/desktopClerk";
14import { ManagedRelayAuthProvider } from "./cloud/managedAuth";15import { ManagedRelayAuthProvider } from "./cloud/managedAuth";
15import { hasCloudPublicConfig } from "./cloud/publicConfig";16import { hasCloudPublicConfig } from "./cloud/publicConfig";
16import { getRouter } from "./router";17import { getRouter } from "./router";
...@@ -37,9 +38,9 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(...@@ -37,9 +38,9 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
37 <React.StrictMode>38 <React.StrictMode>
38 {clerkPublishableKey && hasCloudPublicConfig() ? (39 {clerkPublishableKey && hasCloudPublicConfig() ? (
39 isElectron ? (40 isElectron ? (
40 <DesktopClerkProvider publishableKey={clerkPublishableKey}>41 <ElectronClerkProvider publishableKey={clerkPublishableKey} passkeys={passkeys}>
41 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>42 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>
42 </DesktopClerkProvider>43 </ElectronClerkProvider>
43 ) : (44 ) : (
44 <ClerkProvider publishableKey={clerkPublishableKey}>45 <ClerkProvider publishableKey={clerkPublishableKey}>
45 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>46 <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider>
apps/web/src/observability/clientTracing.ts+3-2
...@@ -3,11 +3,12 @@ import * as Layer from "effect/Layer";...@@ -3,11 +3,12 @@ import * as Layer from "effect/Layer";
3import * as ManagedRuntime from "effect/ManagedRuntime";3import * as ManagedRuntime from "effect/ManagedRuntime";
4import * as Scope from "effect/Scope";4import * as Scope from "effect/Scope";
5import * as Tracer from "effect/Tracer";5import * as Tracer from "effect/Tracer";
6import { FetchHttpClient, HttpClient } from "effect/unstable/http";6import { HttpClient } from "effect/unstable/http";
7import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";7import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
88
9import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";9import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
10import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary";10import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary";
11import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
11import { isElectron } from "../env";12import { isElectron } from "../env";
12import { APP_VERSION } from "~/branding";13import { APP_VERSION } from "~/branding";
1314
...@@ -22,7 +23,7 @@ const CLIENT_TRACING_RESOURCE = {...@@ -22,7 +23,7 @@ const CLIENT_TRACING_RESOURCE = {
22} as const;23} as const;
2324
24const delegateRuntimeLayer = Layer.mergeAll(25const delegateRuntimeLayer = Layer.mergeAll(
25 FetchHttpClient.layer,26 primaryEnvironmentHttpLayer,
26 OtlpSerialization.layerJson,27 OtlpSerialization.layerJson,
27 Layer.succeed(HttpClient.TracerDisabledWhen, () => true),28 Layer.succeed(HttpClient.TracerDisabledWhen, () => true),
28);29);
apps/web/vite.config.ts+1
...@@ -126,6 +126,7 @@ export default defineConfig(() => {...@@ -126,6 +126,7 @@ export default defineConfig(() => {
126 },126 },
127 resolve: {127 resolve: {
128 tsconfigPaths: true,128 tsconfigPaths: true,
129 dedupe: ["react", "react-dom"],
129 },130 },
130 server: {131 server: {
131 host,132 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...@@ -120,20 +120,86 @@ selects the concrete relay deployment, but changing that URL does not require a
120## Desktop OAuth Redirect Allowlist120## Desktop OAuth Redirect Allowlist
121121
122The desktop app opens OAuth in the system browser and returns to the app with a custom URL scheme.122The 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 these123In **Clerk Dashboard > Native applications**, enable the Native API and add these entries under the
124entries under the mobile SSO redirect allowlist:124mobile SSO redirect allowlist:
125125
126```text126```text
127t3code-dev://auth/callback127t3code-dev://app/
128t3code://auth/callback128t3code://app/
129```129```
130130
131The first entry is for local desktop development. The second is for packaged desktop builds.131Local desktop development uses `t3code-dev://app`, while packaged builds use `t3code://app`. Add the
132The app also adds a request-scoped `t3_state` query parameter and validates it on callback. Initial132matching origin to each Clerk instance's Backend API `allowed_origins` array as well. The development
133sign-in and linked-account OAuth flows both return through this bridge. The desktop provider keeps133Clerk instance should only need `t3code-dev://app`; the production Clerk instance should only need
134Clerk's stock profile component, replaces its renderer-page callback with the custom-scheme callback,134`t3code://app`. `@clerk/electron` owns the native request adapter, encrypted Clerk token persistence,
135and opens the provider URL in the system browser. Do not add the local renderer URL as an OAuth135external-browser OAuth transport, and callback delivery for initial sign-in and linked-account flows.
136redirect: an external browser cannot use it to reopen the packaged app.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
138The current mobile UI uses Clerk's native authentication view. If a future mobile browser OAuth204The current mobile UI uses Clerk's native authentication view. If a future mobile browser OAuth
139flow uses a custom redirect URI, add that exact URI to the same allowlist.205flow uses a custom redirect URI, add that exact URI to the same allowlist.
docs/operations/ci.md+1-1
...@@ -2,5 +2,5 @@...@@ -2,5 +2,5 @@
22
3- `.github/workflows/ci.yml` runs `bun run lint`, `bun run typecheck`, and `bun run test` on pull requests and pushes to `main`.3- `.github/workflows/ci.yml` runs `bun run lint`, `bun run typecheck`, and `bun run test` on pull requests and pushes to `main`.
4- `.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.4- `.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.
6- See [Release Checklist](./release.md) for the full release/signing setup checklist.6- 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:...@@ -219,26 +219,44 @@ Required secrets used by the workflow:
219- `APPLE_API_KEY`219- `APPLE_API_KEY`
220- `APPLE_API_KEY_ID`220- `APPLE_API_KEY_ID`
221- `APPLE_API_ISSUER`221- `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
223Checklist:233Checklist:
224234
2251. Apple Developer account access:2351. Apple Developer account access:
226 - Team has rights to create Developer ID certificates.236 - Team has rights to create Developer ID certificates.
2272. Create `Developer ID Application` certificate.2372. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains.
2283. Export certificate + private key as `.p12` from Keychain.2383. Create a `Developer ID Application` certificate and a compatible provisioning profile for that
2294. Base64-encode the `.p12` and store as `CSC_LINK`.239 App ID with Associated Domains enabled.
2305. Store the `.p12` export password as `CSC_KEY_PASSWORD`.2404. Export the certificate + private key as `.p12` from Keychain.
2316. In App Store Connect, create an API key (Team key).2415. Base64-encode the `.p12` and store as `CSC_LINK`.
2327. Add API key values: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:
233 - `APPLE_API_KEY`: contents of the downloaded `.p8`247 - `APPLE_API_KEY`: contents of the downloaded `.p8`
234 - `APPLE_API_KEY_ID`: Key ID248 - `APPLE_API_KEY_ID`: Key ID
235 - `APPLE_API_ISSUER`: Issuer ID249 - `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
238Notes:254Notes:
239255
240- `APPLE_API_KEY` is stored as raw key text in secrets.256- `APPLE_API_KEY` is stored as raw key text in secrets.
241- The workflow writes it to a temporary `AuthKey_<id>.p8` file at runtime.257- 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
243## 3) Azure Trusted Signing setup (Windows)261## 3) Azure Trusted Signing setup (Windows)
244262
...@@ -281,7 +299,9 @@ Checklist:...@@ -281,7 +299,9 @@ Checklist:
281## 5) Troubleshooting299## 5) Troubleshooting
282300
283- macOS build unsigned when expected signed:301- 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.
285- Windows build unsigned when expected signed:305- Windows build unsigned when expected signed:
286 - Check all Azure ATS and auth secrets are populated and non-empty.306 - Check all Azure ATS and auth secrets are populated and non-empty.
287- Build fails with signing error:307- Build fails with signing error:
docs/reference/scripts.md+4-1
...@@ -20,11 +20,14 @@...@@ -20,11 +20,14 @@
2020
21- Default build is unsigned/not notarized for local sharing.21- Default build is unsigned/not notarized for local sharing.
22- The DMG build uses `assets/macos-icon-1024.png` as the production app icon source.22- 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).
24- Desktop packaging includes `apps/server/dist` (the `t3` backend) and starts it on loopback with an auth token for WebSocket/API traffic.24- Desktop packaging includes `apps/server/dist` (the `t3` backend) and starts it on loopback with an auth token for WebSocket/API traffic.
25- Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch.25- Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch.
26- To keep staging files for debugging package contents, run: `bun run dist:desktop:dmg -- --keep-stage`26- To keep staging files for debugging package contents, run: `bun run dist:desktop:dmg -- --keep-stage`
27- To allow code-signing/notarization when configured in CI/secrets, add: `--signed`.27- 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.
28- Windows `--signed` uses Azure Trusted Signing and expects:31- Windows `--signed` uses Azure Trusted Signing and expects:
29 `AZURE_TRUSTED_SIGNING_ENDPOINT`, `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME`,32 `AZURE_TRUSTED_SIGNING_ENDPOINT`, `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME`,
30 `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME`, and `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME`.33 `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME`, and `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME`.
infra/relay/package.json+1-1
...@@ -9,7 +9,7 @@...@@ -9,7 +9,7 @@
9 "typecheck": "tsgo --noEmit"9 "typecheck": "tsgo --noEmit"
10 },10 },
11 "dependencies": {11 "dependencies": {
12 "@clerk/backend": "3.6.1",12 "@clerk/backend": "catalog:",
13 "@effect/sql-pg": "catalog:",13 "@effect/sql-pg": "catalog:",
14 "@t3tools/client-runtime": "workspace:*",14 "@t3tools/client-runtime": "workspace:*",
15 "@t3tools/contracts": "workspace:*",15 "@t3tools/contracts": "workspace:*",
packages/client-runtime/src/connection/resolver.test.ts+44
...@@ -17,6 +17,7 @@ import { ConnectionResolver } from "./resolver.ts";...@@ -17,6 +17,7 @@ import { ConnectionResolver } from "./resolver.ts";
17import { connectionResolverLayer } from "./resolver.ts";17import { connectionResolverLayer } from "./resolver.ts";
18import {18import {
19 CloudSession,19 CloudSession,
20 PrimaryEnvironmentAuth,
20 RelayDeviceIdentity,21 RelayDeviceIdentity,
21 SshEnvironmentGateway,22 SshEnvironmentGateway,
22} from "../platform/capabilities.ts";23} from "../platform/capabilities.ts";
...@@ -101,6 +102,7 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o...@@ -101,6 +102,7 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
101 readonly connectEnvironment?: ManagedRelayClient["Service"]["connectEnvironment"];102 readonly connectEnvironment?: ManagedRelayClient["Service"]["connectEnvironment"];
102 readonly authorizeBearer?: RemoteEnvironmentAuthorization["Service"]["authorizeBearer"];103 readonly authorizeBearer?: RemoteEnvironmentAuthorization["Service"]["authorizeBearer"];
103 readonly authorizeDpop?: RemoteEnvironmentAuthorization["Service"]["authorizeDpop"];104 readonly authorizeDpop?: RemoteEnvironmentAuthorization["Service"]["authorizeDpop"];
105 readonly primaryBearerToken?: string;
104 readonly prepareSsh?: SshEnvironmentGateway["Service"]["prepare"];106 readonly prepareSsh?: SshEnvironmentGateway["Service"]["prepare"];
105}) => {107}) => {
106 const profiles = new Map(108 const profiles = new Map(
...@@ -170,6 +172,12 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o...@@ -170,6 +172,12 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
170 Layer.succeed(ConnectionProfileStore, profileStore),172 Layer.succeed(ConnectionProfileStore, profileStore),
171 Layer.succeed(ConnectionCredentialStore, credentialStore),173 Layer.succeed(ConnectionCredentialStore, credentialStore),
172 Layer.succeed(CloudSession, CloudSession.of({ clerkToken: Effect.succeed("clerk-session") })),174 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 ),
173 Layer.succeed(181 Layer.succeed(
174 RelayDeviceIdentity,182 RelayDeviceIdentity,
175 RelayDeviceIdentity.of({ deviceId: Effect.succeed(Option.some("device-1")) }),183 RelayDeviceIdentity.of({ deviceId: Effect.succeed(Option.some("device-1")) }),
...@@ -217,6 +225,42 @@ describe("ConnectionResolver", () => {...@@ -217,6 +225,42 @@ describe("ConnectionResolver", () => {
217 }),225 }),
218 );226 );
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
220 it.effect("uses the registered bearer profile without re-reading the profile store", () =>264 it.effect("uses the registered bearer profile without re-reading the profile store", () =>
221 Effect.gen(function* () {265 Effect.gen(function* () {
222 const bearerInputs = yield* Ref.make<ReadonlyArray<string>>([]);266 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";...@@ -10,6 +10,7 @@ import { RemoteEnvironmentAuthorization } from "../authorization/service.ts";
10import { ManagedRelayClient } from "../relay/managedRelay.ts";10import { ManagedRelayClient } from "../relay/managedRelay.ts";
11import {11import {
12 CloudSession,12 CloudSession,
13 PrimaryEnvironmentAuth,
13 RelayDeviceIdentity,14 RelayDeviceIdentity,
14 SshEnvironmentGateway,15 SshEnvironmentGateway,
15} from "../platform/capabilities.ts";16} from "../platform/capabilities.ts";
...@@ -58,17 +59,37 @@ function primarySocketUrl(target: PrimaryConnectionTarget): string {...@@ -58,17 +59,37 @@ function primarySocketUrl(target: PrimaryConnectionTarget): string {
58 return url.toString();59 return url.toString();
59}60}
6061
61const primaryBroker = Effect.fn("clientRuntime.connection.broker.primary")(62const makePrimaryBroker = Effect.fn("clientRuntime.connection.broker.makePrimary")(function* () {
62 (target: PrimaryConnectionTarget) =>63 const auth = yield* PrimaryEnvironmentAuth;
63 Effect.succeed({64 const remote = yield* RemoteEnvironmentAuthorization;
64 environmentId: target.environmentId,65
65 label: target.label,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,
66 httpBaseUrl: target.httpBaseUrl,83 httpBaseUrl: target.httpBaseUrl,
67 socketUrl: primarySocketUrl(target),84 wsBaseUrl: target.wsBaseUrl,
68 httpAuthorization: null,85 bearerToken: bearerToken.value,
86 });
87 return {
88 ...authorized,
69 target,89 target,
70 } satisfies PreparedConnection),90 } satisfies PreparedConnection;
71);91 });
92});
7293
73const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () {94const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () {
74 const credentials = yield* ConnectionCredentialStore;95 const credentials = yield* ConnectionCredentialStore;
...@@ -228,6 +249,7 @@ const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(funct...@@ -228,6 +249,7 @@ const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(funct
228export const connectionResolverLayer = Layer.effect(249export const connectionResolverLayer = Layer.effect(
229 ConnectionResolver,250 ConnectionResolver,
230 Effect.gen(function* () {251 Effect.gen(function* () {
252 const primary = yield* makePrimaryBroker();
231 const bearer = yield* makeBearerBroker();253 const bearer = yield* makeBearerBroker();
232 const relay = yield* makeRelayBroker();254 const relay = yield* makeRelayBroker();
233 const ssh = yield* makeSshBroker();255 const ssh = yield* makeSshBroker();
...@@ -242,7 +264,7 @@ export const connectionResolverLayer = Layer.effect(...@@ -242,7 +264,7 @@ export const connectionResolverLayer = Layer.effect(
242 });264 });
243 switch (target._tag) {265 switch (target._tag) {
244 case "PrimaryConnectionTarget":266 case "PrimaryConnectionTarget":
245 return yield* primaryBroker(target);267 return yield* primary(target);
246 case "BearerConnectionTarget":268 case "BearerConnectionTarget":
247 return yield* bearer({ ...entry, target });269 return yield* bearer({ ...entry, target });
248 case "RelayConnectionTarget":270 case "RelayConnectionTarget":
packages/client-runtime/src/platform/capabilities.ts+7
...@@ -43,6 +43,13 @@ export class ClientPresentation extends Context.Service<...@@ -43,6 +43,13 @@ export class ClientPresentation extends Context.Service<
43 }43 }
44>()("@t3tools/client-runtime/platform/capabilities/ClientPresentation") {}44>()("@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
46export class SshEnvironmentGateway extends Context.Service<53export class SshEnvironmentGateway extends Context.Service<
47 SshEnvironmentGateway,54 SshEnvironmentGateway,
48 {55 {
packages/contracts/src/ipc.ts+1-23
...@@ -415,23 +415,6 @@ export const PickFolderOptionsSchema = Schema.Struct({...@@ -415,23 +415,6 @@ export const PickFolderOptionsSchema = Schema.Struct({
415 initialPath: Schema.optionalKey(Schema.NullOr(Schema.String)),415 initialPath: Schema.optionalKey(Schema.NullOr(Schema.String)),
416});416});
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
435/**418/**
436 * Renderer-facing snapshot of a desktop preview tab. Mirrors the main-process419 * Renderer-facing snapshot of a desktop preview tab. Mirrors the main-process
437 * PreviewTabState shape but uses serialisable primitives only.420 * PreviewTabState shape but uses serialisable primitives only.
...@@ -897,6 +880,7 @@ export const DesktopPreviewAutomationWaitForInputSchema = Schema.Struct({...@@ -897,6 +880,7 @@ export const DesktopPreviewAutomationWaitForInputSchema = Schema.Struct({
897export interface DesktopBridge {880export interface DesktopBridge {
898 getAppBranding: () => DesktopAppBranding | null;881 getAppBranding: () => DesktopAppBranding | null;
899 getLocalEnvironmentBootstrap: () => DesktopEnvironmentBootstrap | null;882 getLocalEnvironmentBootstrap: () => DesktopEnvironmentBootstrap | null;
883 getLocalEnvironmentBearerToken: () => Promise<string>;
900 getClientSettings: () => Promise<ClientSettings | null>;884 getClientSettings: () => Promise<ClientSettings | null>;
901 setClientSettings: (settings: ClientSettings) => Promise<void>;885 setClientSettings: (settings: ClientSettings) => Promise<void>;
902 getConnectionCatalog?: () => Promise<string | null>;886 getConnectionCatalog?: () => Promise<string | null>;
...@@ -935,12 +919,6 @@ export interface DesktopBridge {...@@ -935,12 +919,6 @@ export interface DesktopBridge {
935 position?: { x: number; y: number },919 position?: { x: number; y: number },
936 ) => Promise<T | null>;920 ) => Promise<T | null>;
937 openExternal: (url: string) => Promise<boolean>;921 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;
944 onMenuAction: (listener: (action: string) => void) => () => void;922 onMenuAction: (listener: (action: string) => void) => () => void;
945 getUpdateState: () => Promise<DesktopUpdateState>;923 getUpdateState: () => Promise<DesktopUpdateState>;
946 setUpdateChannel: (channel: DesktopUpdateChannel) => Promise<DesktopUpdateState>;924 setUpdateChannel: (channel: DesktopUpdateChannel) => Promise<DesktopUpdateState>;
pnpm-lock.yaml+510-284
...@@ -35,6 +35,13 @@ catalogs:...@@ -35,6 +35,13 @@ catalogs:
35 version: 0.1.2435 version: 0.1.24
3636
37overrides:37overrides:
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
38 '@clerk/clerk-js>@base-org/account': '-'45 '@clerk/clerk-js>@base-org/account': '-'
39 '@clerk/clerk-js>@coinbase/wallet-sdk': '-'46 '@clerk/clerk-js>@coinbase/wallet-sdk': '-'
40 '@clerk/clerk-js>@solana/wallet-adapter-base': '-'47 '@clerk/clerk-js>@solana/wallet-adapter-base': '-'
...@@ -105,6 +112,12 @@ importers:...@@ -105,6 +112,12 @@ importers:
105112
106 apps/desktop:113 apps/desktop:
107 dependencies:114 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
108 '@effect/platform-node':121 '@effect/platform-node':
109 specifier: 4.0.0-beta.78122 specifier: 4.0.0-beta.78
110 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)123 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:...@@ -129,6 +142,9 @@ importers:
129 electron:142 electron:
130 specifier: 41.5.0143 specifier: 41.5.0
131 version: 41.5.0144 version: 41.5.0
145 electron-store:
146 specifier: ^8.2.0
147 version: 8.2.0
132 electron-updater:148 electron-updater:
133 specifier: ^6.6.2149 specifier: ^6.6.2
134 version: 6.8.3150 version: 6.8.3
...@@ -178,10 +194,10 @@ importers:...@@ -178,10 +194,10 @@ importers:
178 dependencies:194 dependencies:
179 '@callstack/liquid-glass':195 '@callstack/liquid-glass':
180 specifier: ^0.7.1196 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)
182 '@clerk/expo':198 '@clerk/expo':
183 specifier: ^3.4.1199 specifier: 3.4.8-snapshot.v20260619001138
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)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)
185 '@effect/atom-react':201 '@effect/atom-react':
186 specifier: 4.0.0-beta.78202 specifier: 4.0.0-beta.78
187 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(react@19.2.3)(scheduler@0.27.0)203 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:...@@ -190,10 +206,10 @@ importers:
190 version: 0.4.2206 version: 0.4.2
191 '@expo/ui':207 '@expo/ui':
192 specifier: ~56.0.8208 specifier: ~56.0.8
193 version: 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)209 version: 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
194 '@legendapp/list':210 '@legendapp/list':
195 specifier: 3.0.0-beta.44211 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)
197 '@noble/curves':213 '@noble/curves':
198 specifier: 'catalog:'214 specifier: 'catalog:'
199 version: 1.9.1215 version: 1.9.1
...@@ -205,7 +221,7 @@ importers:...@@ -205,7 +221,7 @@ importers:
205 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)221 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)
206 '@react-native-menu/menu':222 '@react-native-menu/menu':
207 specifier: ^2.0.0223 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)
209 '@shikijs/core':225 '@shikijs/core':
210 specifier: 4.2.0226 specifier: 4.2.0
211 version: 4.2.0227 version: 4.2.0
...@@ -226,7 +242,7 @@ importers:...@@ -226,7 +242,7 @@ importers:
226 version: link:../../packages/contracts242 version: link:../../packages/contracts
227 '@t3tools/mobile-markdown-text':243 '@t3tools/mobile-markdown-text':
228 specifier: file:./modules/t3-markdown-text244 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)
230 '@t3tools/mobile-review-diff-native':246 '@t3tools/mobile-review-diff-native':
231 specifier: file:./modules/t3-review-diff247 specifier: file:./modules/t3-review-diff
232 version: file:apps/mobile/modules/t3-review-diff248 version: file:apps/mobile/modules/t3-review-diff
...@@ -247,40 +263,40 @@ importers:...@@ -247,40 +263,40 @@ importers:
247 version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)263 version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)
248 expo:264 expo:
249 specifier: ^56.0.0265 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)
251 expo-asset:267 expo-asset:
252 specifier: ~56.0.15268 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)
254 expo-auth-session:270 expo-auth-session:
255 specifier: ~56.0.12271 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)
257 expo-build-properties:273 expo-build-properties:
258 specifier: ~56.0.15274 specifier: ~56.0.15
259 version: 56.0.16(expo@56.0.8)275 version: 56.0.16(expo@56.0.8)
260 expo-camera:276 expo-camera:
261 specifier: ~56.0.7277 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)
263 expo-clipboard:279 expo-clipboard:
264 specifier: ~56.0.3280 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)
266 expo-constants:282 expo-constants:
267 specifier: ~56.0.16283 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))
269 expo-crypto:285 expo-crypto:
270 specifier: ~56.0.4286 specifier: ~56.0.4
271 version: 56.0.4(expo@56.0.8)287 version: 56.0.4(expo@56.0.8)
272 expo-dev-client:288 expo-dev-client:
273 specifier: ~56.0.16289 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))
275 expo-file-system:291 expo-file-system:
276 specifier: ~56.0.7292 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))
278 expo-font:294 expo-font:
279 specifier: ~56.0.5295 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)
281 expo-glass-effect:297 expo-glass-effect:
282 specifier: ~56.0.4298 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)
284 expo-haptics:300 expo-haptics:
285 specifier: ~56.0.3301 specifier: ~56.0.3
286 version: 56.0.3(expo@56.0.8)302 version: 56.0.3(expo@56.0.8)
...@@ -289,19 +305,19 @@ importers:...@@ -289,19 +305,19 @@ importers:
289 version: 56.0.15(expo@56.0.8)305 version: 56.0.15(expo@56.0.8)
290 expo-linking:306 expo-linking:
291 specifier: ~56.0.12307 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)
293 expo-network:309 expo-network:
294 specifier: ~56.0.5310 specifier: ~56.0.5
295 version: 56.0.5(expo@56.0.8)(react@19.2.3)311 version: 56.0.5(expo@56.0.8)(react@19.2.3)
296 expo-notifications:312 expo-notifications:
297 specifier: ~56.0.14313 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)
299 expo-paste-input:315 expo-paste-input:
300 specifier: ^0.1.15316 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)
302 expo-router:318 expo-router:
303 specifier: ~56.2.7319 specifier: ~56.2.7
304 version: 56.2.8(c021de11d02907bd585610408f5252e8)320 version: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
305 expo-secure-store:321 expo-secure-store:
306 specifier: ~56.0.4322 specifier: ~56.0.4
307 version: 56.0.4(expo@56.0.8)323 version: 56.0.4(expo@56.0.8)
...@@ -310,16 +326,16 @@ importers:...@@ -310,16 +326,16 @@ importers:
310 version: 56.0.10(expo@56.0.8)(typescript@6.0.3)326 version: 56.0.10(expo@56.0.8)(typescript@6.0.3)
311 expo-symbols:327 expo-symbols:
312 specifier: ~56.0.5328 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)
314 expo-updates:330 expo-updates:
315 specifier: ~56.0.17331 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)
317 expo-web-browser:333 expo-web-browser:
318 specifier: ~56.0.5334 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))
320 expo-widgets:336 expo-widgets:
321 specifier: ~56.0.15337 specifier: ~56.0.15
322 version: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)338 version: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
323 punycode:339 punycode:
324 specifier: ^2.3.1340 specifier: ^2.3.1
325 version: 2.3.1341 version: 2.3.1
...@@ -331,43 +347,43 @@ importers:...@@ -331,43 +347,43 @@ importers:
331 version: 19.2.3(react@19.2.3)347 version: 19.2.3(react@19.2.3)
332 react-native:348 react-native:
333 specifier: 0.85.3349 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)
335 react-native-gesture-handler:351 react-native-gesture-handler:
336 specifier: ~2.31.1352 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)
338 react-native-image-viewing:354 react-native-image-viewing:
339 specifier: ^0.2.2355 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)
341 react-native-keyboard-controller:357 react-native-keyboard-controller:
342 specifier: 1.21.6358 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)
344 react-native-nitro-markdown:360 react-native-nitro-markdown:
345 specifier: ^0.5.0361 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)
347 react-native-nitro-modules:363 react-native-nitro-modules:
348 specifier: ^0.35.4364 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)
350 react-native-reanimated:366 react-native-reanimated:
351 specifier: 4.3.1367 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)
353 react-native-safe-area-context:369 react-native-safe-area-context:
354 specifier: ~5.7.0370 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)
356 react-native-screens:372 react-native-screens:
357 specifier: 4.25.2373 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)
359 react-native-shiki-engine:375 react-native-shiki-engine:
360 specifier: ^0.3.12376 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)
362 react-native-svg:378 react-native-svg:
363 specifier: 15.15.4379 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)
365 react-native-webview:381 react-native-webview:
366 specifier: ^13.16.1382 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)
368 react-native-worklets:384 react-native-worklets:
369 specifier: 0.8.3385 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)
371 shiki:387 shiki:
372 specifier: 4.2.0388 specifier: 4.2.0
373 version: 4.2.0389 version: 4.2.0
...@@ -376,7 +392,7 @@ importers:...@@ -376,7 +392,7 @@ importers:
376 version: 3.6.0392 version: 3.6.0
377 uniwind:393 uniwind:
378 specifier: ^1.6.2394 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)
380 devDependencies:396 devDependencies:
381 '@effect/vitest':397 '@effect/vitest':
382 specifier: 4.0.0-beta.78398 specifier: 4.0.0-beta.78
...@@ -466,12 +482,12 @@ importers:...@@ -466,12 +482,12 @@ importers:
466 '@base-ui/react':482 '@base-ui/react':
467 specifier: ^1.4.1483 specifier: ^1.4.1
468 version: 1.5.0(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)484 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':485 '@clerk/electron':
470 specifier: ^6.16.0486 specifier: 0.0.1-snapshot.v20260619001138
471 version: 6.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)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)
472 '@clerk/react':488 '@clerk/react':
473 specifier: ^6.9.0489 specifier: 6.10.4-snapshot.v20260619001138
474 version: 6.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)490 version: 6.10.4-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
475 '@dnd-kit/core':491 '@dnd-kit/core':
476 specifier: ^6.3.1492 specifier: ^6.3.1
477 version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)493 version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
...@@ -621,8 +637,8 @@ importers:...@@ -621,8 +637,8 @@ importers:
621 infra/relay:637 infra/relay:
622 dependencies:638 dependencies:
623 '@clerk/backend':639 '@clerk/backend':
624 specifier: 3.6.1640 specifier: 3.8.2-snapshot.v20260619001138
625 version: 3.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)641 version: 3.8.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
626 '@effect/sql-pg':642 '@effect/sql-pg':
627 specifier: 4.0.0-beta.78643 specifier: 4.0.0-beta.78
628 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))644 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))
...@@ -1516,19 +1532,60 @@ packages:...@@ -1516,19 +1532,60 @@ packages:
1516 resolution: {integrity: sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA==}1532 resolution: {integrity: sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA==}
1517 engines: {node: '>= 20.12.0'}1533 engines: {node: '>= 20.12.0'}
15181534
1519 '@clerk/backend@3.6.1':1535 '@clerk/backend@3.8.2-snapshot.v20260619001138':
1520 resolution: {integrity: sha512-LkfekzF/0UMXacX+17xy3ExRraO0mm+thXejC8Q32gWHd1wLdxK3YXDsLDF00E1r1InWBKIt2ZOxs6hTwZPJjA==}1536 resolution: {integrity: sha512-nT6M7rKTuvoDnSZwO3Th2NMjcWZy/0ZfXYyqd/o/lFpUFcQO0J4fM2e2wF9kNCl99EPvDQQUAR8APNNy/j40rg==}
1521 engines: {node: '>=20.9.0'}1537 engines: {node: '>=20.9.0'}
15221538
1523 '@clerk/clerk-js@6.16.0':1539 '@clerk/clerk-js@6.18.2-snapshot.v20260619001138':
1524 resolution: {integrity: sha512-8xv/XDsxhOZd1n4DNIRJ2EehIRUg6UiqKAnfd0L88R2t1g6sVnLi1FInJ5i8Qyx5oY/creXx6X1AZ1V5PobRkA==}1540 resolution: {integrity: sha512-BpRSi2QXdfR5nnzC7/YCCqK40m1M4A/rN5unau7QKHj6V7xChl2fOvxYjekpH+DEyw6NAe/2jdqQv35iv3T5oA==}
1525 engines: {node: '>=20.9.0'}1541 engines: {node: '>=20.9.0'}
15261542
1527 '@clerk/expo@3.4.1':1543 '@clerk/electron-passkeys-darwin-arm64@0.0.1-snapshot.v20260619001138':
1528 resolution: {integrity: sha512-gpAXsuUnsUdUD0/2XjyxaC9quF5rT+2umkmV74nBLVAFurGMMMMHvnHqrQEtZ7tH5GHNXYw5+pgmnzd1HiMQbQ==}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==}
1529 engines: {node: '>=20.9.0'}1569 engines: {node: '>=20.9.0'}
1530 peerDependencies:1570 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
1532 expo: '>=53 <57'1589 expo: '>=53 <57'
1533 expo-apple-authentication: '>=7.0.0'1590 expo-apple-authentication: '>=7.0.0'
1534 expo-auth-session: '>=5'1591 expo-auth-session: '>=5'
...@@ -1560,15 +1617,15 @@ packages:...@@ -1560,15 +1617,15 @@ packages:
1560 react-dom:1617 react-dom:
1561 optional: true1618 optional: true
15621619
1563 '@clerk/react@6.9.0':1620 '@clerk/react@6.10.4-snapshot.v20260619001138':
1564 resolution: {integrity: sha512-M0QGyGS732tYBXeG+28UgElXM2TfoSZ+4mWGisC8yxJX8NjH4hEPJTAQuZmYRLNaCyGQCuzjYVQiQRC+GbDtmA==}1621 resolution: {integrity: sha512-Z7Otjly14SoxadMmk8d9ZdbaXU0me9B1zGdCtnNIcQ7X2GCbeyKm/lOM27IzWYXZKO6o+sXlqsq9A9tcxet5nA==}
1565 engines: {node: '>=20.9.0'}1622 engines: {node: '>=20.9.0'}
1566 peerDependencies:1623 peerDependencies:
1567 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-01624 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
1568 react-dom: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-01625 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':1627 '@clerk/shared@4.19.2-snapshot.v20260619001138':
1571 resolution: {integrity: sha512-YeQ+6zDmqyor1mPHjZx18j+LssL6Pobvid8hb7HQMioSo8sGDBEVi/Z12bs+gUhe9KbdP+ygHsKOqqeGAPuPZA==}1628 resolution: {integrity: sha512-NAIz0L6+CaRrYn0DoXclUP1R0g6C2ljOzHogQ3rx9/SWUVpfaYX2lxPhURU89xvPoOGBZQb2xwk6xFh/7cjJfQ==}
1572 engines: {node: '>=20.9.0'}1629 engines: {node: '>=20.9.0'}
1573 peerDependencies:1630 peerDependencies:
1574 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-01631 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
...@@ -4962,6 +5019,14 @@ packages:...@@ -4962,6 +5019,14 @@ packages:
4962 ajv:5019 ajv:
4963 optional: true5020 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
4965 ajv-formats@3.0.1:5030 ajv-formats@3.0.1:
4966 resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}5031 resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
4967 peerDependencies:5032 peerDependencies:
...@@ -5127,6 +5192,10 @@ packages:...@@ -5127,6 +5192,10 @@ packages:
5127 resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}5192 resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
5128 engines: {node: '>= 4.0.0'}5193 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
5130 auto-bind@5.0.1:5199 auto-bind@5.0.1:
5131 resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==}5200 resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==}
5132 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}5201 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
...@@ -5535,6 +5604,10 @@ packages:...@@ -5535,6 +5604,10 @@ packages:
5535 concat-map@0.0.1:5604 concat-map@0.0.1:
5536 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}5605 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
55375606
5607 conf@10.2.0:
5608 resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
5609 engines: {node: '>=12'}
5610
5538 connect@3.7.0:5611 connect@3.7.0:
5539 resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}5612 resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
5540 engines: {node: '>= 0.10.0'}5613 engines: {node: '>= 0.10.0'}
...@@ -5643,6 +5716,10 @@ packages:...@@ -5643,6 +5716,10 @@ packages:
5643 resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==}5716 resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==}
5644 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}5717 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
5646 debug@2.6.9:5723 debug@2.6.9:
5647 resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}5724 resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
5648 peerDependencies:5725 peerDependencies:
...@@ -5782,6 +5859,10 @@ packages:...@@ -5782,6 +5859,10 @@ packages:
5782 domutils@3.2.2:5859 domutils@3.2.2:
5783 resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}5860 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
5785 dotenv-expand@11.0.7:5866 dotenv-expand@11.0.7:
5786 resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}5867 resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
5787 engines: {node: '>=12'}5868 engines: {node: '>=12'}
...@@ -5940,6 +6021,9 @@ packages:...@@ -5940,6 +6021,9 @@ packages:
5940 electron-publish@26.8.1:6021 electron-publish@26.8.1:
5941 resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==}6022 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
5943 electron-to-chromium@1.5.364:6027 electron-to-chromium@1.5.364:
5944 resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}6028 resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
59456029
...@@ -6488,6 +6572,10 @@ packages:...@@ -6488,6 +6572,10 @@ packages:
6488 find-my-way-ts@0.1.6:6572 find-my-way-ts@0.1.6:
6489 resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==}6573 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
6491 flattie@1.1.1:6579 flattie@1.1.1:
6492 resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}6580 resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
6493 engines: {node: '>=8'}6581 engines: {node: '>=8'}
...@@ -6917,6 +7005,10 @@ packages:...@@ -6917,6 +7005,10 @@ packages:
6917 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}7005 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
6918 engines: {node: '>=0.12.0'}7006 engines: {node: '>=0.12.0'}
69197007
7008 is-obj@2.0.0:
7009 resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
7010 engines: {node: '>=8'}
7011
6920 is-plain-obj@4.1.0:7012 is-plain-obj@4.1.0:
6921 resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}7013 resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
6922 engines: {node: '>=12'}7014 engines: {node: '>=12'}
...@@ -7040,6 +7132,9 @@ packages:...@@ -7040,6 +7132,9 @@ packages:
7040 json-schema-traverse@1.0.0:7132 json-schema-traverse@1.0.0:
7041 resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}7133 resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
70427134
7135 json-schema-typed@7.0.3:
7136 resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
7137
7043 json-schema-typed@8.0.2:7138 json-schema-typed@8.0.2:
7044 resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}7139 resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
70457140
...@@ -7323,6 +7418,10 @@ packages:...@@ -7323,6 +7418,10 @@ packages:
7323 resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}7418 resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
7324 engines: {node: '>= 12.0.0'}7419 engines: {node: '>= 12.0.0'}
73257420
7421 locate-path@3.0.0:
7422 resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
7423 engines: {node: '>=6'}
7424
7326 lodash.debounce@4.0.8:7425 lodash.debounce@4.0.8:
7327 resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}7426 resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
73287427
...@@ -7677,6 +7776,10 @@ packages:...@@ -7677,6 +7776,10 @@ packages:
7677 resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}7776 resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
7678 engines: {node: '>=6'}7777 engines: {node: '>=6'}
76797778
7779 mimic-fn@3.1.0:
7780 resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
7781 engines: {node: '>=8'}
7782
7680 mimic-function@5.0.1:7783 mimic-function@5.0.1:
7681 resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}7784 resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
7682 engines: {node: '>=18'}7785 engines: {node: '>=18'}
...@@ -7985,6 +8088,10 @@ packages:...@@ -7985,6 +8088,10 @@ packages:
7985 resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}8088 resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
7986 engines: {node: '>=8'}8089 engines: {node: '>=8'}
79878090
8091 p-limit@2.3.0:
8092 resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
8093 engines: {node: '>=6'}
8094
7988 p-limit@3.1.0:8095 p-limit@3.1.0:
7989 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}8096 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
7990 engines: {node: '>=10'}8097 engines: {node: '>=10'}
...@@ -7993,6 +8100,10 @@ packages:...@@ -7993,6 +8100,10 @@ packages:
7993 resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==}8100 resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==}
7994 engines: {node: '>=20'}8101 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
7996 p-queue@9.3.0:8107 p-queue@9.3.0:
7997 resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==}8108 resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==}
7998 engines: {node: '>=20'}8109 engines: {node: '>=20'}
...@@ -8001,6 +8112,10 @@ packages:...@@ -8001,6 +8112,10 @@ packages:
8001 resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}8112 resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}
8002 engines: {node: '>=20'}8113 engines: {node: '>=20'}
80038114
8115 p-try@2.2.0:
8116 resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
8117 engines: {node: '>=6'}
8118
8004 package-manager-detector@1.6.0:8119 package-manager-detector@1.6.0:
8005 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}8120 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
80068121
...@@ -8031,6 +8146,10 @@ packages:...@@ -8031,6 +8146,10 @@ packages:
8031 path-browserify@1.0.1:8146 path-browserify@1.0.1:
8032 resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}8147 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
8034 path-expression-matcher@1.5.0:8153 path-expression-matcher@1.5.0:
8035 resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}8154 resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
8036 engines: {node: '>=14.0.0'}8155 engines: {node: '>=14.0.0'}
...@@ -8141,6 +8260,10 @@ packages:...@@ -8141,6 +8260,10 @@ packages:
8141 resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}8260 resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
8142 engines: {node: '>=16.20.0'}8261 engines: {node: '>=16.20.0'}
81438262
8263 pkg-up@3.1.0:
8264 resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
8265 engines: {node: '>=8'}
8266
8144 playwright-core@1.60.0:8267 playwright-core@1.60.0:
8145 resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}8268 resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
8146 engines: {node: '>=18'}8269 engines: {node: '>=18'}
...@@ -9211,6 +9334,10 @@ packages:...@@ -9211,6 +9334,10 @@ packages:
9211 resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}9334 resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
9212 engines: {node: '>=8'}9335 engines: {node: '>=8'}
92139336
9337 type-fest@2.19.0:
9338 resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
9339 engines: {node: '>=12.20'}
9340
9214 type-fest@5.7.0:9341 type-fest@5.7.0:
9215 resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==}9342 resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==}
9216 engines: {node: '>=20'}9343 engines: {node: '>=20'}
...@@ -10712,10 +10839,10 @@ snapshots:...@@ -10712,10 +10839,10 @@ snapshots:
10712 optionalDependencies:10839 optionalDependencies:
10713 '@types/react': 19.2.1610840 '@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)':
10716 dependencies:10843 dependencies:
10717 react: 19.2.310844 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
10720 '@capsizecss/unpack@4.0.0':10847 '@capsizecss/unpack@4.0.0':
10721 dependencies:10848 dependencies:
...@@ -10744,18 +10871,18 @@ snapshots:...@@ -10744,18 +10871,18 @@ snapshots:
10744 fast-wrap-ansi: 0.2.210871 fast-wrap-ansi: 0.2.2
10745 sisteransi: 1.0.510872 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)':
10748 dependencies:10875 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)
10750 standardwebhooks: 1.0.010877 standardwebhooks: 1.0.0
10751 tslib: 2.8.110878 tslib: 2.8.1
10752 transitivePeerDependencies:10879 transitivePeerDependencies:
10753 - react10880 - react
10754 - react-dom10881 - 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)':
10757 dependencies:10884 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)
10759 '@stripe/stripe-js': 5.6.010886 '@stripe/stripe-js': 5.6.0
10760 '@swc/helpers': 0.5.2110887 '@swc/helpers': 0.5.21
10761 '@tanstack/query-core': 5.100.1410888 '@tanstack/query-core': 5.100.14
...@@ -10770,9 +10897,9 @@ snapshots:...@@ -10770,9 +10897,9 @@ snapshots:
10770 - react10897 - react
10771 - react-dom10898 - 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)':
10774 dependencies:10901 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)
10776 '@stripe/stripe-js': 5.6.010903 '@stripe/stripe-js': 5.6.0
10777 '@swc/helpers': 0.5.2110904 '@swc/helpers': 0.5.21
10778 '@tanstack/query-core': 5.100.1410905 '@tanstack/query-core': 5.100.14
...@@ -10787,40 +10914,72 @@ snapshots:...@@ -10787,40 +10914,72 @@ snapshots:
10787 - react10914 - react
10788 - react-dom10915 - 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)':
10791 dependencies:10950 dependencies:
10792 '@clerk/clerk-js': 6.16.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)
10793 '@clerk/react': 6.9.0(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)
10794 '@clerk/shared': 4.17.0(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)
10795 base-64: 1.0.010954 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)
10797 react: 19.2.310956 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)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)
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))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))
10800 tslib: 2.8.110959 tslib: 2.8.1
10801 optionalDependencies:10960 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)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)
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))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))
10804 expo-crypto: 56.0.4(expo@56.0.8)10963 expo-crypto: 56.0.4(expo@56.0.8)
10805 expo-secure-store: 56.0.4(expo@56.0.8)10964 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))
10807 react-dom: 19.2.3(react@19.2.3)10966 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)':
10810 dependencies:10969 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)
10812 react: 19.2.310971 react: 19.2.3
10813 react-dom: 19.2.3(react@19.2.3)10972 react-dom: 19.2.3(react@19.2.3)
10814 tslib: 2.8.110973 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)':
10817 dependencies:10976 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)
10819 react: 19.2.610978 react: 19.2.6
10820 react-dom: 19.2.6(react@19.2.6)10979 react-dom: 19.2.6(react@19.2.6)
10821 tslib: 2.8.110980 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)':
10824 dependencies:10983 dependencies:
10825 '@tanstack/query-core': 5.100.1410984 '@tanstack/query-core': 5.100.14
10826 dequal: 2.0.310985 dequal: 2.0.3
...@@ -10830,7 +10989,7 @@ snapshots:...@@ -10830,7 +10989,7 @@ snapshots:
10830 react: 19.2.310989 react: 19.2.3
10831 react-dom: 19.2.3(react@19.2.3)10990 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)':
10834 dependencies:10993 dependencies:
10835 '@tanstack/query-core': 5.100.1410994 '@tanstack/query-core': 5.100.14
10836 dequal: 2.0.310995 dequal: 2.0.3
...@@ -11370,7 +11529,7 @@ snapshots:...@@ -11370,7 +11529,7 @@ snapshots:
1137011529
11371 '@expo-google-fonts/material-symbols@0.4.38': {}11530 '@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)':
11374 dependencies:11533 dependencies:
11375 '@expo/code-signing-certificates': 0.0.611534 '@expo/code-signing-certificates': 0.0.6
11376 '@expo/config': 56.0.9(typescript@6.0.3)11535 '@expo/config': 56.0.9(typescript@6.0.3)
...@@ -11380,7 +11539,7 @@ snapshots:...@@ -11380,7 +11539,7 @@ snapshots:
11380 '@expo/image-utils': 0.10.1(typescript@6.0.3)11539 '@expo/image-utils': 0.10.1(typescript@6.0.3)
11381 '@expo/inline-modules': 0.0.10(typescript@6.0.3)11540 '@expo/inline-modules': 0.0.10(typescript@6.0.3)
11382 '@expo/json-file': 10.2.011541 '@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)
11384 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)11543 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
11385 '@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)11544 '@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)
11386 '@expo/metro-file-map': 56.0.311545 '@expo/metro-file-map': 56.0.3
...@@ -11405,7 +11564,7 @@ snapshots:...@@ -11405,7 +11564,7 @@ snapshots:
11405 connect: 3.7.011564 connect: 3.7.0
11406 debug: 4.4.311565 debug: 4.4.3
11407 dnssd-advertise: 1.1.411566 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)
11409 expo-server: 56.0.411568 expo-server: 56.0.4
11410 fetch-nodeshim: 0.4.1011569 fetch-nodeshim: 0.4.10
11411 getenv: 2.0.011570 getenv: 2.0.0
...@@ -11431,8 +11590,8 @@ snapshots:...@@ -11431,8 +11590,8 @@ snapshots:
11431 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)11590 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
11432 zod: 3.25.7611591 zod: 3.25.76
11433 optionalDependencies:11592 optionalDependencies:
11434 expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)11593 expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
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)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)
11436 transitivePeerDependencies:11595 transitivePeerDependencies:
11437 - '@expo/dom-webview'11596 - '@expo/dom-webview'
11438 - '@expo/metro-runtime'11597 - '@expo/metro-runtime'
...@@ -11494,18 +11653,18 @@ snapshots:...@@ -11494,18 +11653,18 @@ snapshots:
11494 transitivePeerDependencies:11653 transitivePeerDependencies:
11495 - supports-color11654 - 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)':
11498 dependencies:11657 dependencies:
11499 chalk: 4.1.211658 chalk: 4.1.2
11500 optionalDependencies:11659 optionalDependencies:
11501 react: 19.2.311660 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)':
11505 dependencies:11664 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)
11507 react: 19.2.311666 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
11510 '@expo/env@2.3.0':11669 '@expo/env@2.3.0':
11511 dependencies:11670 dependencies:
...@@ -11566,13 +11725,13 @@ snapshots:...@@ -11566,13 +11725,13 @@ snapshots:
11566 - supports-color11725 - supports-color
11567 - typescript11726 - 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)':
11570 dependencies:11729 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)
11572 anser: 1.4.1011731 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)
11574 react: 19.2.311733 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)
11576 stacktrace-parser: 0.1.1111735 stacktrace-parser: 0.1.11
1157711736
11578 '@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)':11737 '@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:...@@ -11602,7 +11761,7 @@ snapshots:
11602 postcss: 8.5.1511761 postcss: 8.5.15
11603 resolve-from: 5.0.011762 resolve-from: 5.0.0
11604 optionalDependencies:11763 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)
11606 transitivePeerDependencies:11765 transitivePeerDependencies:
11607 - bufferutil11766 - bufferutil
11608 - supports-color11767 - supports-color
...@@ -11620,14 +11779,14 @@ snapshots:...@@ -11620,14 +11779,14 @@ snapshots:
11620 transitivePeerDependencies:11779 transitivePeerDependencies:
11621 - supports-color11780 - 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)':
11624 dependencies:11783 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)
11626 anser: 1.4.1011785 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)
11628 pretty-format: 29.7.011787 pretty-format: 29.7.0
11629 react: 19.2.311788 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)
11631 stacktrace-parser: 0.1.1111790 stacktrace-parser: 0.1.11
11632 whatwg-fetch: 3.6.2011791 whatwg-fetch: 3.6.20
11633 optionalDependencies:11792 optionalDependencies:
...@@ -11702,14 +11861,14 @@ snapshots:...@@ -11702,14 +11861,14 @@ snapshots:
11702 '@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)':11861 '@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)':
11703 dependencies:11862 dependencies:
11704 debug: 4.4.311863 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)11864 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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))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))
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)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)
11708 expo-server: 56.0.411867 expo-server: 56.0.4
11709 react: 19.2.311868 react: 19.2.3
11710 optionalDependencies:11869 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)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)
11712 expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)11871 expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
11713 react-dom: 19.2.3(react@19.2.3)11872 react-dom: 19.2.3(react@19.2.3)
11714 transitivePeerDependencies:11873 transitivePeerDependencies:
11715 - supports-color11874 - supports-color
...@@ -11724,18 +11883,18 @@ snapshots:...@@ -11724,18 +11883,18 @@ snapshots:
1172411883
11725 '@expo/sudo-prompt@9.3.2': {}11884 '@expo/sudo-prompt@9.3.2': {}
1172611885
11727 '@expo/ui@56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)':11886 '@expo/ui@56.0.15(961c4aa6f32829b318e3c87ef20ad401)':
11728 dependencies:11887 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)
11730 react: 19.2.311889 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)
11732 sf-symbols-typescript: 2.2.011891 sf-symbols-typescript: 2.2.0
11733 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)11892 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)
11734 optionalDependencies:11893 optionalDependencies:
11735 '@babel/core': 7.29.711894 '@babel/core': 7.29.7
11736 react-dom: 19.2.3(react@19.2.3)11895 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)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)
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)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)
11739 transitivePeerDependencies:11898 transitivePeerDependencies:
11740 - '@types/react'11899 - '@types/react'
11741 - '@types/react-dom'11900 - '@types/react-dom'
...@@ -11996,13 +12155,13 @@ snapshots:...@@ -11996,13 +12155,13 @@ snapshots:
11996 dependencies:12155 dependencies:
11997 jsbi: 4.3.212156 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)':
12000 dependencies:12159 dependencies:
12001 react: 19.2.312160 react: 19.2.3
12002 use-sync-external-store: 1.6.0(react@19.2.3)12161 use-sync-external-store: 1.6.0(react@19.2.3)
12003 optionalDependencies:12162 optionalDependencies:
12004 react-dom: 19.2.3(react@19.2.3)12163 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
12007 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':12166 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
12008 dependencies:12167 dependencies:
...@@ -12825,15 +12984,15 @@ snapshots:...@@ -12825,15 +12984,15 @@ snapshots:
12825 prompts: 2.4.212984 prompts: 2.4.2
12826 tinyexec: 1.2.412985 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)':
12829 dependencies:12988 dependencies:
12830 react: 19.2.312989 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)':
12834 dependencies:12993 dependencies:
12835 react: 19.2.312994 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
12838 '@react-native/assets-registry@0.85.3': {}12997 '@react-native/assets-registry@0.85.3': {}
1283912998
...@@ -12893,7 +13052,7 @@ snapshots:...@@ -12893,7 +13052,7 @@ snapshots:
12893 tinyglobby: 0.2.1713052 tinyglobby: 0.2.17
12894 yargs: 17.7.213053 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)':
12897 dependencies:13056 dependencies:
12898 '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)13057 '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)
12899 debug: 4.4.313058 debug: 4.4.3
...@@ -12903,7 +13062,7 @@ snapshots:...@@ -12903,7 +13062,7 @@ snapshots:
12903 metro-core: 0.84.413062 metro-core: 0.84.4
12904 semver: 7.8.113063 semver: 7.8.1
12905 optionalDependencies:13064 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)
12907 transitivePeerDependencies:13066 transitivePeerDependencies:
12908 - bufferutil13067 - bufferutil
12909 - supports-color13068 - supports-color
...@@ -12951,7 +13110,7 @@ snapshots:...@@ -12951,7 +13110,7 @@ snapshots:
12951 transitivePeerDependencies:13110 transitivePeerDependencies:
12952 - supports-color13111 - 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)':
12955 dependencies:13114 dependencies:
12956 '@react-native/js-polyfills': 0.85.313115 '@react-native/js-polyfills': 0.85.3
12957 '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7)13116 '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7)
...@@ -12959,16 +13118,18 @@ snapshots:...@@ -12959,16 +13118,18 @@ snapshots:
12959 metro-runtime: 0.84.413118 metro-runtime: 0.84.4
12960 transitivePeerDependencies:13119 transitivePeerDependencies:
12961 - '@babel/core'13120 - '@babel/core'
13121 - bufferutil
12962 - supports-color13122 - supports-color
13123 - utf-8-validate
1296313124
12964 '@react-native/normalize-colors@0.85.3': {}13125 '@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)':
12967 dependencies:13128 dependencies:
12968 invariant: 2.2.413129 invariant: 2.2.4
12969 nullthrows: 1.1.113130 nullthrows: 1.1.1
12970 react: 19.2.313131 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)
12972 optionalDependencies:13133 optionalDependencies:
12973 '@types/react': 19.2.1613134 '@types/react': 19.2.16
1297413135
...@@ -13381,15 +13542,15 @@ snapshots:...@@ -13381,15 +13542,15 @@ snapshots:
13381 dependencies:13542 dependencies:
13382 defer-to-connect: 2.0.113543 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)':
13385 dependencies:13546 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)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)
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)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)
13388 expo-haptics: 56.0.3(expo@56.0.8)13549 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)
13390 react: 19.2.313551 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)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)
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)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
13394 '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {}13555 '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {}
1339513556
...@@ -14060,6 +14221,10 @@ snapshots:...@@ -14060,6 +14221,10 @@ snapshots:
14060 optionalDependencies:14221 optionalDependencies:
14061 ajv: 8.20.014222 ajv: 8.20.0
1406214223
14224 ajv-formats@2.1.1(ajv@8.20.0):
14225 optionalDependencies:
14226 ajv: 8.20.0
14227
14063 ajv-formats@3.0.1(ajv@8.20.0):14228 ajv-formats@3.0.1(ajv@8.20.0):
14064 optionalDependencies:14229 optionalDependencies:
14065 ajv: 8.20.014230 ajv: 8.20.0
...@@ -14355,6 +14520,8 @@ snapshots:...@@ -14355,6 +14520,8 @@ snapshots:
1435514520
14356 at-least-node@1.0.0: {}14521 at-least-node@1.0.0: {}
1435714522
14523 atomically@1.7.0: {}
14524
14358 auto-bind@5.0.1: {}14525 auto-bind@5.0.1: {}
1435914526
14360 aws-ssl-profiles@1.1.2: {}14527 aws-ssl-profiles@1.1.2: {}
...@@ -14459,8 +14626,8 @@ snapshots:...@@ -14459,8 +14626,8 @@ snapshots:
14459 react-refresh: 0.14.214626 react-refresh: 0.14.2
14460 optionalDependencies:14627 optionalDependencies:
14461 '@babel/runtime': 7.29.714628 '@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)14629 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
14463 expo-widgets: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)14630 expo-widgets: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
14464 transitivePeerDependencies:14631 transitivePeerDependencies:
14465 - '@babel/core'14632 - '@babel/core'
14466 - supports-color14633 - supports-color
...@@ -14821,6 +14988,19 @@ snapshots:...@@ -14821,6 +14988,19 @@ snapshots:
1482114988
14822 concat-map@0.0.1: {}14989 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
14824 connect@3.7.0:15004 connect@3.7.0:
14825 dependencies:15005 dependencies:
14826 debug: 2.6.915006 debug: 2.6.9
...@@ -14923,6 +15103,10 @@ snapshots:...@@ -14923,6 +15103,10 @@ snapshots:
1492315103
14924 culori@4.0.2: {}15104 culori@4.0.2: {}
1492515105
15106 debounce-fn@4.0.0:
15107 dependencies:
15108 mimic-fn: 3.1.0
15109
14926 debug@2.6.9:15110 debug@2.6.9:
14927 dependencies:15111 dependencies:
14928 ms: 2.0.015112 ms: 2.0.0
...@@ -15052,6 +15236,10 @@ snapshots:...@@ -15052,6 +15236,10 @@ snapshots:
15052 domelementtype: 2.3.015236 domelementtype: 2.3.0
15053 domhandler: 5.0.315237 domhandler: 5.0.3
1505415238
15239 dot-prop@6.0.1:
15240 dependencies:
15241 is-obj: 2.0.0
15242
15055 dotenv-expand@11.0.7:15243 dotenv-expand@11.0.7:
15056 dependencies:15244 dependencies:
15057 dotenv: 16.6.115245 dotenv: 16.6.1
...@@ -15142,6 +15330,11 @@ snapshots:...@@ -15142,6 +15330,11 @@ snapshots:
15142 transitivePeerDependencies:15330 transitivePeerDependencies:
15143 - supports-color15331 - supports-color
1514415332
15333 electron-store@8.2.0:
15334 dependencies:
15335 conf: 10.2.0
15336 type-fest: 2.19.0
15337
15145 electron-to-chromium@1.5.364: {}15338 electron-to-chromium@1.5.364: {}
1514615339
15147 electron-updater@6.8.3:15340 electron-updater@6.8.3:
...@@ -15325,29 +15518,29 @@ snapshots:...@@ -15325,29 +15518,29 @@ snapshots:
1532515518
15326 expo-application@56.0.3(expo@56.0.8):15519 expo-application@56.0.3(expo@56.0.8):
15327 dependencies:15520 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):
15331 dependencies:15524 dependencies:
15332 '@expo/image-utils': 0.10.1(typescript@6.0.3)15525 '@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)15526 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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))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))
15335 react: 19.2.315528 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)
15337 transitivePeerDependencies:15530 transitivePeerDependencies:
15338 - supports-color15531 - supports-color
15339 - typescript15532 - 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):
15342 dependencies:15535 dependencies:
15343 expo-application: 56.0.3(expo@56.0.8)15536 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))
15345 expo-crypto: 56.0.4(expo@56.0.8)15538 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)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)
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))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))
15348 invariant: 2.2.415541 invariant: 2.2.4
15349 react: 19.2.315542 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)
15351 transitivePeerDependencies:15544 transitivePeerDependencies:
15352 - expo15545 - expo
15353 - supports-color15546 - supports-color
...@@ -15355,119 +15548,119 @@ snapshots:...@@ -15355,119 +15548,119 @@ snapshots:
15355 expo-build-properties@56.0.16(expo@56.0.8):15548 expo-build-properties@56.0.16(expo@56.0.8):
15356 dependencies:15549 dependencies:
15357 '@expo/schema-utils': 56.0.115550 '@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)
15359 resolve-from: 5.0.015552 resolve-from: 5.0.0
15360 semver: 7.8.115553 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):
15363 dependencies:15556 dependencies:
15364 barcode-detector: 3.2.0(@types/emscripten@1.41.5)15557 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)
15366 react: 19.2.315559 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)
15368 transitivePeerDependencies:15561 transitivePeerDependencies:
15369 - '@types/emscripten'15562 - '@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):
15372 dependencies:15565 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)
15374 react: 19.2.315567 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)):
15378 dependencies:15571 dependencies:
15379 '@expo/env': 2.3.015572 '@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)15573 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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)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)
15382 transitivePeerDependencies:15575 transitivePeerDependencies:
15383 - supports-color15576 - supports-color
1538415577
15385 expo-crypto@56.0.4(expo@56.0.8):15578 expo-crypto@56.0.4(expo@56.0.8):
15386 dependencies:15579 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)):
15390 dependencies:15583 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)15584 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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))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))
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))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))
15394 expo-dev-menu-interface: 56.0.1(expo@56.0.8)15587 expo-dev-menu-interface: 56.0.1(expo@56.0.8)
15395 expo-manifests: 56.0.4(expo@56.0.8)15588 expo-manifests: 56.0.4(expo@56.0.8)
15396 expo-updates-interface: 56.0.2(expo@56.0.8)15589 expo-updates-interface: 56.0.2(expo@56.0.8)
15397 transitivePeerDependencies:15590 transitivePeerDependencies:
15398 - react-native15591 - 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)):
15401 dependencies:15594 dependencies:
15402 '@expo/schema-utils': 56.0.115595 '@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)15596 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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))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))
15405 expo-manifests: 56.0.4(expo@56.0.8)15598 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
15408 expo-dev-menu-interface@56.0.1(expo@56.0.8):15601 expo-dev-menu-interface@56.0.1(expo@56.0.8):
15409 dependencies:15602 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)):
15413 dependencies:15606 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)
15415 expo-dev-menu-interface: 56.0.1(expo@56.0.8)15608 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
15418 expo-eas-client@56.0.1: {}15611 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)):
15421 dependencies:15614 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)15615 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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)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):
15426 dependencies:15619 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)
15428 fontfaceobserver: 2.3.015621 fontfaceobserver: 2.3.0
15429 react: 19.2.315622 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):
15433 dependencies:15626 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)
15435 react: 19.2.315628 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
15438 expo-haptics@56.0.3(expo@56.0.8):15631 expo-haptics@56.0.3(expo@56.0.8):
15439 dependencies:15632 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
15442 expo-image-loader@56.0.3(expo@56.0.8):15635 expo-image-loader@56.0.3(expo@56.0.8):
15443 dependencies:15636 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
15446 expo-image-picker@56.0.15(expo@56.0.8):15639 expo-image-picker@56.0.15(expo@56.0.8):
15447 dependencies:15640 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)
15449 expo-image-loader: 56.0.3(expo@56.0.8)15642 expo-image-loader: 56.0.3(expo@56.0.8)
1545015643
15451 expo-json-utils@56.0.0: {}15644 expo-json-utils@56.0.0: {}
1545215645
15453 expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3):15646 expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3):
15454 dependencies:15647 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)
15456 react: 19.2.315649 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):
15459 dependencies:15652 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))
15461 invariant: 2.2.415654 invariant: 2.2.4
15462 react: 19.2.315655 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)
15464 transitivePeerDependencies:15657 transitivePeerDependencies:
15465 - expo15658 - expo
15466 - supports-color15659 - supports-color
1546715660
15468 expo-manifests@56.0.4(expo@56.0.8):15661 expo-manifests@56.0.4(expo@56.0.8):
15469 dependencies:15662 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)
15471 expo-json-utils: 56.0.015664 expo-json-utils: 56.0.0
1547215665
15473 expo-modules-autolinking@56.0.14(typescript@6.0.3):15666 expo-modules-autolinking@56.0.14(typescript@6.0.3):
...@@ -15480,66 +15673,66 @@ snapshots:...@@ -15480,66 +15673,66 @@ snapshots:
15480 - supports-color15673 - supports-color
15481 - typescript15674 - 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):
15484 dependencies:15677 dependencies:
15485 '@expo/expo-modules-macros-plugin': 0.0.915678 '@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))
15487 invariant: 2.2.415680 invariant: 2.2.4
15488 react: 19.2.315681 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)
15490 optionalDependencies:15683 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)):
15494 dependencies:15687 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
15497 expo-network@56.0.5(expo@56.0.8)(react@19.2.3):15690 expo-network@56.0.5(expo@56.0.8)(react@19.2.3):
15498 dependencies:15691 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)
15500 react: 19.2.315693 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):
15503 dependencies:15696 dependencies:
15504 '@expo/image-utils': 0.10.1(typescript@6.0.3)15697 '@expo/image-utils': 0.10.1(typescript@6.0.3)
15505 abort-controller: 3.0.015698 abort-controller: 3.0.0
15506 badgin: 1.2.315699 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)
15508 expo-application: 56.0.3(expo@56.0.8)15701 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))
15510 react: 19.2.315703 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)
15512 transitivePeerDependencies:15705 transitivePeerDependencies:
15513 - supports-color15706 - supports-color
15514 - typescript15707 - 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):
15517 dependencies:15710 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)
15519 react: 19.2.315712 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):
15523 dependencies:15716 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)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)
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)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)
15526 '@expo/schema-utils': 56.0.115719 '@expo/schema-utils': 56.0.1
15527 '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)15720 '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
15528 '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3)15721 '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3)
15529 '@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)15722 '@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)
15531 '@testing-library/jest-dom': 6.9.115724 '@testing-library/jest-dom': 6.9.1
15532 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)15725 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
15533 client-only: 0.0.115726 client-only: 0.0.1
15534 color: 4.2.315727 color: 4.2.3
15535 debug: 4.4.315728 debug: 4.4.3
15536 escape-string-regexp: 4.0.015729 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)15730 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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))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))
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)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)
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)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)
15541 expo-server: 56.0.415734 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)
15543 fast-deep-equal: 3.1.315736 fast-deep-equal: 3.1.3
15544 invariant: 2.2.415737 invariant: 2.2.4
15545 nanoid: 3.3.1215738 nanoid: 3.3.12
...@@ -15547,18 +15740,18 @@ snapshots:...@@ -15547,18 +15740,18 @@ snapshots:
15547 react: 19.2.315740 react: 19.2.3
15548 react-fast-compare: 3.2.215741 react-fast-compare: 3.2.2
15549 react-is: 19.2.715742 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)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)
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)15744 react-native-drawer-layout: 4.2.4(0e9729601f58a7a7ae26c76fe6017455)
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)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)
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)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)
15554 server-only: 0.0.115747 server-only: 0.0.1
15555 sf-symbols-typescript: 2.2.015748 sf-symbols-typescript: 2.2.0
15556 shallowequal: 1.1.015749 shallowequal: 1.1.0
15557 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)15750 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)
15558 optionalDependencies:15751 optionalDependencies:
15559 react-dom: 19.2.3(react@19.2.3)15752 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)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)
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)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)
15562 transitivePeerDependencies:15755 transitivePeerDependencies:
15563 - '@babel/core'15756 - '@babel/core'
15564 - '@testing-library/dom'15757 - '@testing-library/dom'
...@@ -15570,7 +15763,7 @@ snapshots:...@@ -15570,7 +15763,7 @@ snapshots:
1557015763
15571 expo-secure-store@56.0.4(expo@56.0.8):15764 expo-secure-store@56.0.4(expo@56.0.8):
15572 dependencies:15765 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
15575 expo-server@56.0.4: {}15768 expo-server@56.0.4: {}
1557615769
...@@ -15578,7 +15771,7 @@ snapshots:...@@ -15578,7 +15771,7 @@ snapshots:
15578 dependencies:15771 dependencies:
15579 '@expo/config-plugins': 56.0.8(typescript@6.0.3)15772 '@expo/config-plugins': 56.0.8(typescript@6.0.3)
15580 '@expo/image-utils': 0.10.1(typescript@6.0.3)15773 '@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)
15582 xml2js: 0.6.015775 xml2js: 0.6.0
15583 transitivePeerDependencies:15776 transitivePeerDependencies:
15584 - supports-color15777 - supports-color
...@@ -15586,20 +15779,20 @@ snapshots:...@@ -15586,20 +15779,20 @@ snapshots:
1558615779
15587 expo-structured-headers@56.0.0: {}15780 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):
15590 dependencies:15783 dependencies:
15591 '@expo-google-fonts/material-symbols': 0.4.3815784 '@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)15785 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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)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)
15594 react: 19.2.315787 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)
15596 sf-symbols-typescript: 2.2.015789 sf-symbols-typescript: 2.2.0
1559715790
15598 expo-updates-interface@56.0.2(expo@56.0.8):15791 expo-updates-interface@56.0.2(expo@56.0.8):
15599 dependencies:15792 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):
15603 dependencies:15796 dependencies:
15604 '@expo/code-signing-certificates': 0.0.615797 '@expo/code-signing-certificates': 0.0.6
15605 '@expo/plist': 0.7.015798 '@expo/plist': 0.7.0
...@@ -15607,7 +15800,7 @@ snapshots:...@@ -15607,7 +15800,7 @@ snapshots:
15607 arg: 4.1.315800 arg: 4.1.3
15608 chalk: 4.1.215801 chalk: 4.1.2
15609 debug: 4.4.315802 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)
15611 expo-eas-client: 56.0.115804 expo-eas-client: 56.0.1
15612 expo-manifests: 56.0.4(expo@56.0.8)15805 expo-manifests: 56.0.4(expo@56.0.8)
15613 expo-structured-headers: 56.0.015806 expo-structured-headers: 56.0.0
...@@ -15617,25 +15810,25 @@ snapshots:...@@ -15617,25 +15810,25 @@ snapshots:
15617 ignore: 5.3.215810 ignore: 5.3.2
15618 nullthrows: 1.1.115811 nullthrows: 1.1.1
15619 react: 19.2.315812 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)
15621 resolve-from: 5.0.015814 resolve-from: 5.0.0
15622 optionalDependencies:15815 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))
15624 transitivePeerDependencies:15817 transitivePeerDependencies:
15625 - supports-color15818 - 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)):
15628 dependencies:15821 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)15822 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
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)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):
15633 dependencies:15826 dependencies:
15634 '@expo/plist': 0.7.015827 '@expo/plist': 0.7.0
15635 '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)15828 '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
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)15829 expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15637 react: 19.2.315830 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)
15639 transitivePeerDependencies:15832 transitivePeerDependencies:
15640 - '@babel/core'15833 - '@babel/core'
15641 - '@types/react'15834 - '@types/react'
...@@ -15644,37 +15837,37 @@ snapshots:...@@ -15644,37 +15837,37 @@ snapshots:
15644 - react-native-reanimated15837 - react-native-reanimated
15645 - react-native-worklets15838 - 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):
15648 dependencies:15841 dependencies:
15649 '@babel/runtime': 7.29.715842 '@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)
15651 '@expo/config': 56.0.9(typescript@6.0.3)15844 '@expo/config': 56.0.9(typescript@6.0.3)
15652 '@expo/config-plugins': 56.0.8(typescript@6.0.3)15845 '@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)
15654 '@expo/fingerprint': 0.19.315847 '@expo/fingerprint': 0.19.3
15655 '@expo/local-build-cache-provider': 56.0.8(typescript@6.0.3)15848 '@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)
15657 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)15850 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
15658 '@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)15851 '@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)
15659 '@ungap/structured-clone': 1.3.115852 '@ungap/structured-clone': 1.3.1
15660 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)15853 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)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)
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))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))
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))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))
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)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)
15665 expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3)15858 expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3)
15666 expo-modules-autolinking: 56.0.14(typescript@6.0.3)15859 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)
15668 pretty-format: 29.7.015861 pretty-format: 29.7.0
15669 react: 19.2.315862 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)
15671 react-refresh: 0.14.215864 react-refresh: 0.14.2
15672 whatwg-url-minimum: 0.1.215865 whatwg-url-minimum: 0.1.2
15673 optionalDependencies:15866 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)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)
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)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)
15676 react-dom: 19.2.3(react@19.2.3)15869 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)
15678 transitivePeerDependencies:15871 transitivePeerDependencies:
15679 - '@babel/core'15872 - '@babel/core'
15680 - bufferutil15873 - bufferutil
...@@ -15862,6 +16055,10 @@ snapshots:...@@ -15862,6 +16055,10 @@ snapshots:
1586216055
15863 find-my-way-ts@0.1.6: {}16056 find-my-way-ts@0.1.6: {}
1586416057
16058 find-up@3.0.0:
16059 dependencies:
16060 locate-path: 3.0.0
16061
15865 flattie@1.1.1: {}16062 flattie@1.1.1: {}
1586616063
15867 flow-enums-runtime@0.0.6: {}16064 flow-enums-runtime@0.0.6: {}
...@@ -16388,6 +16585,8 @@ snapshots:...@@ -16388,6 +16585,8 @@ snapshots:
1638816585
16389 is-number@7.0.0: {}16586 is-number@7.0.0: {}
1639016587
16588 is-obj@2.0.0: {}
16589
16391 is-plain-obj@4.1.0: {}16590 is-plain-obj@4.1.0: {}
1639216591
16393 is-promise@4.0.0: {}16592 is-promise@4.0.0: {}
...@@ -16488,6 +16687,8 @@ snapshots:...@@ -16488,6 +16687,8 @@ snapshots:
1648816687
16489 json-schema-traverse@1.0.0: {}16688 json-schema-traverse@1.0.0: {}
1649016689
16690 json-schema-typed@7.0.3: {}
16691
16491 json-schema-typed@8.0.2: {}16692 json-schema-typed@8.0.2: {}
1649216693
16493 json-stringify-safe@5.0.1:16694 json-stringify-safe@5.0.1:
...@@ -16715,6 +16916,11 @@ snapshots:...@@ -16715,6 +16916,11 @@ snapshots:
16715 lightningcss-win32-arm64-msvc: 1.32.016916 lightningcss-win32-arm64-msvc: 1.32.0
16716 lightningcss-win32-x64-msvc: 1.32.016917 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
16718 lodash.debounce@4.0.8: {}16924 lodash.debounce@4.0.8: {}
1671916925
16720 lodash.escaperegexp@4.1.2: {}16926 lodash.escaperegexp@4.1.2: {}
...@@ -17363,6 +17569,8 @@ snapshots:...@@ -17363,6 +17569,8 @@ snapshots:
1736317569
17364 mimic-fn@2.1.0: {}17570 mimic-fn@2.1.0: {}
1736517571
17572 mimic-fn@3.1.0: {}
17573
17366 mimic-function@5.0.1: {}17574 mimic-function@5.0.1: {}
1736717575
17368 mimic-response@1.0.1: {}17576 mimic-response@1.0.1: {}
...@@ -17703,6 +17911,10 @@ snapshots:...@@ -17703,6 +17911,10 @@ snapshots:
1770317911
17704 p-cancelable@2.1.1: {}17912 p-cancelable@2.1.1: {}
1770517913
17914 p-limit@2.3.0:
17915 dependencies:
17916 p-try: 2.2.0
17917
17706 p-limit@3.1.0:17918 p-limit@3.1.0:
17707 dependencies:17919 dependencies:
17708 yocto-queue: 0.1.017920 yocto-queue: 0.1.0
...@@ -17711,6 +17923,10 @@ snapshots:...@@ -17711,6 +17923,10 @@ snapshots:
17711 dependencies:17923 dependencies:
17712 yocto-queue: 1.2.217924 yocto-queue: 1.2.2
1771317925
17926 p-locate@3.0.0:
17927 dependencies:
17928 p-limit: 2.3.0
17929
17714 p-queue@9.3.0:17930 p-queue@9.3.0:
17715 dependencies:17931 dependencies:
17716 eventemitter3: 5.0.417932 eventemitter3: 5.0.4
...@@ -17718,6 +17934,8 @@ snapshots:...@@ -17718,6 +17934,8 @@ snapshots:
1771817934
17719 p-timeout@7.0.1: {}17935 p-timeout@7.0.1: {}
1772017936
17937 p-try@2.2.0: {}
17938
17721 package-manager-detector@1.6.0: {}17939 package-manager-detector@1.6.0: {}
1772217940
17723 pako@1.0.11: {}17941 pako@1.0.11: {}
...@@ -17755,6 +17973,8 @@ snapshots:...@@ -17755,6 +17973,8 @@ snapshots:
1775517973
17756 path-browserify@1.0.1: {}17974 path-browserify@1.0.1: {}
1775717975
17976 path-exists@3.0.0: {}
17977
17758 path-expression-matcher@1.5.0: {}17978 path-expression-matcher@1.5.0: {}
1775917979
17760 path-is-absolute@1.0.1: {}17980 path-is-absolute@1.0.1: {}
...@@ -17847,6 +18067,10 @@ snapshots:...@@ -17847,6 +18067,10 @@ snapshots:
1784718067
17848 pkce-challenge@5.0.1: {}18068 pkce-challenge@5.0.1: {}
1784918069
18070 pkg-up@3.1.0:
18071 dependencies:
18072 find-up: 3.0.0
18073
17850 playwright-core@1.60.0: {}18074 playwright-core@1.60.0: {}
1785118075
17852 plist@3.1.0:18076 plist@3.1.0:
...@@ -18061,102 +18285,102 @@ snapshots:...@@ -18061,102 +18285,102 @@ snapshots:
18061 transitivePeerDependencies:18285 transitivePeerDependencies:
18062 - supports-color18286 - 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):
18065 dependencies:18289 dependencies:
18066 color: 4.2.318290 color: 4.2.3
18067 react: 19.2.318291 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)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)
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)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)
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)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)
18071 use-latest-callback: 0.2.6(react@19.2.3)18295 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):
18074 dependencies:18298 dependencies:
18075 '@egjs/hammerjs': 2.0.1718299 '@egjs/hammerjs': 2.0.17
18076 '@types/react-test-renderer': 19.1.018300 '@types/react-test-renderer': 19.1.0
18077 hoist-non-react-statics: 3.3.218301 hoist-non-react-statics: 3.3.2
18078 invariant: 2.2.418302 invariant: 2.2.4
18079 react: 19.2.318303 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):
18083 dependencies:18307 dependencies:
18084 react: 19.2.318308 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):
18088 dependencies:18312 dependencies:
18089 react: 19.2.318313 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):
18093 dependencies:18317 dependencies:
18094 react: 19.2.318318 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)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)
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)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)
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)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):
18100 dependencies:18324 dependencies:
18101 react: 19.2.318325 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)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)
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)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)
18104 optionalDependencies:18328 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):
18108 dependencies:18332 dependencies:
18109 react: 19.2.318333 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):
18113 dependencies:18337 dependencies:
18114 react: 19.2.318338 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)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)
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)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)
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)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)
18118 semver: 7.8.118342 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):
18121 dependencies:18345 dependencies:
18122 react: 19.2.318346 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):
18126 dependencies:18350 dependencies:
18127 react: 19.2.318351 react: 19.2.3
18128 react-freeze: 1.0.4(react@19.2.3)18352 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)
18130 warn-once: 0.1.118354 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):
18133 dependencies:18357 dependencies:
18134 '@shikijs/types': 4.2.018358 '@shikijs/types': 4.2.0
18135 '@shikijs/vscode-textmate': 10.0.218359 '@shikijs/vscode-textmate': 10.0.2
18136 react: 19.2.318360 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):
18140 dependencies:18364 dependencies:
18141 css-select: 5.2.218365 css-select: 5.2.2
18142 css-tree: 1.1.318366 css-tree: 1.1.3
18143 react: 19.2.318367 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)
18145 warn-once: 0.1.118369 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)):
18148 dependencies:18372 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)
18150 whatwg-url-without-unicode: 8.0.0-318374 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):
18153 dependencies:18377 dependencies:
18154 escape-string-regexp: 4.0.018378 escape-string-regexp: 4.0.0
18155 invariant: 2.2.418379 invariant: 2.2.4
18156 react: 19.2.318380 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):
18160 dependencies:18384 dependencies:
18161 '@babel/core': 7.29.718385 '@babel/core': 7.29.7
18162 '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7)18386 '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7)
...@@ -18168,23 +18392,23 @@ snapshots:...@@ -18168,23 +18392,23 @@ snapshots:
18168 '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7)18392 '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7)
18169 '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7)18393 '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7)
18170 '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7)18394 '@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)
18172 convert-source-map: 2.0.018396 convert-source-map: 2.0.0
18173 react: 19.2.318397 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)
18175 semver: 7.8.118399 semver: 7.8.1
18176 transitivePeerDependencies:18400 transitivePeerDependencies:
18177 - supports-color18401 - 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):
18180 dependencies:18404 dependencies:
18181 '@react-native/assets-registry': 0.85.318405 '@react-native/assets-registry': 0.85.3
18182 '@react-native/codegen': 0.85.3(@babel/core@7.29.7)18406 '@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)
18184 '@react-native/gradle-plugin': 0.85.318408 '@react-native/gradle-plugin': 0.85.3
18185 '@react-native/js-polyfills': 0.85.318409 '@react-native/js-polyfills': 0.85.3
18186 '@react-native/normalize-colors': 0.85.318410 '@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)
18188 abort-controller: 3.0.018412 abort-controller: 3.0.0
18189 anser: 1.4.1018413 anser: 1.4.10
18190 ansi-regex: 5.0.118414 ansi-regex: 5.0.1
...@@ -19100,6 +19324,8 @@ snapshots:...@@ -19100,6 +19324,8 @@ snapshots:
1910019324
19101 type-fest@0.7.1: {}19325 type-fest@0.7.1: {}
1910219326
19327 type-fest@2.19.0: {}
19328
19103 type-fest@5.7.0:19329 type-fest@5.7.0:
19104 dependencies:19330 dependencies:
19105 tagged-tag: 1.0.019331 tagged-tag: 1.0.0
...@@ -19211,14 +19437,14 @@ snapshots:...@@ -19211,14 +19437,14 @@ snapshots:
1921119437
19212 universalify@2.0.1: {}19438 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):
19215 dependencies:19441 dependencies:
19216 '@tailwindcss/node': 4.2.119442 '@tailwindcss/node': 4.2.1
19217 '@tailwindcss/oxide': 4.2.119443 '@tailwindcss/oxide': 4.2.1
19218 culori: 4.0.219444 culori: 4.0.2
19219 lightningcss: 1.30.119445 lightningcss: 1.30.1
19220 react: 19.2.319446 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)
19222 tailwindcss: 4.3.019448 tailwindcss: 4.3.0
1922319449
19224 unpipe@1.0.0: {}19450 unpipe@1.0.0: {}
pnpm-workspace.yaml+17
...@@ -6,6 +6,13 @@ packages:...@@ -6,6 +6,13 @@ packages:
6 - scripts6 - scripts
77
8catalog:8catalog:
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
9 "@effect/atom-react": 4.0.0-beta.7816 "@effect/atom-react": 4.0.0-beta.78
10 "@effect/openapi-generator": 4.0.0-beta.7817 "@effect/openapi-generator": 4.0.0-beta.78
11 "@effect/platform-bun": 4.0.0-beta.7818 "@effect/platform-bun": 4.0.0-beta.78
...@@ -40,6 +47,16 @@ onlyBuiltDependencies:...@@ -40,6 +47,16 @@ onlyBuiltDependencies:
40 - sharp47 - sharp
4148
42overrides:49overrides:
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:"
43 "@clerk/clerk-js>@base-org/account": "-"60 "@clerk/clerk-js>@base-org/account": "-"
44 "@clerk/clerk-js>@coinbase/wallet-sdk": "-"61 "@clerk/clerk-js>@coinbase/wallet-sdk": "-"
45 "@clerk/clerk-js>@solana/wallet-adapter-base": "-"62 "@clerk/clerk-js>@solana/wallet-adapter-base": "-"
scripts/build-desktop-artifact.test.ts+104
...@@ -8,7 +8,11 @@ import * as Option from "effect/Option";...@@ -8,7 +8,11 @@ import * as Option from "effect/Option";
8import {8import {
9 createStageWorkspaceConfig,9 createStageWorkspaceConfig,
10 createStagePnpmConfig,10 createStagePnpmConfig,
11 createBuildConfig,
11 DESKTOP_ASAR_UNPACK,12 DESKTOP_ASAR_UNPACK,
13 renderMacPasskeyEntitlements,
14 resolveClerkPasskeyNativeArtifacts,
15 resolveMacPasskeySigningConfiguration,
12 resolveDesktopRuntimeDependencies,16 resolveDesktopRuntimeDependencies,
13 resolveFffNativeDependencies,17 resolveFffNativeDependencies,
14 resolveBuildOptions,18 resolveBuildOptions,
...@@ -175,6 +179,86 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -175,6 +179,86 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
175 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);179 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
176 });180 });
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
178 it("promotes target fff binaries to direct staged dependencies", () => {262 it("promotes target fff binaries to direct staged dependencies", () => {
179 assert.deepStrictEqual(resolveFffNativeDependencies("mac", "arm64", "0.9.4"), {263 assert.deepStrictEqual(resolveFffNativeDependencies("mac", "arm64", "0.9.4"), {
180 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",264 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",
...@@ -192,6 +276,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -192,6 +276,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
192 });276 });
193 });277 });
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
195 it("falls back to the default mock update port when the configured port is blank", () => {299 it("falls back to the default mock update port when the configured port is blank", () => {
196 assert.equal(resolveMockUpdateServerUrl(undefined), "http://localhost:3000");300 assert.equal(resolveMockUpdateServerUrl(undefined), "http://localhost:3000");
197 assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");301 assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");
scripts/build-desktop-artifact.ts+232-3
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1#!/usr/bin/env node1#!/usr/bin/env node
22
3import { createRequire } from "node:module";
4
3import { fromYaml } from "@t3tools/shared/schemaYaml";5import { fromYaml } from "@t3tools/shared/schemaYaml";
4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";6import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
7import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
5import { resolveSpawnCommand } from "@t3tools/shared/shell";8import { resolveSpawnCommand } from "@t3tools/shared/shell";
6import rootPackageJson from "../package.json" with { type: "json" };9import rootPackageJson from "../package.json" with { type: "json" };
7import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };10import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
...@@ -9,6 +12,7 @@ import serverPackageJson from "../apps/server/package.json" with { type: "json"...@@ -9,6 +12,7 @@ import serverPackageJson from "../apps/server/package.json" with { type: "json"
912
10import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";13import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
11import { getDefaultBuildArch } from "./lib/build-target-arch.ts";14import { getDefaultBuildArch } from "./lib/build-target-arch.ts";
15import { loadRepoEnv } from "./lib/public-config.ts";
12import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";16import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
1317
14import * as NodeRuntime from "@effect/platform-node/NodeRuntime";18import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
...@@ -27,6 +31,8 @@ import { Command, Flag } from "effect/unstable/cli";...@@ -27,6 +31,8 @@ import { Command, Flag } from "effect/unstable/cli";
27import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";31import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2832
29const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const;33const 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
31const BuildPlatform = Schema.Literals(["mac", "linux", "win"]);37const BuildPlatform = Schema.Literals(["mac", "linux", "win"]);
32const BuildArch = Schema.Literals(["arm64", "x64", "universal"]);38const BuildArch = Schema.Literals(["arm64", "x64", "universal"]);
...@@ -293,6 +299,121 @@ interface StagePackageJson {...@@ -293,6 +299,121 @@ interface StagePackageJson {
293export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;299export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;
294export const DESKTOP_ASAR_UNPACK = ["node_modules/@ff-labs/fff-bin-*/**/*"] as const;300export 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
296export function resolveFffNativeDependencies(417export function resolveFffNativeDependencies(
297 platform: typeof BuildPlatform.Type,418 platform: typeof BuildPlatform.Type,
298 arch: typeof BuildArch.Type,419 arch: typeof BuildArch.Type,
...@@ -319,6 +440,63 @@ export function resolveFffNativeDependencies(...@@ -319,6 +440,63 @@ export function resolveFffNativeDependencies(
319 );440 );
320}441}
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
322export function createStageWorkspaceConfig(500export function createStageWorkspaceConfig(
323 platform: typeof BuildPlatform.Type,501 platform: typeof BuildPlatform.Type,
324 arch: typeof BuildArch.Type,502 arch: typeof BuildArch.Type,
...@@ -739,16 +917,22 @@ export function resolveDesktopProductName(version: string): string {...@@ -739,16 +917,22 @@ export function resolveDesktopProductName(version: string): string {
739 : (desktopPackageJson.productName ?? "T3 Code");917 : (desktopPackageJson.productName ?? "T3 Code");
740}918}
741919
742const createBuildConfig = Effect.fn("createBuildConfig")(function* (920export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
743 platform: typeof BuildPlatform.Type,921 platform: typeof BuildPlatform.Type,
744 target: string,922 target: string,
745 version: string,923 version: string,
746 signed: boolean,924 signed: boolean,
747 mockUpdates: boolean,925 mockUpdates: boolean,
748 mockUpdateServerPort: number | undefined,926 mockUpdateServerPort: number | undefined,
927 macPasskeySigning:
928 | {
929 readonly entitlementsPath: string;
930 readonly provisioningProfilePath: string;
931 }
932 | undefined,
749) {933) {
750 const buildConfig: Record<string, unknown> = {934 const buildConfig: Record<string, unknown> = {
751 appId: "com.t3tools.t3code",935 appId: DESKTOP_APP_ID,
752 productName: resolveDesktopProductName(version),936 productName: resolveDesktopProductName(version),
753 artifactName: "T3-Code-${version}-${arch}.${ext}",937 artifactName: "T3-Code-${version}-${arch}.${ext}",
754 asarUnpack: [...DESKTOP_ASAR_UNPACK],938 asarUnpack: [...DESKTOP_ASAR_UNPACK],
...@@ -777,9 +961,15 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (...@@ -777,9 +961,15 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (
777 protocols: [961 protocols: [
778 {962 {
779 name: "T3 Code",963 name: "T3 Code",
780 schemes: ["t3code"],964 schemes: ["t3code", "t3code-dev"],
781 },965 },
782 ],966 ],
967 ...(macPasskeySigning
968 ? {
969 entitlements: macPasskeySigning.entitlementsPath,
970 provisioningProfile: macPasskeySigning.provisioningProfilePath,
971 }
972 : {}),
783 };973 };
784 }974 }
785975
...@@ -956,6 +1146,38 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -956,6 +1146,38 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
956 // electron-builder is filtering out stageResourcesDir directory in the AppImage for production1146 // electron-builder is filtering out stageResourcesDir directory in the AppImage for production
957 yield* fs.copy(stageResourcesDir, path.join(stageAppDir, "apps/desktop/prod-resources"));1147 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
959 const stageDependencies = {1181 const stageDependencies = {
960 ...resolvedServerDependencies,1182 ...resolvedServerDependencies,
961 ...resolvedDesktopRuntimeDependencies,1183 ...resolvedDesktopRuntimeDependencies,
...@@ -983,6 +1205,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -983,6 +1205,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
983 options.signed,1205 options.signed,
984 options.mockUpdates,1206 options.mockUpdates,
985 options.mockUpdateServerPort,1207 options.mockUpdateServerPort,
1208 macPasskeySigning && macEntitlementsPath
1209 ? {
1210 entitlementsPath: macEntitlementsPath,
1211 provisioningProfilePath: macPasskeySigning.provisioningProfilePath,
1212 }
1213 : undefined,
986 ),1214 ),
987 dependencies: stageDependencies,1215 dependencies: stageDependencies,
988 devDependencies: {1216 devDependencies: {
...@@ -1014,6 +1242,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -1014,6 +1242,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
1014 }),1242 }),
1015 { label: "vp install --prod", verbose: options.verbose },1243 { label: "vp install --prod", verbose: options.verbose },
1016 );1244 );
1245 yield* stageClerkPasskeyNativeBinaries(stageAppDir, options.platform, options.arch);
10171246
1018 // electron-builder treats several set-but-empty variables (e.g. CSC_LINK="")1247 // electron-builder treats several set-but-empty variables (e.g. CSC_LINK="")
1019 // as enabled, so copy the host env and scrub empty values instead of relying1248 // as enabled, so copy the host env and scrub empty values instead of relying