| author | |
| committer | |
| log | 3e01c4bc572ea6a938c9a47143c914ff042ea41a |
| tree | a2eaaca128a13a7f7583996817daeff6477679bc |
| parent | 20f37f367a146fb4118bd0f0182336f4ab8c6ac8 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>67 files changed, 1986 insertions(+), 3318 deletions(-)
.env.example+7| ... | ... | @@ -8,6 +8,13 @@ |
| 8 | 8 | # T3CODE_CLERK_JWT_TEMPLATE=t3-relay |
| 9 | 9 | # T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauthapp_... |
| 10 | 10 | |
| 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 | 18 | # Get this from your relay deployment. `infra/relay` deploys update it automatically. |
| 12 | 19 | # T3CODE_RELAY_URL=https://relay.example.com |
| 13 | 20 |
.github/workflows/ci.yml+1| ... | ... | @@ -38,6 +38,7 @@ jobs: |
| 38 | 38 | run: | |
| 39 | 39 | test -f apps/desktop/dist-electron/preload.cjs |
| 40 | 40 | 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 | |
| 41 | 42 | |
| 42 | 43 | test: |
| 43 | 44 | name: Test |
.github/workflows/release.yml+15| ... | ... | @@ -425,6 +425,9 @@ jobs: |
| 425 | 425 | APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} |
| 426 | 426 | APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} |
| 427 | 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 | 431 | AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} |
| 429 | 432 | AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} |
| 430 | 433 | AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} |
| ... | ... | @@ -452,9 +455,21 @@ jobs: |
| 452 | 455 | |
| 453 | 456 | if [[ "${{ matrix.platform }}" == "mac" ]]; then |
| 454 | 457 | 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 | 463 | key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" |
| 456 | 464 | printf '%s' "$APPLE_API_KEY" > "$key_path" |
| 457 | 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 | 473 | echo "macOS signing enabled." |
| 459 | 474 | args+=(--signed) |
| 460 | 475 | else |
apps/desktop/package.json+3| ... | ... | @@ -12,6 +12,8 @@ |
| 12 | 12 | "smoke-test": "node scripts/smoke-test.mjs" |
| 13 | 13 | }, |
| 14 | 14 | "dependencies": { |
| 15 | "@clerk/electron": "catalog:", | |
| 16 | "@clerk/electron-passkeys": "catalog:", | |
| 15 | 17 | "@effect/platform-node": "catalog:", |
| 16 | 18 | "@t3tools/client-runtime": "workspace:*", |
| 17 | 19 | "@t3tools/contracts": "workspace:*", |
| ... | ... | @@ -20,6 +22,7 @@ |
| 20 | 22 | "@t3tools/tailscale": "workspace:*", |
| 21 | 23 | "effect": "catalog:", |
| 22 | 24 | "electron": "41.5.0", |
| 25 | "electron-store": "^8.2.0", | |
| 23 | 26 | "electron-updater": "^6.6.2", |
| 24 | 27 | "playwright-core": "1.60.0", |
| 25 | 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 | 31 | ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` |
| 32 | 32 | : "com.t3tools.t3code"; |
| 33 | 33 | const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; |
| 34 | const LAUNCHER_VERSION = 11; | |
| 34 | const LAUNCHER_VERSION = 12; | |
| 35 | 35 | const defaultIconPath = join(desktopDir, "resources", "icon.icns"); |
| 36 | 36 | const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png"); |
| 37 | 37 | // oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime. |
| 38 | 38 | const hostPlatform = NodeOS.platform(); |
| 39 | 39 | |
| 40 | function 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 | ||
| 48 | 40 | function setPlistString(plistPath, key, value) { |
| 49 | 41 | const replaceResult = spawnSync("plutil", ["-replace", key, "-string", value, plistPath], { |
| 50 | 42 | encoding: "utf8", |
| ... | ... | @@ -100,7 +92,6 @@ function shellSingleQuote(value) { |
| 100 | 92 | |
| 101 | 93 | function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) { |
| 102 | 94 | const mainEntryPath = join(desktopDir, "dist-electron", "main.cjs"); |
| 103 | const protocolCallbackUrl = `http://127.0.0.1:${resolveDevelopmentProtocolCallbackPort()}/auth/callback`; | |
| 104 | 95 | const envEntries = [ |
| 105 | 96 | ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL], |
| 106 | 97 | ["T3CODE_PORT", process.env.T3CODE_PORT], |
| ... | ... | @@ -109,23 +100,12 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) { |
| 109 | 100 | ["T3CODE_OTLP_TRACES_URL", process.env.T3CODE_OTLP_TRACES_URL], |
| 110 | 101 | ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS], |
| 111 | 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 | 103 | ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0); |
| 115 | 104 | writeFileSync( |
| 116 | 105 | targetBinaryPath, |
| 117 | 106 | [ |
| 118 | 107 | "#!/bin/sh", |
| 119 | 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 | 109 | `exec ${shellSingleQuote(electronBinaryPath)} --t3code-dev-root=${shellSingleQuote(desktopDir)} ${shellSingleQuote(mainEntryPath)} "$@"`, |
| 130 | 110 | "", |
| 131 | 111 | ].join("\n"), |
apps/desktop/src/app/DesktopApp.ts+13-5| ... | ... | @@ -11,7 +11,7 @@ import * as ElectronDialog from "../electron/ElectronDialog.ts"; |
| 11 | 11 | import * as ElectronProtocol from "../electron/ElectronProtocol.ts"; |
| 12 | 12 | import { installDesktopIpcHandlers } from "../ipc/DesktopIpcHandlers.ts"; |
| 13 | 13 | import * as DesktopAppIdentity from "./DesktopAppIdentity.ts"; |
| 14 | import * as DesktopCloudAuth from "./DesktopCloudAuth.ts"; | |
| 14 | import * as DesktopClerk from "./DesktopClerk.ts"; | |
| 15 | 15 | import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts"; |
| 16 | 16 | import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts"; |
| 17 | 17 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| ... | ... | @@ -163,6 +163,16 @@ const bootstrap = Effect.gen(function* () { |
| 163 | 163 | } |
| 164 | 164 | const serverExposureState = yield* serverExposure.configureFromSettings({ port: backendPort }); |
| 165 | 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 | 176 | yield* logBootstrapInfo("bootstrap resolved backend endpoint", { |
| 167 | 177 | baseUrl: backendConfig.httpBaseUrl.href, |
| 168 | 178 | }); |
| ... | ... | @@ -189,9 +199,8 @@ const startup = Effect.gen(function* () { |
| 189 | 199 | const appIdentity = yield* DesktopAppIdentity.DesktopAppIdentity; |
| 190 | 200 | const applicationMenu = yield* DesktopApplicationMenu.DesktopApplicationMenu; |
| 191 | 201 | const electronApp = yield* ElectronApp.ElectronApp; |
| 192 | const electronProtocol = yield* ElectronProtocol.ElectronProtocol; | |
| 193 | 202 | const lifecycle = yield* DesktopLifecycle.DesktopLifecycle; |
| 194 | const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth; | |
| 203 | const clerk = yield* DesktopClerk.DesktopClerk; | |
| 195 | 204 | const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment; |
| 196 | 205 | const desktopSettings = yield* DesktopAppSettings.DesktopAppSettings; |
| 197 | 206 | const updates = yield* DesktopUpdates.DesktopUpdates; |
| ... | ... | @@ -209,7 +218,7 @@ const startup = Effect.gen(function* () { |
| 209 | 218 | |
| 210 | 219 | yield* appIdentity.configure; |
| 211 | 220 | yield* lifecycle.register; |
| 212 | yield* cloudAuth.configure; | |
| 221 | yield* clerk.configure; | |
| 213 | 222 | |
| 214 | 223 | yield* electronApp.whenReady.pipe( |
| 215 | 224 | Effect.withSpan("desktop.electron.whenReady"), |
| ... | ... | @@ -218,7 +227,6 @@ const startup = Effect.gen(function* () { |
| 218 | 227 | yield* logStartupInfo("app ready"); |
| 219 | 228 | yield* appIdentity.configure; |
| 220 | 229 | yield* applicationMenu.configure; |
| 221 | yield* electronProtocol.registerDesktopFileProtocol; | |
| 222 | 230 | yield* updates.configure; |
| 223 | 231 | yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause))); |
| 224 | 232 | }).pipe(Effect.withSpan("desktop.startup")); |
apps/desktop/src/app/DesktopClerk.test.ts created+95| ... | ... | @@ -0,0 +1,95 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | |
| 2 | import * as Effect from "effect/Effect"; | |
| 3 | import * as Layer from "effect/Layer"; | |
| 4 | import { vi } from "vite-plus/test"; | |
| 5 | ||
| 6 | const { 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 | ||
| 16 | vi.mock("@clerk/electron", () => ({ | |
| 17 | createClerkBridge: createClerkBridgeMock, | |
| 18 | })); | |
| 19 | ||
| 20 | vi.mock("@clerk/electron/storage", () => ({ | |
| 21 | storage: storageMock, | |
| 22 | })); | |
| 23 | ||
| 24 | import { | |
| 25 | createDesktopClerkBridge, | |
| 26 | resolveDesktopClerkFrontendApiHostname, | |
| 27 | } from "./DesktopClerk.ts"; | |
| 28 | import * as DesktopClerk from "./DesktopClerk.ts"; | |
| 29 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 30 | ||
| 31 | describe("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 @@ |
| 1 | import { createClerkBridge } from "@clerk/electron"; | |
| 2 | import { storage } from "@clerk/electron/storage"; | |
| 3 | import * as Context from "effect/Context"; | |
| 4 | import * as Effect from "effect/Effect"; | |
| 5 | import * as Layer from "effect/Layer"; | |
| 6 | import * as Option from "effect/Option"; | |
| 7 | import * as Scope from "effect/Scope"; | |
| 8 | ||
| 9 | import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth"; | |
| 10 | import * as ElectronApp from "../electron/ElectronApp.ts"; | |
| 11 | import * as ElectronProtocol from "../electron/ElectronProtocol.ts"; | |
| 12 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | |
| 13 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 14 | ||
| 15 | declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined; | |
| 16 | ||
| 17 | export interface DesktopClerkShape { | |
| 18 | readonly configure: Effect.Effect< | |
| 19 | void, | |
| 20 | never, | |
| 21 | ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope | |
| 22 | >; | |
| 23 | } | |
| 24 | ||
| 25 | export class DesktopClerk extends Context.Service<DesktopClerk, DesktopClerkShape>()( | |
| 26 | "@t3tools/desktop/app/DesktopClerk", | |
| 27 | ) {} | |
| 28 | ||
| 29 | export 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 | ||
| 42 | export const desktopClerkFrontendApiHostname = resolveDesktopClerkFrontendApiHostname( | |
| 43 | typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined" | |
| 44 | ? undefined | |
| 45 | : __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__, | |
| 46 | ); | |
| 47 | ||
| 48 | export 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 | ||
| 59 | const 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 | ||
| 92 | export const layer = Layer.effect(DesktopClerk, make); |
apps/desktop/src/app/DesktopCloudAuth.test.ts deleted-302| ... | ... | @@ -1,302 +0,0 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 3 | import * as Effect from "effect/Effect"; | |
| 4 | import * as Layer from "effect/Layer"; | |
| 5 | import * as Option from "effect/Option"; | |
| 6 | ||
| 7 | import * as ElectronApp from "../electron/ElectronApp.ts"; | |
| 8 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | |
| 9 | import * as IpcChannels from "../ipc/channels.ts"; | |
| 10 | import * as DesktopCloudAuth from "./DesktopCloudAuth.ts"; | |
| 11 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 12 | ||
| 13 | interface 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 | ||
| 32 | function 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 | ||
| 119 | function 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 | ||
| 129 | const flushCloudAuthDispatch = Effect.promise(() => Promise.resolve()); | |
| 130 | ||
| 131 | describe("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 @@ |
| 1 | import * as Context from "effect/Context"; | |
| 2 | import * as Crypto from "effect/Crypto"; | |
| 3 | import * as Data from "effect/Data"; | |
| 4 | import * as Effect from "effect/Effect"; | |
| 5 | import * as Layer from "effect/Layer"; | |
| 6 | import * as Option from "effect/Option"; | |
| 7 | import * as Scope from "effect/Scope"; | |
| 8 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | |
| 9 | ||
| 10 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | |
| 11 | import * as ElectronApp from "../electron/ElectronApp.ts"; | |
| 12 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | |
| 13 | import * as IpcChannels from "../ipc/channels.ts"; | |
| 14 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 15 | import type * as Electron from "electron"; | |
| 16 | ||
| 17 | export const CLOUD_AUTH_CALLBACK_HOST = "auth"; | |
| 18 | export const CLOUD_AUTH_CALLBACK_PATHNAME = "/callback"; | |
| 19 | export const CLOUD_AUTH_CALLBACK_STATE_PARAM = "t3_state"; | |
| 20 | export const CLOUD_AUTH_CALLBACK_SCHEME = "t3code"; | |
| 21 | export const DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME = "t3code-dev"; | |
| 22 | ||
| 23 | const CLOUD_AUTH_REQUEST_TIMEOUT_MS = 5 * 60 * 1000; | |
| 24 | ||
| 25 | export 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 | ||
| 35 | interface PendingCloudAuthRequest { | |
| 36 | readonly state: string; | |
| 37 | readonly redirectUrl: string; | |
| 38 | readonly close: () => void; | |
| 39 | } | |
| 40 | ||
| 41 | export 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 | ||
| 50 | export class DesktopCloudAuth extends Context.Service<DesktopCloudAuth, DesktopCloudAuthShape>()( | |
| 51 | "@t3tools/desktop/app/DesktopCloudAuth", | |
| 52 | ) {} | |
| 53 | ||
| 54 | export function resolveCloudAuthCallbackScheme(input: { readonly isDevelopment: boolean }): string { | |
| 55 | return input.isDevelopment ? DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME : CLOUD_AUTH_CALLBACK_SCHEME; | |
| 56 | } | |
| 57 | ||
| 58 | export 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 | ||
| 69 | export 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 | ||
| 90 | export 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 | ||
| 106 | export function resolveProtocolClientLaunchArgs(input: { | |
| 107 | readonly argv: readonly string[]; | |
| 108 | }): readonly string[] { | |
| 109 | return input.argv.slice(1); | |
| 110 | } | |
| 111 | ||
| 112 | function 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 | ||
| 128 | function isProtocolRegistrationManagedExternally(): boolean { | |
| 129 | return process.env.T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED?.trim() === "1"; | |
| 130 | } | |
| 131 | ||
| 132 | function 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 | ||
| 148 | const closeCloudAuthRequest = (request: PendingCloudAuthRequest | null): null => { | |
| 149 | request?.close(); | |
| 150 | return null; | |
| 151 | }; | |
| 152 | ||
| 153 | function 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 | ||
| 158 | function ignoreCloudAuthCallback(_rawUrl: string) {} | |
| 159 | ||
| 160 | function 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 | ||
| 190 | const 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 | ||
| 330 | export const layer = Layer.effect(DesktopCloudAuth, make); |
apps/desktop/src/app/DesktopCloudAuthTokenStore.test.ts deleted-96| ... | ... | @@ -1,96 +0,0 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 2 | import { assert, describe, it } from "@effect/vitest"; | |
| 3 | import * as Effect from "effect/Effect"; | |
| 4 | import * as FileSystem from "effect/FileSystem"; | |
| 5 | import * as Layer from "effect/Layer"; | |
| 6 | import * as Option from "effect/Option"; | |
| 7 | ||
| 8 | import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts"; | |
| 9 | import * as DesktopConfig from "./DesktopConfig.ts"; | |
| 10 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 11 | import * as DesktopCloudAuthTokenStore from "./DesktopCloudAuthTokenStore.ts"; | |
| 12 | ||
| 13 | const textDecoder = new TextDecoder(); | |
| 14 | const textEncoder = new TextEncoder(); | |
| 15 | ||
| 16 | function 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 | ||
| 34 | function 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 | ||
| 58 | const 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 | ||
| 70 | describe("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 @@ |
| 1 | import { fromLenientJson } from "@t3tools/shared/schemaJson"; | |
| 2 | import * as Context from "effect/Context"; | |
| 3 | import * as Crypto from "effect/Crypto"; | |
| 4 | import * as Data from "effect/Data"; | |
| 5 | import * as Effect from "effect/Effect"; | |
| 6 | import * as Encoding from "effect/Encoding"; | |
| 7 | import * as FileSystem from "effect/FileSystem"; | |
| 8 | import * as Layer from "effect/Layer"; | |
| 9 | import * as Option from "effect/Option"; | |
| 10 | import * as Path from "effect/Path"; | |
| 11 | import * as PlatformError from "effect/PlatformError"; | |
| 12 | import * as Schema from "effect/Schema"; | |
| 13 | ||
| 14 | import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts"; | |
| 15 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | |
| 16 | ||
| 17 | interface CloudAuthTokenDocument { | |
| 18 | readonly version: number; | |
| 19 | readonly encryptedClientJwt: string; | |
| 20 | } | |
| 21 | ||
| 22 | const CloudAuthTokenDocumentSchema = Schema.Struct({ | |
| 23 | version: Schema.Number, | |
| 24 | encryptedClientJwt: Schema.String, | |
| 25 | }); | |
| 26 | ||
| 27 | const CloudAuthTokenDocumentJson = fromLenientJson(CloudAuthTokenDocumentSchema); | |
| 28 | const decodeCloudAuthTokenDocumentJson = Schema.decodeEffect(CloudAuthTokenDocumentJson); | |
| 29 | const encodeCloudAuthTokenDocumentJson = Schema.encodeEffect(CloudAuthTokenDocumentJson); | |
| 30 | ||
| 31 | export 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 | ||
| 41 | export 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 | ||
| 51 | export 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 | ||
| 69 | export class DesktopCloudAuthTokenStore extends Context.Service< | |
| 70 | DesktopCloudAuthTokenStore, | |
| 71 | DesktopCloudAuthTokenStoreShape | |
| 72 | >()("@t3tools/desktop/app/DesktopCloudAuthTokenStore") {} | |
| 73 | ||
| 74 | function 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 | ||
| 82 | const 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 | ||
| 96 | const 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 | ||
| 111 | export 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 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | |
| 2 | import * as Effect from "effect/Effect"; | |
| 3 | import * as Layer from "effect/Layer"; | |
| 4 | import * as Option from "effect/Option"; | |
| 5 | import * as Ref from "effect/Ref"; | |
| 6 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; | |
| 7 | ||
| 8 | import * as DesktopBackendManager from "./DesktopBackendManager.ts"; | |
| 9 | import * as DesktopLocalEnvironmentAuth from "./DesktopLocalEnvironmentAuth.ts"; | |
| 10 | ||
| 11 | const 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 | ||
| 30 | describe("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 @@ |
| 1 | import { bootstrapRemoteBearerSession } from "@t3tools/client-runtime/authorization"; | |
| 2 | import * as Context from "effect/Context"; | |
| 3 | import * as Data from "effect/Data"; | |
| 4 | import * as Effect from "effect/Effect"; | |
| 5 | import * as Layer from "effect/Layer"; | |
| 6 | import * as Option from "effect/Option"; | |
| 7 | import * as Ref from "effect/Ref"; | |
| 8 | import * as Semaphore from "effect/Semaphore"; | |
| 9 | import { HttpClient } from "effect/unstable/http"; | |
| 10 | ||
| 11 | import * as DesktopBackendManager from "./DesktopBackendManager.ts"; | |
| 12 | ||
| 13 | export interface DesktopLocalEnvironmentAuthShape { | |
| 14 | readonly getBearerToken: Effect.Effect<string, DesktopLocalEnvironmentAuthError>; | |
| 15 | } | |
| 16 | ||
| 17 | export class DesktopLocalEnvironmentAuthError extends Data.TaggedError( | |
| 18 | "DesktopLocalEnvironmentAuthError", | |
| 19 | )<{ | |
| 20 | readonly message: string; | |
| 21 | readonly cause?: unknown; | |
| 22 | }> {} | |
| 23 | ||
| 24 | export class DesktopLocalEnvironmentAuth extends Context.Service< | |
| 25 | DesktopLocalEnvironmentAuth, | |
| 26 | DesktopLocalEnvironmentAuthShape | |
| 27 | >()("@t3tools/desktop/backend/DesktopLocalEnvironmentAuth") {} | |
| 28 | ||
| 29 | export 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 | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | |
| 4 | import * as Option from "effect/Option"; | |
| 5 | import type * as Electron from "electron"; | |
| 6 | 3 | import { beforeEach, vi } from "vite-plus/test"; |
| 7 | 4 | |
| 8 | const { registerFileProtocolMock, registerSchemesAsPrivilegedMock, unregisterProtocolMock } = | |
| 9 | vi.hoisted(() => ({ | |
| 10 | registerFileProtocolMock: vi.fn(), | |
| 11 | registerSchemesAsPrivilegedMock: vi.fn(), | |
| 12 | unregisterProtocolMock: vi.fn(), | |
| 13 | })); | |
| 5 | const { handleMock, netFetchMock, unhandleMock } = vi.hoisted(() => ({ | |
| 6 | handleMock: vi.fn(), | |
| 7 | netFetchMock: vi.fn(), | |
| 8 | unhandleMock: vi.fn(), | |
| 9 | })); | |
| 14 | 10 | |
| 15 | 11 | vi.mock("electron", () => ({ |
| 16 | protocol: { | |
| 17 | registerFileProtocol: registerFileProtocolMock, | |
| 18 | registerSchemesAsPrivileged: registerSchemesAsPrivilegedMock, | |
| 19 | unregisterProtocol: unregisterProtocolMock, | |
| 20 | }, | |
| 12 | net: { fetch: netFetchMock }, | |
| 13 | protocol: { handle: handleMock, unhandle: unhandleMock }, | |
| 21 | 14 | })); |
| 22 | 15 | |
| 23 | 16 | import * as ElectronProtocol from "./ElectronProtocol.ts"; |
| 24 | 17 | |
| 25 | 18 | describe("ElectronProtocol", () => { |
| 26 | 19 | beforeEach(() => { |
| 27 | registerFileProtocolMock.mockReset(); | |
| 28 | registerSchemesAsPrivilegedMock.mockReset(); | |
| 29 | unregisterProtocolMock.mockReset(); | |
| 20 | handleMock.mockReset(); | |
| 21 | netFetchMock.mockReset(); | |
| 22 | unhandleMock.mockReset(); | |
| 30 | 23 | }); |
| 31 | 24 | |
| 32 | it("normalizes safe desktop protocol pathnames", () => { | |
| 33 | assert.equal( | |
| 34 | Option.getOrNull(ElectronProtocol.normalizeDesktopProtocolPathname("/settings/./general")), | |
| 35 | "settings/general", | |
| 36 | ); | |
| 37 | assert.isTrue(Option.isNone(ElectronProtocol.normalizeDesktopProtocolPathname("/../secret"))); | |
| 38 | }); | |
| 25 | it.effect("proxies the stable renderer origin to the current app server", () => | |
| 26 | Effect.gen(function* () { | |
| 27 | let handler: ((request: Request) => Promise<Response>) | undefined; | |
| 28 | handleMock.mockImplementation((_scheme, nextHandler) => { | |
| 29 | handler = nextHandler; | |
| 30 | }); | |
| 31 | netFetchMock.mockResolvedValue(new Response("ok")); | |
| 32 | ||
| 33 | yield* Effect.scoped( | |
| 34 | Effect.gen(function* () { | |
| 35 | const protocol = yield* ElectronProtocol.ElectronProtocol; | |
| 36 | yield* protocol.registerDesktopProtocol({ | |
| 37 | scheme: "t3code-dev", | |
| 38 | targetOrigin: new URL("http://127.0.0.1:3773/"), | |
| 39 | backendOrigin: new URL("http://127.0.0.1:3774/"), | |
| 40 | clerkFrontendApiHostname: "clerk.t3.codes", | |
| 41 | }); | |
| 42 | assert.isDefined(handler); | |
| 43 | ||
| 44 | const response = yield* Effect.promise(() => | |
| 45 | handler!(new Request("t3code-dev://app/api/health?verbose=1")), | |
| 46 | ); | |
| 47 | assert.equal(yield* Effect.promise(() => response.text()), "ok"); | |
| 48 | assert.include( | |
| 49 | response.headers.get("content-security-policy") ?? "", | |
| 50 | "script-src 'self' 'unsafe-inline' https://clerk.t3.codes https://challenges.cloudflare.com", | |
| 51 | ); | |
| 52 | assert.include( | |
| 53 | response.headers.get("content-security-policy") ?? "", | |
| 54 | "connect-src 'self' http: https: ws: wss:", | |
| 55 | ); | |
| 56 | assert.include( | |
| 57 | response.headers.get("content-security-policy") ?? "", | |
| 58 | "img-src 'self' t3code-dev: blob: data: http: https:", | |
| 59 | ); | |
| 60 | assert.include( | |
| 61 | response.headers.get("content-security-policy") ?? "", | |
| 62 | "font-src 'self' t3code-dev: data:", | |
| 63 | ); | |
| 64 | }), | |
| 65 | ); | |
| 39 | 66 | |
| 40 | it.effect("registers desktop scheme privileges through a layer", () => | |
| 41 | Effect.scoped( | |
| 42 | Layer.build(ElectronProtocol.layerSchemePrivileges).pipe( | |
| 43 | Effect.andThen( | |
| 44 | Effect.sync(() => { | |
| 45 | assert.deepEqual(registerSchemesAsPrivilegedMock.mock.calls, [ | |
| 46 | [ | |
| 47 | [ | |
| 48 | { | |
| 49 | scheme: "t3", | |
| 50 | privileges: { | |
| 51 | standard: true, | |
| 52 | secure: true, | |
| 53 | supportFetchAPI: true, | |
| 54 | corsEnabled: true, | |
| 55 | }, | |
| 56 | }, | |
| 57 | ], | |
| 58 | ], | |
| 59 | ]); | |
| 60 | }), | |
| 61 | ), | |
| 62 | ), | |
| 63 | ), | |
| 67 | assert.deepEqual( | |
| 68 | handleMock.mock.calls.map((call) => call[0]), | |
| 69 | ["t3code-dev"], | |
| 70 | ); | |
| 71 | assert.equal(netFetchMock.mock.calls[0]?.[0], "http://127.0.0.1:3773/api/health?verbose=1"); | |
| 72 | assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]); | |
| 73 | }).pipe(Effect.provide(ElectronProtocol.layer)), | |
| 64 | 74 | ); |
| 65 | 75 | |
| 66 | it.effect("scopes registered file protocols", () => | |
| 76 | it.effect("rejects custom protocol requests for another host", () => | |
| 67 | 77 | Effect.gen(function* () { |
| 68 | let capturedHandler: | |
| 69 | | (( | |
| 70 | request: Electron.ProtocolRequest, | |
| 71 | callback: (response: Electron.ProtocolResponse) => void, | |
| 72 | ) => void) | |
| 73 | | undefined; | |
| 74 | ||
| 75 | registerFileProtocolMock.mockImplementation((_scheme, handler) => { | |
| 76 | capturedHandler = handler; | |
| 77 | return true; | |
| 78 | let handler: ((request: Request) => Promise<Response>) | undefined; | |
| 79 | handleMock.mockImplementation((_scheme, nextHandler) => { | |
| 80 | handler = nextHandler; | |
| 78 | 81 | }); |
| 79 | 82 | |
| 80 | 83 | const response = yield* Effect.scoped( |
| 81 | 84 | Effect.gen(function* () { |
| 82 | const electronProtocol = yield* ElectronProtocol.ElectronProtocol; | |
| 83 | yield* electronProtocol.registerFileProtocol({ | |
| 84 | scheme: "t3", | |
| 85 | handler: () => Effect.succeed({ path: "/app/index.html" }), | |
| 86 | }); | |
| 87 | ||
| 88 | assert.isDefined(capturedHandler); | |
| 89 | return yield* Effect.callback<Electron.ProtocolResponse>((resume) => { | |
| 90 | capturedHandler?.({ url: "t3://app/" } as Electron.ProtocolRequest, (response) => | |
| 91 | resume(Effect.succeed(response)), | |
| 92 | ); | |
| 85 | const protocol = yield* ElectronProtocol.ElectronProtocol; | |
| 86 | yield* protocol.registerDesktopProtocol({ | |
| 87 | scheme: "t3code", | |
| 88 | targetOrigin: new URL("http://127.0.0.1:3773/"), | |
| 89 | backendOrigin: new URL("http://127.0.0.1:3773/"), | |
| 90 | clerkFrontendApiHostname: undefined, | |
| 93 | 91 | }); |
| 92 | return yield* Effect.promise(() => handler!(new Request("t3code://other/"))); | |
| 94 | 93 | }), |
| 95 | 94 | ); |
| 96 | 95 | |
| 97 | assert.deepEqual(response, { path: "/app/index.html" }); | |
| 98 | assert.deepEqual( | |
| 99 | registerFileProtocolMock.mock.calls.map((call) => call[0]), | |
| 100 | ["t3"], | |
| 101 | ); | |
| 102 | assert.deepEqual(unregisterProtocolMock.mock.calls, [["t3"]]); | |
| 96 | assert.equal(response.status, 404); | |
| 97 | assert.equal(netFetchMock.mock.calls.length, 0); | |
| 103 | 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 | import * as Cause from "effect/Cause"; | |
| 2 | 1 | import * as Context from "effect/Context"; |
| 3 | 2 | import * as Data from "effect/Data"; |
| 4 | 3 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | |
| 6 | 4 | import * as Layer from "effect/Layer"; |
| 7 | import * as Option from "effect/Option"; | |
| 8 | 5 | import * as Ref from "effect/Ref"; |
| 9 | 6 | import * as Scope from "effect/Scope"; |
| 10 | 7 | |
| 11 | 8 | import * as Electron from "electron"; |
| 12 | 9 | |
| 13 | import { DesktopEnvironment, type DesktopEnvironmentShape } from "../app/DesktopEnvironment.ts"; | |
| 10 | export const DESKTOP_HOST = "app"; | |
| 11 | export const DESKTOP_PRODUCTION_SCHEME = "t3code"; | |
| 12 | export const DESKTOP_DEVELOPMENT_SCHEME = "t3code-dev"; | |
| 14 | 13 | |
| 15 | export const DESKTOP_SCHEME = "t3"; | |
| 14 | export function getDesktopScheme(isDevelopment: boolean): string { | |
| 15 | return isDevelopment ? DESKTOP_DEVELOPMENT_SCHEME : DESKTOP_PRODUCTION_SCHEME; | |
| 16 | } | |
| 17 | ||
| 18 | export function getDesktopOrigin(isDevelopment: boolean): string { | |
| 19 | return `${getDesktopScheme(isDevelopment)}://${DESKTOP_HOST}`; | |
| 20 | } | |
| 21 | ||
| 22 | export function getDesktopUrl(isDevelopment: boolean): string { | |
| 23 | return `${getDesktopOrigin(isDevelopment)}/`; | |
| 24 | } | |
| 16 | 25 | |
| 17 | 26 | export class ElectronProtocolRegistrationError extends Data.TaggedError( |
| 18 | 27 | "ElectronProtocolRegistrationError", |
| ... | ... | @@ -21,252 +30,117 @@ export class ElectronProtocolRegistrationError extends Data.TaggedError( |
| 21 | 30 | readonly cause: unknown; |
| 22 | 31 | }> { |
| 23 | 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 | } |
| 27 | 36 | |
| 28 | export class ElectronProtocolStaticBundleMissingError extends Data.TaggedError( | |
| 29 | "ElectronProtocolStaticBundleMissingError", | |
| 30 | )<{}> { | |
| 31 | override get message() { | |
| 32 | return "Desktop static bundle missing. Build apps/server (with bundled client) first."; | |
| 33 | } | |
| 37 | export interface DesktopProtocolRegistrationInput { | |
| 38 | readonly scheme: string; | |
| 39 | readonly targetOrigin: URL; | |
| 40 | readonly backendOrigin: URL; | |
| 41 | readonly clerkFrontendApiHostname: string | undefined; | |
| 34 | 42 | } |
| 35 | 43 | |
| 36 | 44 | export interface ElectronProtocolShape { |
| 37 | readonly registerFileProtocol: <E, R>(input: { | |
| 38 | readonly scheme: string; | |
| 39 | readonly handler: ( | |
| 40 | request: Electron.ProtocolRequest, | |
| 41 | ) => Effect.Effect<Electron.ProtocolResponse, E, R>; | |
| 42 | readonly onFailure?: ( | |
| 43 | request: Electron.ProtocolRequest, | |
| 44 | cause: Cause.Cause<E>, | |
| 45 | ) => Electron.ProtocolResponse; | |
| 46 | }) => Effect.Effect<void, ElectronProtocolRegistrationError, R | Scope.Scope>; | |
| 47 | readonly registerDesktopFileProtocol: Effect.Effect< | |
| 48 | void, | |
| 49 | ElectronProtocolRegistrationError | ElectronProtocolStaticBundleMissingError, | |
| 50 | FileSystem.FileSystem | DesktopEnvironment | Scope.Scope | |
| 51 | >; | |
| 45 | readonly registerDesktopProtocol: ( | |
| 46 | input: DesktopProtocolRegistrationInput, | |
| 47 | ) => Effect.Effect<void, ElectronProtocolRegistrationError, Scope.Scope>; | |
| 52 | 48 | } |
| 53 | 49 | |
| 54 | 50 | export class ElectronProtocol extends Context.Service<ElectronProtocol, ElectronProtocolShape>()( |
| 55 | 51 | "@t3tools/desktop/electron/ElectronProtocol", |
| 56 | 52 | ) {} |
| 57 | 53 | |
| 58 | export function normalizeDesktopProtocolPathname(rawPath: string): Option.Option<string> { | |
| 59 | const segments: string[] = []; | |
| 60 | for (const segment of rawPath.split("/")) { | |
| 61 | if (segment.length === 0 || segment === ".") { | |
| 62 | continue; | |
| 63 | } | |
| 64 | if (segment === "..") { | |
| 65 | return Option.none(); | |
| 66 | } | |
| 67 | segments.push(segment); | |
| 68 | } | |
| 69 | return Option.some(segments.join("/")); | |
| 70 | } | |
| 71 | ||
| 72 | const 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 | ||
| 86 | export const layerSchemePrivileges = Layer.effectDiscard(registerDesktopSchemePrivileges); | |
| 87 | ||
| 88 | const 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"), | |
| 54 | export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrationInput): string { | |
| 55 | const clerkOrigin = input.clerkFrontendApiHostname | |
| 56 | ? `https://${input.clerkFrontendApiHostname}` | |
| 57 | : undefined; | |
| 58 | const scriptSources = [ | |
| 59 | "'self'", | |
| 60 | "'unsafe-inline'", | |
| 61 | ...(clerkOrigin ? [clerkOrigin] : []), | |
| 62 | "https://challenges.cloudflare.com", | |
| 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 | }); | |
| 109 | 64 | |
| 110 | const resolveDesktopStaticPath = Effect.fn("desktop.electron.protocol.resolveDesktopStaticPath")( | |
| 111 | function* ( | |
| 112 | staticRoot: string, | |
| 113 | requestUrl: string, | |
| 114 | ): Effect.fn.Return<string, never, FileSystem.FileSystem | DesktopEnvironment> { | |
| 115 | const fileSystem = yield* FileSystem.FileSystem; | |
| 116 | const environment = yield* DesktopEnvironment; | |
| 117 | const url = new URL(requestUrl); | |
| 118 | const rawPath = decodeURIComponent(url.pathname); | |
| 119 | const normalizedPath = normalizeDesktopProtocolPathname(rawPath); | |
| 120 | if (Option.isNone(normalizedPath)) { | |
| 121 | return environment.path.join(staticRoot, "index.html"); | |
| 122 | } | |
| 123 | ||
| 124 | const requestedPath = normalizedPath.value.length > 0 ? normalizedPath.value : "index.html"; | |
| 125 | const resolvedPath = environment.path.join(staticRoot, requestedPath); | |
| 126 | ||
| 127 | if (environment.path.extname(resolvedPath)) { | |
| 128 | return resolvedPath; | |
| 129 | } | |
| 65 | // The renderer connects directly to user-configured environments in addition to | |
| 66 | // the build-configured Clerk, relay, and OTLP endpoints. Those environment | |
| 67 | // origins are not known when this response policy is created, so restrict | |
| 68 | // connections by the network schemes the client supports instead of by host. | |
| 69 | const connectSources = ["'self'", "http:", "https:", "ws:", "wss:"]; | |
| 70 | ||
| 71 | return [ | |
| 72 | "default-src 'self'", | |
| 73 | `script-src ${scriptSources.join(" ")}`, | |
| 74 | `connect-src ${connectSources.join(" ")}`, | |
| 75 | `img-src 'self' ${input.scheme}: blob: data: http: https:`, | |
| 76 | "style-src 'self' 'unsafe-inline'", | |
| 77 | `font-src 'self' ${input.scheme}: data:`, | |
| 78 | "worker-src 'self' blob:", | |
| 79 | "frame-src 'self' https://challenges.cloudflare.com", | |
| 80 | "form-action 'self'", | |
| 81 | ].join("; "); | |
| 82 | } | |
| 130 | 83 | |
| 131 | const nestedIndex = environment.path.join(resolvedPath, "index.html"); | |
| 132 | const nestedIndexExists = yield* fileSystem | |
| 133 | .exists(nestedIndex) | |
| 134 | .pipe(Effect.orElseSucceed(() => false)); | |
| 135 | if (nestedIndexExists) { | |
| 136 | return nestedIndex; | |
| 137 | } | |
| 84 | function withContentSecurityPolicy(response: Response, policy: string): Response { | |
| 85 | const headers = new Headers(response.headers); | |
| 86 | headers.set("Content-Security-Policy", policy); | |
| 87 | return new Response(response.body, { | |
| 88 | status: response.status, | |
| 89 | statusText: response.statusText, | |
| 90 | headers, | |
| 91 | }); | |
| 92 | } | |
| 138 | 93 | |
| 139 | return environment.path.join(staticRoot, "index.html"); | |
| 140 | }, | |
| 141 | ); | |
| 94 | async function proxyRequest( | |
| 95 | request: Request, | |
| 96 | targetOrigin: URL, | |
| 97 | contentSecurityPolicy: string, | |
| 98 | ): Promise<Response> { | |
| 99 | const requestUrl = new URL(request.url); | |
| 100 | if (requestUrl.host !== DESKTOP_HOST) { | |
| 101 | return new Response(null, { status: 404 }); | |
| 102 | } | |
| 142 | 103 | |
| 143 | function isStaticAssetRequest(requestUrl: string, environment: DesktopEnvironmentShape): boolean { | |
| 144 | try { | |
| 145 | const url = new URL(requestUrl); | |
| 146 | return environment.path.extname(url.pathname).length > 0; | |
| 147 | } catch { | |
| 148 | return false; | |
| 104 | const targetUrl = new URL(`${requestUrl.pathname}${requestUrl.search}`, targetOrigin); | |
| 105 | const init: RequestInit = { | |
| 106 | method: request.method, | |
| 107 | headers: request.headers, | |
| 108 | }; | |
| 109 | if (request.method !== "GET" && request.method !== "HEAD") { | |
| 110 | init.body = request.body; | |
| 111 | (init as RequestInit & { duplex: "half" }).duplex = "half"; | |
| 149 | 112 | } |
| 113 | const response = await Electron.net.fetch(targetUrl.toString(), init); | |
| 114 | return withContentSecurityPolicy(response, contentSecurityPolicy); | |
| 150 | 115 | } |
| 151 | 116 | |
| 152 | 117 | const make = Effect.gen(function* () { |
| 153 | const registeredProtocols = yield* Ref.make<ReadonlySet<string>>(new Set()); | |
| 118 | const registered = yield* Ref.make(false); | |
| 154 | 119 | |
| 155 | const registerFileProtocol = Effect.fn("desktop.electron.protocol.registerFileProtocol")( | |
| 156 | function* <E, R>({ | |
| 157 | scheme, | |
| 158 | handler, | |
| 159 | onFailure, | |
| 160 | }: { | |
| 161 | readonly scheme: string; | |
| 162 | readonly handler: ( | |
| 163 | request: Electron.ProtocolRequest, | |
| 164 | ) => Effect.Effect<Electron.ProtocolResponse, E, R>; | |
| 165 | readonly onFailure?: ( | |
| 166 | request: Electron.ProtocolRequest, | |
| 167 | cause: Cause.Cause<E>, | |
| 168 | ) => Electron.ProtocolResponse; | |
| 169 | }): Effect.fn.Return<void, ElectronProtocolRegistrationError, R | Scope.Scope> { | |
| 170 | yield* Effect.annotateCurrentSpan({ scheme }); | |
| 171 | const alreadyRegistered = yield* Ref.get(registeredProtocols).pipe( | |
| 172 | Effect.map((protocols) => protocols.has(scheme)), | |
| 173 | ); | |
| 174 | if (alreadyRegistered) { | |
| 175 | return; | |
| 176 | } | |
| 120 | const registerDesktopProtocol = Effect.fn("desktop.electron.protocol.registerDesktopProtocol")( | |
| 121 | function* (input: DesktopProtocolRegistrationInput) { | |
| 122 | if (yield* Ref.get(registered)) return; | |
| 177 | 123 | |
| 178 | const context = yield* Effect.context<R>(); | |
| 179 | const runPromise = Effect.runPromiseWith(context); | |
| 124 | const contentSecurityPolicy = makeDesktopContentSecurityPolicy(input); | |
| 180 | 125 | |
| 181 | 126 | yield* Effect.acquireRelease( |
| 182 | 127 | Effect.try({ |
| 183 | 128 | try: () => { |
| 184 | const registered = Electron.protocol.registerFileProtocol( | |
| 185 | scheme, | |
| 186 | (request, callback) => { | |
| 187 | const response = handler(request).pipe( | |
| 188 | Effect.withSpan("desktop.electron.protocol.handleFileRequest"), | |
| 189 | Effect.catchCause((cause) => | |
| 190 | Effect.succeed(onFailure?.(request, cause) ?? ({ error: -2 } as const)), | |
| 191 | ), | |
| 192 | ); | |
| 193 | ||
| 194 | void runPromise(response).then(callback, () => callback({ error: -2 })); | |
| 195 | }, | |
| 129 | Electron.protocol.handle(input.scheme, (request) => | |
| 130 | proxyRequest(request, input.targetOrigin, contentSecurityPolicy), | |
| 196 | 131 | ); |
| 197 | if (!registered) { | |
| 198 | throw new ElectronProtocolRegistrationError({ | |
| 199 | scheme, | |
| 200 | cause: "registerFileProtocol returned false", | |
| 201 | }); | |
| 202 | } | |
| 203 | 132 | }, |
| 204 | catch: (cause) => | |
| 205 | cause instanceof ElectronProtocolRegistrationError | |
| 206 | ? cause | |
| 207 | : new ElectronProtocolRegistrationError({ scheme, cause }), | |
| 208 | }).pipe( | |
| 209 | Effect.andThen( | |
| 210 | Ref.update(registeredProtocols, (protocols) => new Set(protocols).add(scheme)), | |
| 211 | ), | |
| 212 | ), | |
| 133 | catch: (cause) => new ElectronProtocolRegistrationError({ scheme: input.scheme, cause }), | |
| 134 | }).pipe(Effect.andThen(Ref.set(registered, true))), | |
| 213 | 135 | () => |
| 214 | 136 | Effect.sync(() => { |
| 215 | Electron.protocol.unregisterProtocol(scheme); | |
| 216 | }).pipe( | |
| 217 | Effect.andThen( | |
| 218 | Ref.update(registeredProtocols, (protocols) => { | |
| 219 | const next = new Set(protocols); | |
| 220 | next.delete(scheme); | |
| 221 | return next; | |
| 222 | }), | |
| 223 | ), | |
| 224 | ), | |
| 137 | Electron.protocol.unhandle(input.scheme); | |
| 138 | }).pipe(Effect.andThen(Ref.set(registered, false))), | |
| 225 | 139 | ); |
| 226 | 140 | }, |
| 227 | 141 | ); |
| 228 | 142 | |
| 229 | const registerDesktopFileProtocol = Effect.gen(function* () { | |
| 230 | const environment = yield* DesktopEnvironment; | |
| 231 | if (environment.isDevelopment) return; | |
| 232 | ||
| 233 | const staticRoot = yield* resolveDesktopStaticDir; | |
| 234 | if (Option.isNone(staticRoot)) { | |
| 235 | return yield* new ElectronProtocolStaticBundleMissingError(); | |
| 236 | } | |
| 237 | ||
| 238 | const staticRootResolved = environment.path.resolve(staticRoot.value); | |
| 239 | const staticRootPrefix = `${staticRootResolved}${environment.path.sep}`; | |
| 240 | const fallbackIndex = environment.path.join(staticRootResolved, "index.html"); | |
| 241 | ||
| 242 | yield* registerFileProtocol({ | |
| 243 | scheme: DESKTOP_SCHEME, | |
| 244 | handler: Effect.fn("desktop.electron.protocol.handleDesktopFileRequest")(function* (request) { | |
| 245 | const fileSystem = yield* FileSystem.FileSystem; | |
| 246 | const environment = yield* DesktopEnvironment; | |
| 247 | const candidate = yield* resolveDesktopStaticPath(staticRootResolved, request.url); | |
| 248 | const resolvedCandidate = environment.path.resolve(candidate); | |
| 249 | const isInRoot = | |
| 250 | resolvedCandidate === fallbackIndex || resolvedCandidate.startsWith(staticRootPrefix); | |
| 251 | const isAssetRequest = isStaticAssetRequest(request.url, environment); | |
| 252 | const exists = yield* fileSystem | |
| 253 | .exists(resolvedCandidate) | |
| 254 | .pipe(Effect.orElseSucceed(() => false)); | |
| 255 | ||
| 256 | if (!isInRoot || !exists) { | |
| 257 | return isAssetRequest ? ({ error: -6 } as const) : ({ path: fallbackIndex } as const); | |
| 258 | } | |
| 259 | ||
| 260 | return { path: resolvedCandidate } as const; | |
| 261 | }), | |
| 262 | onFailure: () => ({ path: fallbackIndex }), | |
| 263 | }); | |
| 264 | }).pipe(Effect.withSpan("desktop.electron.protocol.registerDesktopFileProtocol")); | |
| 265 | ||
| 266 | return ElectronProtocol.of({ | |
| 267 | registerFileProtocol, | |
| 268 | registerDesktopFileProtocol, | |
| 269 | }); | |
| 143 | return ElectronProtocol.of({ registerDesktopProtocol }); | |
| 270 | 144 | }); |
| 271 | 145 | |
| 272 | 146 | export const layer = Layer.effect(ElectronProtocol, make); |
apps/desktop/src/ipc/DesktopIpcHandlers.ts+2-12| ... | ... | @@ -1,13 +1,6 @@ |
| 1 | 1 | import * as Effect from "effect/Effect"; |
| 2 | 2 | |
| 3 | 3 | import * as DesktopIpc from "./DesktopIpc.ts"; |
| 4 | import { | |
| 5 | clearCloudAuthToken, | |
| 6 | createCloudAuthRequest, | |
| 7 | fetchCloudAuth, | |
| 8 | getCloudAuthToken, | |
| 9 | setCloudAuthToken, | |
| 10 | } from "./methods/cloudAuth.ts"; | |
| 11 | 4 | import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts"; |
| 12 | 5 | import { |
| 13 | 6 | clearConnectionCatalog, |
| ... | ... | @@ -40,6 +33,7 @@ import { |
| 40 | 33 | import { |
| 41 | 34 | confirm, |
| 42 | 35 | getAppBranding, |
| 36 | getLocalEnvironmentBearerToken, | |
| 43 | 37 | getLocalEnvironmentBootstrap, |
| 44 | 38 | openExternal, |
| 45 | 39 | pickFolder, |
| ... | ... | @@ -54,6 +48,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers" |
| 54 | 48 | |
| 55 | 49 | yield* ipc.handleSync(getAppBranding); |
| 56 | 50 | yield* ipc.handleSync(getLocalEnvironmentBootstrap); |
| 51 | yield* ipc.handle(getLocalEnvironmentBearerToken); | |
| 57 | 52 | |
| 58 | 53 | yield* ipc.handle(getClientSettings); |
| 59 | 54 | yield* ipc.handle(setClientSettings); |
| ... | ... | @@ -80,11 +75,6 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers" |
| 80 | 75 | yield* ipc.handle(setTheme); |
| 81 | 76 | yield* ipc.handle(showContextMenu); |
| 82 | 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 | 78 | yield* ipc.handle(getUpdateState); |
| 89 | 79 | yield* ipc.handle(setUpdateChannel); |
| 90 | 80 | yield* ipc.handle(downloadUpdate); |
apps/desktop/src/ipc/channels.ts+2-6| ... | ... | @@ -3,12 +3,6 @@ export const CONFIRM_CHANNEL = "desktop:confirm"; |
| 3 | 3 | export const SET_THEME_CHANNEL = "desktop:set-theme"; |
| 4 | 4 | export const CONTEXT_MENU_CHANNEL = "desktop:context-menu"; |
| 5 | 5 | export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external"; |
| 6 | export const CREATE_CLOUD_AUTH_REQUEST_CHANNEL = "desktop:create-cloud-auth-request"; | |
| 7 | export const GET_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:get-cloud-auth-token"; | |
| 8 | export const SET_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:set-cloud-auth-token"; | |
| 9 | export const CLEAR_CLOUD_AUTH_TOKEN_CHANNEL = "desktop:clear-cloud-auth-token"; | |
| 10 | export const FETCH_CLOUD_AUTH_CHANNEL = "desktop:fetch-cloud-auth"; | |
| 11 | export const CLOUD_AUTH_CALLBACK_CHANNEL = "desktop:cloud-auth-callback"; | |
| 12 | 6 | export const MENU_ACTION_CHANNEL = "desktop:menu-action"; |
| 13 | 7 | export const UPDATE_STATE_CHANNEL = "desktop:update-state"; |
| 14 | 8 | export const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state"; |
| ... | ... | @@ -18,6 +12,8 @@ export const UPDATE_INSTALL_CHANNEL = "desktop:update-install"; |
| 18 | 12 | export const UPDATE_CHECK_CHANNEL = "desktop:update-check"; |
| 19 | 13 | export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding"; |
| 20 | 14 | export const GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL = "desktop:get-local-environment-bootstrap"; |
| 15 | export const GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL = | |
| 16 | "desktop:get-local-environment-bearer-token"; | |
| 21 | 17 | export const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings"; |
| 22 | 18 | export const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings"; |
| 23 | 19 | export const GET_CONNECTION_CATALOG_CHANNEL = "desktop:get-connection-catalog"; |
apps/desktop/src/ipc/methods/cloudAuth.test.ts deleted-97| ... | ... | @@ -1,97 +0,0 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | |
| 2 | import * as Effect from "effect/Effect"; | |
| 3 | import { afterEach } from "vite-plus/test"; | |
| 4 | ||
| 5 | import { fetchCloudAuth, validateClerkFrontendApiUrl } from "./cloudAuth.ts"; | |
| 6 | ||
| 7 | const originalClerkPublishableKey = process.env.T3CODE_CLERK_PUBLISHABLE_KEY; | |
| 8 | const originalFetch = globalThis.fetch; | |
| 9 | ||
| 10 | const clerkPublishableKey = (hostname: string): string => | |
| 11 | `pk_test_${Buffer.from(`${hostname}$`).toString("base64")}`; | |
| 12 | ||
| 13 | type FetchCall = readonly [input: RequestInfo | URL, init: RequestInit]; | |
| 14 | ||
| 15 | const 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 | ||
| 30 | describe("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 @@ |
| 1 | import { | |
| 2 | DesktopCloudAuthFetchInputSchema, | |
| 3 | DesktopCloudAuthFetchResultSchema, | |
| 4 | } from "@t3tools/contracts"; | |
| 5 | import { | |
| 6 | clerkFrontendApiHostnameFromPublishableKey, | |
| 7 | isAllowedClerkFrontendApiHostname, | |
| 8 | } from "@t3tools/shared/relayAuth"; | |
| 9 | import * as Data from "effect/Data"; | |
| 10 | import * as Effect from "effect/Effect"; | |
| 11 | import { identity } from "effect/Function"; | |
| 12 | import * as Layer from "effect/Layer"; | |
| 13 | import * as Option from "effect/Option"; | |
| 14 | import * as Schema from "effect/Schema"; | |
| 15 | import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"; | |
| 16 | ||
| 17 | import * as DesktopCloudAuth from "../../app/DesktopCloudAuth.ts"; | |
| 18 | import * as DesktopCloudAuthTokenStore from "../../app/DesktopCloudAuthTokenStore.ts"; | |
| 19 | import * as IpcChannels from "../channels.ts"; | |
| 20 | import { makeIpcMethod } from "../DesktopIpc.ts"; | |
| 21 | ||
| 22 | declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined; | |
| 23 | ||
| 24 | export 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 | ||
| 33 | function 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 | ||
| 44 | const allowedClerkFrontendApiHosts = (hostname: string): boolean => | |
| 45 | isAllowedClerkFrontendApiHostname(hostname, configuredClerkFrontendApiHostname()); | |
| 46 | ||
| 47 | export 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 | ||
| 57 | function 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 | ||
| 96 | const 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 | ||
| 119 | export 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 | ||
| 129 | export 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 | ||
| 139 | export 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 | ||
| 149 | export 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 | ||
| 159 | export 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 | 10 | import * as Schema from "effect/Schema"; |
| 11 | 11 | |
| 12 | 12 | import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts"; |
| 13 | import * as DesktopLocalEnvironmentAuth from "../../backend/DesktopLocalEnvironmentAuth.ts"; | |
| 13 | 14 | import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts"; |
| 14 | 15 | import * as ElectronDialog from "../../electron/ElectronDialog.ts"; |
| 15 | 16 | import * as ElectronMenu from "../../electron/ElectronMenu.ts"; |
| ... | ... | @@ -64,6 +65,16 @@ export const getLocalEnvironmentBootstrap = makeSyncIpcMethod({ |
| 64 | 65 | }), |
| 65 | 66 | }); |
| 66 | 67 | |
| 68 | export 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 | ||
| 67 | 78 | export const pickFolder = makeIpcMethod({ |
| 68 | 79 | channel: IpcChannels.PICK_FOLDER_CHANNEL, |
| 69 | 80 | payload: Schema.UndefinedOr(PickFolderOptionsSchema), |
apps/desktop/src/main.ts+19-7| ... | ... | @@ -27,13 +27,13 @@ import * as ElectronUpdater from "./electron/ElectronUpdater.ts"; |
| 27 | 27 | import * as ElectronWindow from "./electron/ElectronWindow.ts"; |
| 28 | 28 | import * as DesktopApp from "./app/DesktopApp.ts"; |
| 29 | 29 | import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts"; |
| 30 | import * as DesktopCloudAuth from "./app/DesktopCloudAuth.ts"; | |
| 31 | import * as DesktopCloudAuthTokenStore from "./app/DesktopCloudAuthTokenStore.ts"; | |
| 32 | 30 | import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts"; |
| 31 | import * as DesktopClerk from "./app/DesktopClerk.ts"; | |
| 33 | 32 | import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts"; |
| 34 | 33 | import * as DesktopAssets from "./app/DesktopAssets.ts"; |
| 35 | 34 | import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts"; |
| 36 | 35 | import * as DesktopBackendManager from "./backend/DesktopBackendManager.ts"; |
| 36 | import * as DesktopLocalEnvironmentAuth from "./backend/DesktopLocalEnvironmentAuth.ts"; | |
| 37 | 37 | import * as DesktopEnvironment from "./app/DesktopEnvironment.ts"; |
| 38 | 38 | import * as DesktopLifecycle from "./app/DesktopLifecycle.ts"; |
| 39 | 39 | import * as DesktopObservability from "./app/DesktopObservability.ts"; |
| ... | ... | @@ -119,7 +119,6 @@ const desktopFoundationLayer = Layer.mergeAll( |
| 119 | 119 | DesktopAppSettings.layer, |
| 120 | 120 | DesktopClientSettings.layer, |
| 121 | 121 | DesktopConnectionCatalogStore.layer.pipe(Layer.provideMerge(DesktopSavedEnvironments.layer)), |
| 122 | DesktopCloudAuthTokenStore.layer, | |
| 123 | 122 | DesktopAssets.layer, |
| 124 | 123 | DesktopObservability.layer, |
| 125 | 124 | ).pipe(Layer.provideMerge(desktopEnvironmentLayer)); |
| ... | ... | @@ -149,17 +148,30 @@ const desktopBackendLayer = DesktopBackendManager.layer.pipe( |
| 149 | 148 | Layer.provideMerge(desktopWindowLayer), |
| 150 | 149 | ); |
| 151 | 150 | |
| 151 | const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe( | |
| 152 | Layer.provideMerge(desktopBackendLayer), | |
| 153 | ); | |
| 154 | ||
| 152 | 155 | const desktopApplicationLayer = Layer.mergeAll( |
| 153 | 156 | DesktopLifecycle.layer, |
| 154 | 157 | DesktopApplicationMenu.layer, |
| 155 | DesktopCloudAuth.layer, | |
| 156 | 158 | DesktopShellEnvironment.layer, |
| 157 | 159 | desktopSshLayer, |
| 158 | ).pipe(Layer.provideMerge(DesktopUpdates.layer), Layer.provideMerge(desktopBackendLayer)); | |
| 160 | ).pipe( | |
| 161 | Layer.provideMerge(DesktopUpdates.layer), | |
| 162 | Layer.provideMerge(desktopLocalEnvironmentAuthLayer), | |
| 163 | ); | |
| 164 | ||
| 165 | const desktopClerkLayer = DesktopClerk.layer.pipe( | |
| 166 | Layer.provideMerge(desktopEnvironmentLayer), | |
| 167 | Layer.provideMerge(NodeServices.layer), | |
| 168 | Layer.provideMerge(ElectronApp.layer), | |
| 169 | ); | |
| 159 | 170 | |
| 160 | const desktopRuntimeLayer = ElectronProtocol.layerSchemePrivileges.pipe( | |
| 161 | Layer.flatMap(() => | |
| 171 | const desktopRuntimeLayer = desktopClerkLayer.pipe( | |
| 172 | Layer.flatMap((clerkContext) => | |
| 162 | 173 | desktopApplicationLayer.pipe( |
| 174 | Layer.provideMerge(Layer.succeedContext(clerkContext)), | |
| 163 | 175 | Layer.provideMerge(NodeServices.layer), |
| 164 | 176 | Layer.provideMerge(NodeHttpClient.layerUndici), |
| 165 | 177 | Layer.provideMerge(NetService.layer), |
apps/desktop/src/preload.ts+5-17| ... | ... | @@ -4,10 +4,13 @@ import type { |
| 4 | 4 | DesktopPreviewRecordingFrame, |
| 5 | 5 | DesktopPreviewTabState, |
| 6 | 6 | } from "@t3tools/contracts"; |
| 7 | import { exposeClerkBridge } from "@clerk/electron/preload"; | |
| 7 | 8 | import { contextBridge, ipcRenderer } from "electron"; |
| 8 | 9 | |
| 9 | 10 | import * as IpcChannels from "./ipc/channels.ts"; |
| 10 | 11 | |
| 12 | exposeClerkBridge({ passkeys: true }); | |
| 13 | ||
| 11 | 14 | function unwrapEnsureSshEnvironmentResult(result: unknown) { |
| 12 | 15 | if ( |
| 13 | 16 | typeof result === "object" && |
| ... | ... | @@ -39,6 +42,8 @@ contextBridge.exposeInMainWorld("desktopBridge", { |
| 39 | 42 | } |
| 40 | 43 | return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>; |
| 41 | 44 | }, |
| 45 | getLocalEnvironmentBearerToken: () => | |
| 46 | ipcRenderer.invoke(IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL), | |
| 42 | 47 | getClientSettings: () => ipcRenderer.invoke(IpcChannels.GET_CLIENT_SETTINGS_CHANNEL), |
| 43 | 48 | setClientSettings: (settings) => |
| 44 | 49 | ipcRenderer.invoke(IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, settings), |
| ... | ... | @@ -95,23 +100,6 @@ contextBridge.exposeInMainWorld("desktopBridge", { |
| 95 | 100 | ...(position === undefined ? {} : { position }), |
| 96 | 101 | }), |
| 97 | 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 | 103 | onMenuAction: (listener) => { |
| 116 | 104 | const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => { |
| 117 | 105 | if (typeof action !== "string") return; |
apps/desktop/src/window/DesktopWindow.test.ts+5-5| ... | ... | @@ -191,19 +191,19 @@ describe("DesktopWindow", () => { |
| 191 | 191 | it("recognizes only same-origin renderer navigations", () => { |
| 192 | 192 | assert.isTrue( |
| 193 | 193 | DesktopWindow.isSameOriginRendererNavigation({ |
| 194 | applicationUrl: "http://127.0.0.1:3773/", | |
| 195 | navigationUrl: "http://127.0.0.1:3773/settings/connections", | |
| 194 | applicationUrl: "t3code://app/", | |
| 195 | navigationUrl: "t3code://app/settings/connections", | |
| 196 | 196 | }), |
| 197 | 197 | ); |
| 198 | 198 | assert.isFalse( |
| 199 | 199 | DesktopWindow.isSameOriginRendererNavigation({ |
| 200 | applicationUrl: "http://127.0.0.1:3773/", | |
| 200 | applicationUrl: "t3code://app/", | |
| 201 | 201 | navigationUrl: "https://accounts.microsoft.com/oauth", |
| 202 | 202 | }), |
| 203 | 203 | ); |
| 204 | 204 | assert.isFalse( |
| 205 | 205 | DesktopWindow.isSameOriginRendererNavigation({ |
| 206 | applicationUrl: "http://127.0.0.1:3773/", | |
| 206 | applicationUrl: "t3code://app/", | |
| 207 | 207 | navigationUrl: "not a url", |
| 208 | 208 | }), |
| 209 | 209 | ); |
| ... | ... | @@ -231,7 +231,7 @@ describe("DesktopWindow", () => { |
| 231 | 231 | assert.equal(yield* Ref.get(createCount), 1); |
| 232 | 232 | assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor); |
| 233 | 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 | 235 | assert.equal(fakeWindow.openDevTools.mock.calls.length, 1); |
| 236 | 236 | }).pipe(Effect.provide(layer)); |
| 237 | 237 | }), |
apps/desktop/src/window/DesktopWindow.ts+7-30| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Data from "effect/Data"; | |
| 3 | 2 | import * as Effect from "effect/Effect"; |
| 4 | 3 | import * as Layer from "effect/Layer"; |
| 5 | 4 | import * as Option from "effect/Option"; |
| ... | ... | @@ -16,8 +15,8 @@ import * as ElectronMenu from "../electron/ElectronMenu.ts"; |
| 16 | 15 | import * as ElectronShell from "../electron/ElectronShell.ts"; |
| 17 | 16 | import * as ElectronTheme from "../electron/ElectronTheme.ts"; |
| 18 | 17 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; |
| 18 | import { getDesktopUrl } from "../electron/ElectronProtocol.ts"; | |
| 19 | 19 | import * as IpcChannels from "../ipc/channels.ts"; |
| 20 | import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts"; | |
| 21 | 20 | |
| 22 | 21 | const TITLEBAR_HEIGHT = 40; |
| 23 | 22 | const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux |
| ... | ... | @@ -32,7 +31,6 @@ type WindowTitleBarOptions = Pick< |
| 32 | 31 | type DesktopWindowRuntimeServices = |
| 33 | 32 | | DesktopEnvironment.DesktopEnvironment |
| 34 | 33 | | DesktopAssets.DesktopAssets |
| 35 | | DesktopServerExposure.DesktopServerExposure | |
| 36 | 34 | | DesktopState.DesktopState |
| 37 | 35 | | ElectronMenu.ElectronMenu |
| 38 | 36 | | ElectronShell.ElectronShell |
| ... | ... | @@ -40,16 +38,7 @@ type DesktopWindowRuntimeServices = |
| 40 | 38 | | ElectronWindow.ElectronWindow |
| 41 | 39 | | PreviewManager.PreviewManager; |
| 42 | 40 | |
| 43 | export 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 | ||
| 51 | 41 | export type DesktopWindowError = |
| 52 | | DesktopWindowDevServerUrlMissingError | |
| 53 | 42 | | ElectronWindow.ElectronWindowCreateError |
| 54 | 43 | | PreviewManager.PreviewManagerError; |
| 55 | 44 | |
| ... | ... | @@ -71,15 +60,6 @@ export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowS |
| 71 | 60 | const { logInfo: logWindowInfo, logWarning: logWindowWarning } = |
| 72 | 61 | DesktopObservability.makeComponentLogger("desktop-window"); |
| 73 | 62 | |
| 74 | function 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 | ||
| 83 | 63 | function getIconOption( |
| 84 | 64 | iconPaths: DesktopAssets.DesktopIconPaths, |
| 85 | 65 | platform: NodeJS.Platform, |
| ... | ... | @@ -171,18 +151,16 @@ const make = Effect.gen(function* () { |
| 171 | 151 | const electronTheme = yield* ElectronTheme.ElectronTheme; |
| 172 | 152 | const electronWindow = yield* ElectronWindow.ElectronWindow; |
| 173 | 153 | const previewManager = yield* PreviewManager.PreviewManager; |
| 174 | const serverExposure = yield* DesktopServerExposure.DesktopServerExposure; | |
| 175 | 154 | const state = yield* DesktopState.DesktopState; |
| 176 | 155 | const context = yield* Effect.context<DesktopWindowRuntimeServices>(); |
| 177 | 156 | const runPromise = Effect.runPromiseWith(context); |
| 178 | 157 | |
| 179 | const createWindow = Effect.fn("desktop.window.createWindow")(function* ( | |
| 180 | backendHttpUrl: URL, | |
| 181 | ): Effect.fn.Return<Electron.BrowserWindow, DesktopWindowError> { | |
| 158 | const createWindow = Effect.fn("desktop.window.createWindow")(function* (): Effect.fn.Return< | |
| 159 | Electron.BrowserWindow, | |
| 160 | DesktopWindowError | |
| 161 | > { | |
| 182 | 162 | yield* previewManager.getBrowserSession(); |
| 183 | const applicationUrl = environment.isDevelopment | |
| 184 | ? yield* resolveDesktopDevServerUrl(environment) | |
| 185 | : backendHttpUrl.href; | |
| 163 | const applicationUrl = getDesktopUrl(environment.isDevelopment); | |
| 186 | 164 | const iconPaths = yield* assets.iconPaths; |
| 187 | 165 | const iconOption = getIconOption(iconPaths, environment.platform); |
| 188 | 166 | const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors; |
| ... | ... | @@ -350,8 +328,7 @@ const make = Effect.gen(function* () { |
| 350 | 328 | }); |
| 351 | 329 | |
| 352 | 330 | const createMain = Effect.gen(function* () { |
| 353 | const backendConfig = yield* serverExposure.backendConfig; | |
| 354 | const window = yield* createWindow(backendConfig.httpBaseUrl); | |
| 331 | const window = yield* createWindow(); | |
| 355 | 332 | yield* electronWindow.setMain(window); |
| 356 | 333 | yield* logWindowInfo("main window created"); |
| 357 | 334 | return window; |
apps/desktop/vite.config.ts+6| ... | ... | @@ -56,6 +56,12 @@ export default defineConfig({ |
| 56 | 56 | outExtensions: () => ({ js: ".cjs" }), |
| 57 | 57 | define: publicConfigDefine, |
| 58 | 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 | 67 | format: "cjs", |
apps/mobile/package.json+1-1| ... | ... | @@ -40,7 +40,7 @@ |
| 40 | 40 | }, |
| 41 | 41 | "dependencies": { |
| 42 | 42 | "@callstack/liquid-glass": "^0.7.1", |
| 43 | "@clerk/expo": "^3.4.1", | |
| 43 | "@clerk/expo": "catalog:", | |
| 44 | 44 | "@effect/atom-react": "catalog:", |
| 45 | 45 | "@expo-google-fonts/dm-sans": "^0.4.2", |
| 46 | 46 | "@expo/ui": "~56.0.8", |
apps/mobile/src/connection/platform.ts+5| ... | ... | @@ -3,6 +3,7 @@ import { |
| 3 | 3 | CloudSession, |
| 4 | 4 | EnvironmentOwnedDataCleanup, |
| 5 | 5 | PlatformConnectionSource, |
| 6 | PrimaryEnvironmentAuth, | |
| 6 | 7 | RelayDeviceIdentity, |
| 7 | 8 | SshEnvironmentGateway, |
| 8 | 9 | } from "@t3tools/client-runtime/platform"; |
| ... | ... | @@ -119,6 +120,10 @@ const capabilitiesLayer = Layer.succeedContext( |
| 119 | 120 | }), |
| 120 | 121 | }), |
| 121 | 122 | ).pipe( |
| 123 | Context.add( | |
| 124 | PrimaryEnvironmentAuth, | |
| 125 | PrimaryEnvironmentAuth.of({ bearerToken: Effect.succeed(Option.none()) }), | |
| 126 | ), | |
| 122 | 127 | Context.add( |
| 123 | 128 | RelayDeviceIdentity, |
| 124 | 129 | RelayDeviceIdentity.of({ |
apps/server/src/relay/AgentAwarenessRelay.test.ts+5-1| ... | ... | @@ -625,7 +625,11 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 625 | 625 | } satisfies ExecutionEnvironmentDescriptor; |
| 626 | 626 | |
| 627 | 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 | 633 | runFork(Deferred.succeed(fetchSeen, url)); |
| 630 | 634 | return Promise.resolve(Response.json({ ok: true, deliveries: [] })); |
| 631 | 635 | }) as unknown as typeof fetch; |
apps/web/package.json+2-2| ... | ... | @@ -12,8 +12,8 @@ |
| 12 | 12 | }, |
| 13 | 13 | "dependencies": { |
| 14 | 14 | "@base-ui/react": "^1.4.1", |
| 15 | "@clerk/clerk-js": "^6.16.0", | |
| 16 | "@clerk/react": "^6.9.0", | |
| 15 | "@clerk/electron": "catalog:", | |
| 16 | "@clerk/react": "catalog:", | |
| 17 | 17 | "@dnd-kit/core": "^6.3.1", |
| 18 | 18 | "@dnd-kit/modifiers": "^9.0.0", |
| 19 | 19 | "@dnd-kit/sortable": "^10.0.0", |
apps/web/src/authBootstrap.test.ts+20-17| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | import { |
| 2 | AuthSessionState as AuthSessionStateSchema, | |
| 3 | 2 | EnvironmentAuthInvalidError, |
| 4 | 3 | type AuthBrowserSessionResult, |
| 5 | 4 | type AuthCreatePairingCredentialInput, |
| ... | ... | @@ -8,10 +7,11 @@ import { |
| 8 | 7 | } from "@t3tools/contracts"; |
| 9 | 8 | import * as DateTime from "effect/DateTime"; |
| 10 | 9 | import * as Effect from "effect/Effect"; |
| 11 | import * as Schema from "effect/Schema"; | |
| 10 | import { HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | |
| 12 | 11 | import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 13 | 12 | |
| 14 | 13 | import { installEnvironmentHttpTest } from "../test/environmentHttpTest"; |
| 14 | import { __setPrimaryHttpRunnerForTests, type PrimaryHttpEffectRunner } from "./lib/runtime"; | |
| 15 | 15 | |
| 16 | 16 | type TestWindow = { |
| 17 | 17 | location: URL; |
| ... | ... | @@ -36,8 +36,6 @@ const DESKTOP_AUTH = { |
| 36 | 36 | } as const; |
| 37 | 37 | |
| 38 | 38 | const SESSION_EXPIRES_AT = DateTime.makeUnsafe("2026-04-05T00:00:00.000Z"); |
| 39 | const encodeAuthSessionState = Schema.encodeSync(AuthSessionStateSchema); | |
| 40 | ||
| 41 | 39 | const unauthenticatedSession = (auth: AuthSessionState["auth"]): AuthSessionState => ({ |
| 42 | 40 | authenticated: false, |
| 43 | 41 | auth, |
| ... | ... | @@ -117,6 +115,7 @@ describe("resolveInitialServerAuthGateState", () => { |
| 117 | 115 | disposeHttpTest = undefined; |
| 118 | 116 | const { __resetServerAuthBootstrapForTests } = await import("./environments/primary"); |
| 119 | 117 | __resetServerAuthBootstrapForTests(); |
| 118 | __setPrimaryHttpRunnerForTests(); | |
| 120 | 119 | vi.unstubAllEnvs(); |
| 121 | 120 | vi.useRealTimers(); |
| 122 | 121 | vi.restoreAllMocks(); |
| ... | ... | @@ -220,18 +219,22 @@ describe("resolveInitialServerAuthGateState", () => { |
| 220 | 219 | |
| 221 | 220 | it("retries transient auth session bootstrap failures after restart", async () => { |
| 222 | 221 | vi.useFakeTimers(); |
| 223 | const fetchMock = vi | |
| 224 | .fn<typeof fetch>() | |
| 225 | .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 })) | |
| 226 | .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 })) | |
| 227 | .mockResolvedValueOnce(new Response("Bad Gateway", { status: 502 })) | |
| 228 | .mockResolvedValueOnce( | |
| 229 | new Response( | |
| 230 | JSON.stringify(encodeAuthSessionState(unauthenticatedSession(LOOPBACK_AUTH))), | |
| 231 | { status: 200, headers: { "content-type": "application/json" } }, | |
| 232 | ), | |
| 233 | ); | |
| 234 | vi.stubGlobal("fetch", fetchMock); | |
| 222 | let attempts = 0; | |
| 223 | const request = HttpClientRequest.get("http://localhost/api/auth/session"); | |
| 224 | const response = HttpClientResponse.fromWeb( | |
| 225 | request, | |
| 226 | new Response("Bad Gateway", { status: 502 }), | |
| 227 | ); | |
| 228 | const runner: PrimaryHttpEffectRunner = async <A>() => { | |
| 229 | attempts += 1; | |
| 230 | if (attempts < 4) { | |
| 231 | throw new HttpClientError.HttpClientError({ | |
| 232 | reason: new HttpClientError.StatusCodeError({ request, response }), | |
| 233 | }); | |
| 234 | } | |
| 235 | return unauthenticatedSession(LOOPBACK_AUTH) as A; | |
| 236 | }; | |
| 237 | __setPrimaryHttpRunnerForTests(runner); | |
| 235 | 238 | |
| 236 | 239 | const { resolveInitialServerAuthGateState } = await import("./environments/primary"); |
| 237 | 240 | |
| ... | ... | @@ -242,7 +245,7 @@ describe("resolveInitialServerAuthGateState", () => { |
| 242 | 245 | status: "requires-auth", |
| 243 | 246 | auth: LOOPBACK_AUTH, |
| 244 | 247 | }); |
| 245 | expect(fetchMock).toHaveBeenCalledTimes(4); | |
| 248 | expect(attempts).toBe(4); | |
| 246 | 249 | }); |
| 247 | 250 | |
| 248 | 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 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | ||
| 3 | import { resolveDesktopCloudAuthOAuthOptions } from "./desktopAuth"; | |
| 4 | ||
| 5 | describe("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 @@ |
| 1 | export type DesktopCloudAuthOAuthStrategy = `oauth_${string}`; | |
| 2 | ||
| 3 | export interface DesktopCloudAuthOAuthOption { | |
| 4 | readonly strategy: DesktopCloudAuthOAuthStrategy; | |
| 5 | readonly label: string; | |
| 6 | readonly providerId: string; | |
| 7 | readonly iconUrl: string | null; | |
| 8 | } | |
| 9 | ||
| 10 | interface ClerkOAuthProviderSetting { | |
| 11 | readonly enabled?: unknown; | |
| 12 | readonly authenticatable?: unknown; | |
| 13 | readonly strategy?: unknown; | |
| 14 | readonly name?: unknown; | |
| 15 | readonly logo_url?: unknown; | |
| 16 | } | |
| 17 | ||
| 18 | interface ClerkUserSettingsLike { | |
| 19 | readonly authenticatableSocialStrategies?: unknown; | |
| 20 | readonly social?: unknown; | |
| 21 | } | |
| 22 | ||
| 23 | interface ClerkEnvironmentLike { | |
| 24 | readonly userSettings?: ClerkUserSettingsLike; | |
| 25 | } | |
| 26 | ||
| 27 | interface ClerkLike { | |
| 28 | readonly __internal_environment?: ClerkEnvironmentLike; | |
| 29 | readonly environment?: ClerkEnvironmentLike; | |
| 30 | } | |
| 31 | ||
| 32 | const isClerkOAuthProviderSetting = (value: unknown): value is ClerkOAuthProviderSetting => | |
| 33 | typeof value === "object" && value !== null; | |
| 34 | ||
| 35 | const 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 | |
| 49 | export function isDesktopCloudAuthOAuthStrategy( | |
| 50 | value: unknown, | |
| 51 | ): value is DesktopCloudAuthOAuthStrategy { | |
| 52 | return typeof value === "string" && value.startsWith("oauth_"); | |
| 53 | } | |
| 54 | ||
| 55 | export 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 | ||
| 69 | export 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 | ||
| 107 | function 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 | ||
| 119 | function 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 | ||
| 135 | function 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 @@ |
| 1 | import { Clerk } from "@clerk/clerk-js"; | |
| 2 | import { | |
| 3 | buildClerkUIScriptAttributes, | |
| 4 | clerkUIScriptUrl, | |
| 5 | InternalClerkProvider, | |
| 6 | } from "@clerk/react/internal"; | |
| 7 | import type { ClerkProviderProps } from "@clerk/react"; | |
| 8 | import { | |
| 9 | clerkFrontendApiHostnameFromPublishableKey, | |
| 10 | isAllowedClerkFrontendApiHostname, | |
| 11 | } from "@t3tools/shared/relayAuth"; | |
| 12 | import React, { useEffect, useState } from "react"; | |
| 13 | ||
| 14 | import { | |
| 15 | makeDesktopClerkExternalAccountAdapter, | |
| 16 | type DesktopClerkUser, | |
| 17 | } from "./desktopClerkExternalAccounts"; | |
| 18 | ||
| 19 | type DesktopClerkUiCtor = NonNullable<Window["__internal_ClerkUICtor"]>; | |
| 20 | ||
| 21 | interface ClerkFrontendApiRequest { | |
| 22 | credentials?: RequestCredentials; | |
| 23 | headers?: Headers; | |
| 24 | url?: URL; | |
| 25 | } | |
| 26 | ||
| 27 | interface ClerkFrontendApiResponse { | |
| 28 | headers: Headers; | |
| 29 | payload?: { | |
| 30 | errors?: readonly { | |
| 31 | code?: string; | |
| 32 | }[]; | |
| 33 | }; | |
| 34 | } | |
| 35 | ||
| 36 | interface 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 | ||
| 58 | interface DesktopClerkProviderProps { | |
| 59 | readonly children: React.ReactNode; | |
| 60 | readonly publishableKey: string; | |
| 61 | } | |
| 62 | ||
| 63 | let desktopClerk: Clerk | null = null; | |
| 64 | let desktopClerkFetchInstalled = false; | |
| 65 | let desktopClerkUiLoad: Promise<DesktopClerkUiCtor> | null = null; | |
| 66 | let desktopClerkFrontendApiHostname: string | null = null; | |
| 67 | let desktopClerkExternalAccountCleanup: (() => void) | null = null; | |
| 68 | ||
| 69 | const 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 | ||
| 85 | const getStoredClientJwt = (): Promise<string | null> => | |
| 86 | window.desktopBridge?.getCloudAuthToken() ?? Promise.resolve(null); | |
| 87 | ||
| 88 | const setStoredClientJwt = (token: string): Promise<boolean> => | |
| 89 | window.desktopBridge?.setCloudAuthToken(token) ?? Promise.resolve(false); | |
| 90 | ||
| 91 | const clearStoredClientJwt = (): Promise<void> => | |
| 92 | window.desktopBridge?.clearCloudAuthToken() ?? Promise.resolve(); | |
| 93 | ||
| 94 | const isClerkFrontendApiUrl = (url: URL): boolean => | |
| 95 | url.protocol === "https:" && | |
| 96 | isAllowedClerkFrontendApiHostname(url.hostname, desktopClerkFrontendApiHostname); | |
| 97 | ||
| 98 | const 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 | ||
| 106 | function 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 | ||
| 140 | function 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 | ||
| 159 | function 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 | ||
| 220 | function 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 | ||
| 278 | export 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 @@ |
| 1 | import { describe, expect, it, vi } from "vite-plus/test"; | |
| 2 | ||
| 3 | import { | |
| 4 | makeDesktopClerkExternalAccountAdapter, | |
| 5 | type DesktopClerkUser, | |
| 6 | } from "./desktopClerkExternalAccounts"; | |
| 7 | ||
| 8 | describe("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 @@ |
| 1 | interface DesktopClerkExternalAccountParams { | |
| 2 | readonly redirectUrl?: string; | |
| 3 | readonly [key: string]: unknown; | |
| 4 | } | |
| 5 | ||
| 6 | interface DesktopClerkExternalAccount { | |
| 7 | reauthorize: (params: DesktopClerkExternalAccountParams) => Promise<DesktopClerkExternalAccount>; | |
| 8 | } | |
| 9 | ||
| 10 | interface DesktopClerkUser { | |
| 11 | readonly externalAccounts: readonly DesktopClerkExternalAccount[]; | |
| 12 | createExternalAccount: ( | |
| 13 | params: DesktopClerkExternalAccountParams, | |
| 14 | ) => Promise<DesktopClerkExternalAccount>; | |
| 15 | reload: () => Promise<unknown>; | |
| 16 | } | |
| 17 | ||
| 18 | interface DesktopClerkExternalAccountBridge { | |
| 19 | readonly createCloudAuthRequest: () => Promise<string>; | |
| 20 | readonly onCloudAuthCallback: (listener: (rawUrl: string) => void) => () => void; | |
| 21 | } | |
| 22 | ||
| 23 | interface DesktopClerkExternalAccountAdapter { | |
| 24 | readonly dispose: () => void; | |
| 25 | readonly installUser: (user: DesktopClerkUser) => void; | |
| 26 | } | |
| 27 | ||
| 28 | interface 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 | |
| 38 | export 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 | ||
| 112 | export type { DesktopClerkExternalAccountAdapter, DesktopClerkUser }; |
apps/web/src/cloud/linkEnvironment.test.ts+30| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | import { |
| 2 | type DesktopBridge, | |
| 2 | 3 | EnvironmentId, |
| 3 | 4 | type RelayClientInstallProgressEvent, |
| 4 | 5 | WS_METHODS, |
| ... | ... | @@ -28,6 +29,7 @@ import { |
| 28 | 29 | ManagedRelayDpopSigner, |
| 29 | 30 | } from "@t3tools/client-runtime/relay"; |
| 30 | 31 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; |
| 32 | import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth"; | |
| 31 | 33 | |
| 32 | 34 | import { |
| 33 | 35 | collectCloudLinkTargets, |
| ... | ... | @@ -146,6 +148,7 @@ beforeEach(() => { |
| 146 | 148 | }); |
| 147 | 149 | |
| 148 | 150 | afterEach(() => { |
| 151 | __resetDesktopPrimaryAuthForTests(); | |
| 149 | 152 | vi.unstubAllGlobals(); |
| 150 | 153 | vi.unstubAllEnvs(); |
| 151 | 154 | vi.restoreAllMocks(); |
| ... | ... | @@ -224,6 +227,33 @@ describe("web cloud link environment client", () => { |
| 224 | 227 | }), |
| 225 | 228 | ); |
| 226 | 229 | |
| 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 | 257 | it.effect("links an available primary environment without invoking installation", () => |
| 228 | 258 | Effect.gen(function* () { |
| 229 | 259 | const fetchMock = vi |
apps/web/src/cloud/linkEnvironment.ts+9-22| ... | ... | @@ -31,7 +31,7 @@ import { |
| 31 | 31 | readPrimaryEnvironmentDescriptor, |
| 32 | 32 | readPrimaryEnvironmentTarget, |
| 33 | 33 | } from "../environments/primary"; |
| 34 | import { withPrimaryEnvironmentRequestInit } from "../environments/primary/requestInit"; | |
| 34 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 35 | 35 | import { resolveCloudPublicConfig } from "./publicConfig"; |
| 36 | 36 | import { |
| 37 | 37 | finishRelayClientInstall, |
| ... | ... | @@ -327,11 +327,8 @@ export function readPrimaryCloudLinkState(input: { |
| 327 | 327 | const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); |
| 328 | 328 | return yield* client.connect |
| 329 | 329 | .linkState({ headers: {} }) |
| 330 | .pipe( | |
| 331 | withPrimaryEnvironmentRequestInit, | |
| 332 | Effect.mapError(environmentApiError("Could not read environment cloud link state.")), | |
| 333 | ); | |
| 334 | }); | |
| 330 | .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state."))); | |
| 331 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 335 | 332 | } |
| 336 | 333 | |
| 337 | 334 | export function updatePrimaryCloudPreferences(input: { |
| ... | ... | @@ -346,10 +343,9 @@ export function updatePrimaryCloudPreferences(input: { |
| 346 | 343 | payload: input, |
| 347 | 344 | }) |
| 348 | 345 | .pipe( |
| 349 | withPrimaryEnvironmentRequestInit, | |
| 350 | 346 | Effect.mapError(environmentApiError("Could not update environment cloud preferences.")), |
| 351 | 347 | ); |
| 352 | }); | |
| 348 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 353 | 349 | } |
| 354 | 350 | |
| 355 | 351 | export function unlinkPrimaryEnvironmentFromCloud(input: { |
| ... | ... | @@ -360,10 +356,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: { |
| 360 | 356 | const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); |
| 361 | 357 | yield* client.connect |
| 362 | 358 | .unlink({ headers: {} }) |
| 363 | .pipe( | |
| 364 | withPrimaryEnvironmentRequestInit, | |
| 365 | Effect.mapError(environmentApiError("Could not unlink the environment from cloud.")), | |
| 366 | ); | |
| 359 | .pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud."))); | |
| 367 | 360 | |
| 368 | 361 | const configuredRelayUrl = relayUrl(); |
| 369 | 362 | if (configuredRelayUrl && input.clerkToken) { |
| ... | ... | @@ -381,7 +374,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: { |
| 381 | 374 | ), |
| 382 | 375 | ); |
| 383 | 376 | } |
| 384 | }); | |
| 377 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 385 | 378 | } |
| 386 | 379 | |
| 387 | 380 | export function linkPrimaryEnvironmentToCloud(input: { |
| ... | ... | @@ -433,10 +426,7 @@ export function linkPrimaryEnvironmentToCloud(input: { |
| 433 | 426 | origin: endpointOrigin(input.target.httpBaseUrl), |
| 434 | 427 | }, |
| 435 | 428 | }) |
| 436 | .pipe( | |
| 437 | withPrimaryEnvironmentRequestInit, | |
| 438 | Effect.mapError(environmentApiError("Could not obtain environment link proof.")), | |
| 439 | ); | |
| 429 | .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof."))); | |
| 440 | 430 | const link = yield* relayClient |
| 441 | 431 | .linkEnvironment({ |
| 442 | 432 | clerkToken: input.clerkToken, |
| ... | ... | @@ -470,9 +460,6 @@ export function linkPrimaryEnvironmentToCloud(input: { |
| 470 | 460 | endpointRuntime: link.endpointRuntime, |
| 471 | 461 | }, |
| 472 | 462 | }) |
| 473 | .pipe( | |
| 474 | withPrimaryEnvironmentRequestInit, | |
| 475 | Effect.mapError(environmentApiError("Could not configure environment relay access.")), | |
| 476 | ); | |
| 477 | }); | |
| 463 | .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access."))); | |
| 464 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 478 | 465 | } |
apps/web/src/components/clerk/DesktopClerkCard.tsx deleted-137| ... | ... | @@ -1,137 +0,0 @@ |
| 1 | import type { ReactNode } from "react"; | |
| 2 | ||
| 3 | import { 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 | |
| 10 | export 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 | ||
| 32 | export 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 | ||
| 41 | export 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 | ||
| 64 | export 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 | ||
| 77 | export 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 | ||
| 92 | export 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 | ||
| 110 | function 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 @@ |
| 1 | import { LoaderCircleIcon } from "lucide-react"; | |
| 2 | ||
| 3 | import type { | |
| 4 | DesktopCloudAuthOAuthOption, | |
| 5 | DesktopCloudAuthOAuthStrategy, | |
| 6 | } from "../../cloud/desktopAuth"; | |
| 7 | import { cn } from "../../lib/utils"; | |
| 8 | import { | |
| 9 | DesktopClerkAlert, | |
| 10 | DesktopClerkCard, | |
| 11 | DesktopClerkFooterAction, | |
| 12 | DesktopClerkHeader, | |
| 13 | } from "./DesktopClerkCard"; | |
| 14 | import { 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 | |
| 18 | export 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 | ||
| 32 | export 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 | ||
| 71 | function 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 | ||
| 124 | function 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 @@ |
| 1 | import { useClerk } from "@clerk/react"; | |
| 2 | import { useState } from "react"; | |
| 3 | ||
| 4 | import { | |
| 5 | DesktopClerkAlert, | |
| 6 | DesktopClerkCard, | |
| 7 | DesktopClerkFooterAction, | |
| 8 | DesktopClerkHeader, | |
| 9 | DesktopClerkInput, | |
| 10 | DesktopClerkPrimaryButton, | |
| 11 | } from "./DesktopClerkCard"; | |
| 12 | import { DesktopClerkSignIn } from "./DesktopClerkSignIn"; | |
| 13 | ||
| 14 | type 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 | |
| 19 | export 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 | ||
| 29 | function 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 | ||
| 96 | function 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 @@ |
| 1 | import { useClerk } from "@clerk/react"; | |
| 2 | import { useSignIn, useSignUp } from "@clerk/react/legacy"; | |
| 3 | import { useCallback, useEffect, useRef, useState } from "react"; | |
| 4 | ||
| 5 | import { | |
| 6 | type DesktopCloudAuthOAuthStrategy, | |
| 7 | resolveDesktopCloudAuthOAuthOptions, | |
| 8 | } from "../../cloud/desktopAuth"; | |
| 9 | import { 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 | |
| 14 | class 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 | ||
| 24 | async 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 | ||
| 35 | function 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 | ||
| 46 | export 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 | 1 | import { useClerk } from "@clerk/react"; |
| 2 | import { useState } from "react"; | |
| 3 | ||
| 4 | import { isElectron } from "../../env"; | |
| 5 | import { Dialog, DialogPopup } from "../ui/dialog"; | |
| 6 | import { DesktopClerkWaitlist } from "./DesktopClerkWaitlist"; | |
| 7 | 2 | |
| 8 | 3 | export function useT3ConnectAuthPrompt() { |
| 9 | 4 | const clerk = useClerk(); |
| 10 | const [desktopAuthOpen, setDesktopAuthOpen] = useState(false); | |
| 11 | ||
| 12 | 5 | const openAuthPrompt = () => { |
| 13 | if (isElectron) { | |
| 14 | setDesktopAuthOpen(true); | |
| 15 | return; | |
| 16 | } | |
| 17 | 6 | clerk.openWaitlist(); |
| 18 | 7 | }; |
| 19 | ||
| 20 | const authPrompt = isElectron ? ( | |
| 21 | <Dialog open={desktopAuthOpen} onOpenChange={setDesktopAuthOpen}> | |
| 22 | <DialogPopup className="max-w-md border-0 bg-transparent shadow-none" showCloseButton={false}> | |
| 23 | <DesktopClerkWaitlist /> | |
| 24 | </DialogPopup> | |
| 25 | </Dialog> | |
| 26 | ) : null; | |
| 27 | ||
| 28 | return { authPrompt, openAuthPrompt }; | |
| 8 | return { authPrompt: null, openAuthPrompt }; | |
| 29 | 9 | } |
apps/web/src/connection/platform.ts+31-30| ... | ... | @@ -3,6 +3,7 @@ import { |
| 3 | 3 | CloudSession, |
| 4 | 4 | EnvironmentOwnedDataCleanup, |
| 5 | 5 | PlatformConnectionSource, |
| 6 | PrimaryEnvironmentAuth, | |
| 6 | 7 | RelayDeviceIdentity, |
| 7 | 8 | SshEnvironmentGateway, |
| 8 | 9 | } from "@t3tools/client-runtime/platform"; |
| ... | ... | @@ -30,9 +31,9 @@ import * as Option from "effect/Option"; |
| 30 | 31 | import * as Queue from "effect/Queue"; |
| 31 | 32 | import * as Schedule from "effect/Schedule"; |
| 32 | 33 | import * as Stream from "effect/Stream"; |
| 33 | import { FetchHttpClient, HttpClient } from "effect/unstable/http"; | |
| 34 | 34 | |
| 35 | import { primaryEnvironmentRequestInit } from "../environments/primary/requestInit"; | |
| 35 | import { readDesktopPrimaryBearerToken } from "../environments/primary/desktopAuth"; | |
| 36 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 36 | 37 | import { readPrimaryEnvironmentTarget } from "../environments/primary/target"; |
| 37 | 38 | import { clearComposerDraftsEnvironment } from "../composerDraftStore"; |
| 38 | 39 | import { isHostedStaticApp } from "../hostedPairing"; |
| ... | ... | @@ -193,6 +194,16 @@ const capabilitiesLayer = Layer.effectContext( |
| 193 | 194 | const identity = RelayDeviceIdentity.of({ |
| 194 | 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 | 207 | const ssh = SshEnvironmentGateway.of({ |
| 197 | 208 | provision: Effect.fn("web.connectionPlatform.ssh.provision")(function* (target) { |
| 198 | 209 | const bridge = window.desktopBridge; |
| ... | ... | @@ -252,6 +263,7 @@ const capabilitiesLayer = Layer.effectContext( |
| 252 | 263 | }); |
| 253 | 264 | |
| 254 | 265 | return Context.make(CloudSession, cloudSession).pipe( |
| 266 | Context.add(PrimaryEnvironmentAuth, primaryAuth), | |
| 255 | 267 | Context.add(RelayDeviceIdentity, identity), |
| 256 | 268 | Context.add(ClientPresentation, presentation), |
| 257 | 269 | Context.add(SshEnvironmentGateway, ssh), |
| ... | ... | @@ -271,10 +283,7 @@ const loadPrimaryConnectionRegistration = Effect.fn( |
| 271 | 283 | } |
| 272 | 284 | const descriptor = yield* fetchRemoteEnvironmentDescriptor({ |
| 273 | 285 | httpBaseUrl: resolved.target.httpBaseUrl, |
| 274 | }).pipe( | |
| 275 | Effect.provideService(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit), | |
| 276 | Effect.mapError(mapRemoteEnvironmentError), | |
| 277 | ); | |
| 286 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer), Effect.mapError(mapRemoteEnvironmentError)); | |
| 278 | 287 | return new PrimaryConnectionRegistration({ |
| 279 | 288 | target: new PrimaryConnectionTarget({ |
| 280 | 289 | environmentId: descriptor.environmentId, |
| ... | ... | @@ -289,32 +298,24 @@ const primaryRegistrationRetrySchedule = Schedule.exponential("1 second").pipe( |
| 289 | 298 | Schedule.either(Schedule.spaced("16 seconds")), |
| 290 | 299 | ); |
| 291 | 300 | |
| 292 | const platformConnectionSourceLayer = Layer.effect( | |
| 293 | PlatformConnectionSource, | |
| 294 | Effect.gen(function* () { | |
| 295 | if (isHostedStaticApp()) { | |
| 296 | return PlatformConnectionSource.of({ | |
| 297 | registrations: Stream.empty, | |
| 298 | }); | |
| 299 | } | |
| 300 | const httpClient = yield* HttpClient.HttpClient; | |
| 301 | const platformConnectionSourceLayer = Layer.sync(PlatformConnectionSource, () => { | |
| 302 | if (isHostedStaticApp()) { | |
| 301 | 303 | return PlatformConnectionSource.of({ |
| 302 | registrations: Stream.fromEffect( | |
| 303 | loadPrimaryConnectionRegistration().pipe( | |
| 304 | Effect.provideService(HttpClient.HttpClient, httpClient), | |
| 305 | ), | |
| 306 | ).pipe( | |
| 307 | Stream.tapError((error) => | |
| 308 | Effect.logWarning("Could not discover the primary environment.", { | |
| 309 | error, | |
| 310 | }), | |
| 311 | ), | |
| 312 | Stream.retry(primaryRegistrationRetrySchedule), | |
| 313 | Stream.catchCause(() => Stream.empty), | |
| 314 | ), | |
| 304 | registrations: Stream.empty, | |
| 315 | 305 | }); |
| 316 | }), | |
| 317 | ); | |
| 306 | } | |
| 307 | return PlatformConnectionSource.of({ | |
| 308 | registrations: Stream.fromEffect(loadPrimaryConnectionRegistration()).pipe( | |
| 309 | Stream.tapError((error) => | |
| 310 | Effect.logWarning("Could not discover the primary environment.", { | |
| 311 | error, | |
| 312 | }), | |
| 313 | ), | |
| 314 | Stream.retry(primaryRegistrationRetrySchedule), | |
| 315 | Stream.catchCause(() => Stream.empty), | |
| 316 | ), | |
| 317 | }); | |
| 318 | }); | |
| 318 | 319 | |
| 319 | 320 | const environmentOwnedDataCleanupLayer = Layer.succeed( |
| 320 | 321 | EnvironmentOwnedDataCleanup, |
apps/web/src/environments/primary/desktopAuth.test.ts created+33| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | import type { DesktopBridge } from "@t3tools/contracts"; | |
| 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "@effect/vitest"; | |
| 3 | ||
| 4 | import { __resetDesktopPrimaryAuthForTests, readDesktopPrimaryBearerToken } from "./desktopAuth"; | |
| 5 | ||
| 6 | describe("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 @@ |
| 1 | let desktopBearerTokenPromise: Promise<string> | null = null; | |
| 2 | ||
| 3 | export 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 | ||
| 19 | export function __resetDesktopPrimaryAuthForTests(): void { | |
| 20 | desktopBearerTokenPromise = null; | |
| 21 | } |
apps/web/src/environments/primary/httpLayer.test.ts created+65| ... | ... | @@ -0,0 +1,65 @@ |
| 1 | import type { DesktopBridge } from "@t3tools/contracts"; | |
| 2 | import { afterEach, describe, expect, it, vi } from "@effect/vitest"; | |
| 3 | import * as Effect from "effect/Effect"; | |
| 4 | import { HttpClient } from "effect/unstable/http"; | |
| 5 | ||
| 6 | import { __resetDesktopPrimaryAuthForTests } from "./desktopAuth"; | |
| 7 | import { makePrimaryEnvironmentHttpLayer } from "./httpLayer"; | |
| 8 | ||
| 9 | describe.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 @@ |
| 1 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 2 | import * as Effect from "effect/Effect"; | |
| 3 | import * as Layer from "effect/Layer"; | |
| 4 | import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"; | |
| 5 | ||
| 6 | import { readDesktopPrimaryBearerToken } from "./desktopAuth"; | |
| 7 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; | |
| 8 | ||
| 9 | function 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 | ||
| 22 | function 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 | ||
| 34 | export 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 | ||
| 58 | export const primaryEnvironmentHttpLayer = makePrimaryEnvironmentHttpLayer(); |
apps/web/src/environments/primary/index.ts+2| ... | ... | @@ -33,6 +33,8 @@ export { |
| 33 | 33 | |
| 34 | 34 | export { refreshPrimarySessionState, usePrimarySessionState } from "./sessionState"; |
| 35 | 35 | |
| 36 | export { PrimaryEnvironmentHttpClient } from "./httpClient"; | |
| 37 | ||
| 36 | 38 | export { |
| 37 | 39 | readPrimaryEnvironmentTarget, |
| 38 | 40 | resolvePrimaryEnvironmentHttpUrl, |
apps/web/src/environments/primary/requestInit.ts deleted-7| ... | ... | @@ -1,7 +0,0 @@ |
| 1 | import * as Effect from "effect/Effect"; | |
| 2 | import { FetchHttpClient } from "effect/unstable/http"; | |
| 3 | ||
| 4 | export const primaryEnvironmentRequestInit = { credentials: "include" } as const; | |
| 5 | ||
| 6 | export 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 | 1 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 2 | 2 | import type * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | import { FetchHttpClient } from "effect/unstable/http"; | |
| 5 | 4 | import * as Socket from "effect/unstable/socket/Socket"; |
| 6 | 5 | |
| 7 | 6 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; |
| 8 | import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; | |
| 9 | 7 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; |
| 10 | 8 | import { |
| 11 | 9 | PrimaryEnvironmentHttpClient, |
| 12 | 10 | primaryEnvironmentHttpClientLive, |
| 13 | 11 | } from "../environments/primary/httpClient"; |
| 14 | import { primaryEnvironmentRequestInit } from "../environments/primary/requestInit"; | |
| 12 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 15 | 13 | |
| 16 | 14 | import { browserCryptoLayer } from "../cloud/dpop"; |
| 17 | 15 | import { managedRelayClientLayer } from "../cloud/managedRelayLayer"; |
| ... | ... | @@ -32,15 +30,7 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig( |
| 32 | 30 | export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer); |
| 33 | 31 | |
| 34 | 32 | const primaryHttpRuntime = ManagedRuntime.make( |
| 35 | primaryEnvironmentHttpClientLive.pipe( | |
| 36 | Layer.provide( | |
| 37 | Layer.mergeAll( | |
| 38 | remoteHttpClientLayer((input, init) => globalThis.fetch(input, init)), | |
| 39 | Layer.succeed(FetchHttpClient.RequestInit, primaryEnvironmentRequestInit), | |
| 40 | httpHeaderRedactionLayer, | |
| 41 | ), | |
| 42 | ), | |
| 43 | ), | |
| 33 | primaryEnvironmentHttpClientLive.pipe(Layer.provide(primaryEnvironmentHttpLayer)), | |
| 44 | 34 | ); |
| 45 | 35 | |
| 46 | 36 | export type PrimaryHttpEffectRunner = <A, E>( |
apps/web/src/main.tsx+4-3| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | 1 | import React from "react"; |
| 2 | 2 | import ReactDOM from "react-dom/client"; |
| 3 | 3 | import { ClerkProvider } from "@clerk/react"; |
| 4 | import { passkeys } from "@clerk/electron/passkeys"; | |
| 5 | import { ClerkProvider as ElectronClerkProvider } from "@clerk/electron/react"; | |
| 4 | 6 | import { createHashHistory, createBrowserHistory } from "@tanstack/react-router"; |
| 5 | 7 | |
| 6 | 8 | import "@fontsource-variable/dm-sans/index.css"; |
| ... | ... | @@ -10,7 +12,6 @@ import "@xterm/xterm/css/xterm.css"; |
| 10 | 12 | import "./index.css"; |
| 11 | 13 | |
| 12 | 14 | import { isElectron } from "./env"; |
| 13 | import { DesktopClerkProvider } from "./cloud/desktopClerk"; | |
| 14 | 15 | import { ManagedRelayAuthProvider } from "./cloud/managedAuth"; |
| 15 | 16 | import { hasCloudPublicConfig } from "./cloud/publicConfig"; |
| 16 | 17 | import { getRouter } from "./router"; |
| ... | ... | @@ -37,9 +38,9 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( |
| 37 | 38 | <React.StrictMode> |
| 38 | 39 | {clerkPublishableKey && hasCloudPublicConfig() ? ( |
| 39 | 40 | isElectron ? ( |
| 40 | <DesktopClerkProvider publishableKey={clerkPublishableKey}> | |
| 41 | <ElectronClerkProvider publishableKey={clerkPublishableKey} passkeys={passkeys}> | |
| 41 | 42 | <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider> |
| 42 | </DesktopClerkProvider> | |
| 43 | </ElectronClerkProvider> | |
| 43 | 44 | ) : ( |
| 44 | 45 | <ClerkProvider publishableKey={clerkPublishableKey}> |
| 45 | 46 | <ManagedRelayAuthProvider>{app}</ManagedRelayAuthProvider> |
apps/web/src/observability/clientTracing.ts+3-2| ... | ... | @@ -3,11 +3,12 @@ import * as Layer from "effect/Layer"; |
| 3 | 3 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 4 | 4 | import * as Scope from "effect/Scope"; |
| 5 | 5 | import * as Tracer from "effect/Tracer"; |
| 6 | import { FetchHttpClient, HttpClient } from "effect/unstable/http"; | |
| 6 | import { HttpClient } from "effect/unstable/http"; | |
| 7 | 7 | import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; |
| 8 | 8 | |
| 9 | 9 | import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; |
| 10 | 10 | import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary"; |
| 11 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 11 | 12 | import { isElectron } from "../env"; |
| 12 | 13 | import { APP_VERSION } from "~/branding"; |
| 13 | 14 | |
| ... | ... | @@ -22,7 +23,7 @@ const CLIENT_TRACING_RESOURCE = { |
| 22 | 23 | } as const; |
| 23 | 24 | |
| 24 | 25 | const delegateRuntimeLayer = Layer.mergeAll( |
| 25 | FetchHttpClient.layer, | |
| 26 | primaryEnvironmentHttpLayer, | |
| 26 | 27 | OtlpSerialization.layerJson, |
| 27 | 28 | Layer.succeed(HttpClient.TracerDisabledWhen, () => true), |
| 28 | 29 | ); |
apps/web/vite.config.ts+1| ... | ... | @@ -126,6 +126,7 @@ export default defineConfig(() => { |
| 126 | 126 | }, |
| 127 | 127 | resolve: { |
| 128 | 128 | tsconfigPaths: true, |
| 129 | dedupe: ["react", "react-dom"], | |
| 129 | 130 | }, |
| 130 | 131 | server: { |
| 131 | 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 | 120 | ## Desktop OAuth Redirect Allowlist |
| 121 | 121 | |
| 122 | 122 | The desktop app opens OAuth in the system browser and returns to the app with a custom URL scheme. |
| 123 | In **Clerk Dashboard > Native applications**, enable native application support and add these | |
| 124 | entries under the mobile SSO redirect allowlist: | |
| 123 | In **Clerk Dashboard > Native applications**, enable the Native API and add these entries under the | |
| 124 | mobile SSO redirect allowlist: | |
| 125 | 125 | |
| 126 | 126 | ```text |
| 127 | t3code-dev://auth/callback | |
| 128 | t3code://auth/callback | |
| 127 | t3code-dev://app/ | |
| 128 | t3code://app/ | |
| 129 | 129 | ``` |
| 130 | 130 | |
| 131 | The first entry is for local desktop development. The second is for packaged desktop builds. | |
| 132 | The app also adds a request-scoped `t3_state` query parameter and validates it on callback. Initial | |
| 133 | sign-in and linked-account OAuth flows both return through this bridge. The desktop provider keeps | |
| 134 | Clerk's stock profile component, replaces its renderer-page callback with the custom-scheme callback, | |
| 135 | and opens the provider URL in the system browser. Do not add the local renderer URL as an OAuth | |
| 136 | redirect: an external browser cannot use it to reopen the packaged app. | |
| 131 | Local desktop development uses `t3code-dev://app`, while packaged builds use `t3code://app`. Add the | |
| 132 | matching origin to each Clerk instance's Backend API `allowed_origins` array as well. The development | |
| 133 | Clerk instance should only need `t3code-dev://app`; the production Clerk instance should only need | |
| 134 | `t3code://app`. `@clerk/electron` owns the native request adapter, encrypted Clerk token persistence, | |
| 135 | external-browser OAuth transport, and callback delivery for initial sign-in and linked-account flows. | |
| 136 | ||
| 137 | There is currently no Dashboard UI for `allowed_origins`. Preserve any existing entries and update | |
| 138 | the instance through the Backend API: | |
| 139 | ||
| 140 | ```sh | |
| 141 | curl -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 | ||
| 147 | Never put `CLERK_SECRET_KEY` in the desktop app, a client-facing environment file, or a build | |
| 148 | artifact. | |
| 149 | ||
| 150 | ## Desktop Passkeys | |
| 151 | ||
| 152 | The production macOS bundle ID is `com.t3tools.t3code`. To enable native passkeys: | |
| 153 | ||
| 154 | 1. Create an explicit macOS App ID for `com.t3tools.t3code` in the Apple Developer portal and enable | |
| 155 | **Associated Domains**. | |
| 156 | 2. Create a compatible macOS provisioning profile for that App ID and the certificate used to sign | |
| 157 | the distributed app. | |
| 158 | 3. 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. | |
| 160 | 4. Confirm Clerk serves `https://<frontend-api>/.well-known/apple-app-site-association` and that | |
| 161 | `webcredentials.apps` contains `<TEAM_ID>.com.t3tools.t3code`. | |
| 162 | 5. Set the local or CI signing configuration described below. | |
| 163 | ||
| 164 | For a local signed build, add these values to `.env.local` or export them before invoking the | |
| 165 | desktop artifact command: | |
| 166 | ||
| 167 | ```dotenv | |
| 168 | T3CODE_APPLE_TEAM_ID=ABC1234567 | |
| 169 | T3CODE_MACOS_PROVISIONING_PROFILE=/absolute/path/to/t3code.provisionprofile | |
| 170 | # Optional: comma-separated override when Clerk's RP ID differs from the Frontend API hostname. | |
| 171 | T3CODE_CLERK_PASSKEY_RP_DOMAINS=example.clerk.accounts.dev,clerk.example.com | |
| 172 | ``` | |
| 173 | ||
| 174 | When `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, | |
| 176 | or 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 | ||
| 179 | The normal `dev:desktop` launcher is unsigned and cannot complete macOS passkey ceremonies. For | |
| 180 | renderer HMR, build and install a signed app first, run the renderer dev server, then launch the | |
| 181 | installed app executable with `VITE_DEV_SERVER_URL` and `T3CODE_PORT` set. Rebuild the signed app | |
| 182 | after native dependency, main-process, preload, entitlement, provisioning, or signing changes; | |
| 183 | renderer-only changes can reuse the installed app. | |
| 184 | ||
| 185 | For the default development ports, run `pnpm dev:web` in one terminal and launch the installed | |
| 186 | binary from another: | |
| 187 | ||
| 188 | ```sh | |
| 189 | VITE_DEV_SERVER_URL=http://127.0.0.1:5733 \ | |
| 190 | T3CODE_PORT=13773 \ | |
| 191 | "/Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)" | |
| 192 | ``` | |
| 193 | ||
| 194 | After changing Associated Domains, bump the build version before rebuilding; macOS may otherwise | |
| 195 | reuse stale Shared Web Credentials metadata for the same app/version pair. | |
| 196 | ||
| 197 | Verify the installed bundle before testing: | |
| 198 | ||
| 199 | ```sh | |
| 200 | codesign --verify --deep --strict "/Applications/T3 Code (Alpha).app" | |
| 201 | codesign -d --entitlements :- "/Applications/T3 Code (Alpha).app" | |
| 202 | ``` | |
| 137 | 203 | |
| 138 | 204 | The current mobile UI uses Clerk's native authentication view. If a future mobile browser OAuth |
| 139 | 205 | flow uses a custom redirect URI, add that exact URI to the same allowlist. |
docs/operations/ci.md+1-1| ... | ... | @@ -2,5 +2,5 @@ |
| 2 | 2 | |
| 3 | 3 | - `.github/workflows/ci.yml` runs `bun run lint`, `bun run typecheck`, and `bun run test` on pull requests and pushes to `main`. |
| 4 | 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 | 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 | 219 | - `APPLE_API_KEY` |
| 220 | 220 | - `APPLE_API_KEY_ID` |
| 221 | 221 | - `APPLE_API_ISSUER` |
| 222 | - `MACOS_PROVISIONING_PROFILE` (base64-encoded provisioning profile with Associated Domains) | |
| 223 | ||
| 224 | Required repository variables: | |
| 225 | ||
| 226 | - `APPLE_TEAM_ID` | |
| 227 | ||
| 228 | Optional 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. | |
| 222 | 232 | |
| 223 | 233 | Checklist: |
| 224 | 234 | |
| 225 | 235 | 1. Apple Developer account access: |
| 226 | 236 | - Team has rights to create Developer ID certificates. |
| 227 | 2. Create `Developer ID Application` certificate. | |
| 228 | 3. Export certificate + private key as `.p12` from Keychain. | |
| 229 | 4. Base64-encode the `.p12` and store as `CSC_LINK`. | |
| 230 | 5. Store the `.p12` export password as `CSC_KEY_PASSWORD`. | |
| 231 | 6. In App Store Connect, create an API key (Team key). | |
| 232 | 7. Add API key values: | |
| 237 | 2. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains. | |
| 238 | 3. Create a `Developer ID Application` certificate and a compatible provisioning profile for that | |
| 239 | App ID with Associated Domains enabled. | |
| 240 | 4. Export the certificate + private key as `.p12` from Keychain. | |
| 241 | 5. Base64-encode the `.p12` and store as `CSC_LINK`. | |
| 242 | 6. Base64-encode the provisioning profile and store it as `MACOS_PROVISIONING_PROFILE`. | |
| 243 | 7. Store the `.p12` export password as `CSC_KEY_PASSWORD`, and set `APPLE_TEAM_ID` to the | |
| 244 | 10-character Apple Developer Team ID. | |
| 245 | 8. In App Store Connect, create an API key (Team key). | |
| 246 | 9. Add API key values: | |
| 233 | 247 | - `APPLE_API_KEY`: contents of the downloaded `.p8` |
| 234 | 248 | - `APPLE_API_KEY_ID`: Key ID |
| 235 | 249 | - `APPLE_API_ISSUER`: Issuer ID |
| 236 | 8. Re-run a tag release and confirm macOS artifacts are signed/notarized. | |
| 250 | 10. Complete the Clerk Native API and AASA setup in [T3 Connect Clerk Setup](../cloud/t3-connect-clerk.md#desktop-passkeys). | |
| 251 | 11. Re-run a tag release and confirm macOS artifacts are signed/notarized and contain the expected | |
| 252 | `com.apple.developer.associated-domains` entitlement. | |
| 237 | 253 | |
| 238 | 254 | Notes: |
| 239 | 255 | |
| 240 | 256 | - `APPLE_API_KEY` is stored as raw key text in secrets. |
| 241 | 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. | |
| 242 | 260 | |
| 243 | 261 | ## 3) Azure Trusted Signing setup (Windows) |
| 244 | 262 | |
| ... | ... | @@ -281,7 +299,9 @@ Checklist: |
| 281 | 299 | ## 5) Troubleshooting |
| 282 | 300 | |
| 283 | 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 | 305 | - Windows build unsigned when expected signed: |
| 286 | 306 | - Check all Azure ATS and auth secrets are populated and non-empty. |
| 287 | 307 | - Build fails with signing error: |
docs/reference/scripts.md+4-1| ... | ... | @@ -20,11 +20,14 @@ |
| 20 | 20 | |
| 21 | 21 | - Default build is unsigned/not notarized for local sharing. |
| 22 | 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 | 24 | - Desktop packaging includes `apps/server/dist` (the `t3` backend) and starts it on loopback with an auth token for WebSocket/API traffic. |
| 25 | 25 | - Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch. |
| 26 | 26 | - To keep staging files for debugging package contents, run: `bun run dist:desktop:dmg -- --keep-stage` |
| 27 | 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 | 31 | - Windows `--signed` uses Azure Trusted Signing and expects: |
| 29 | 32 | `AZURE_TRUSTED_SIGNING_ENDPOINT`, `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME`, |
| 30 | 33 | `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME`, and `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME`. |
infra/relay/package.json+1-1| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | "typecheck": "tsgo --noEmit" |
| 10 | 10 | }, |
| 11 | 11 | "dependencies": { |
| 12 | "@clerk/backend": "3.6.1", | |
| 12 | "@clerk/backend": "catalog:", | |
| 13 | 13 | "@effect/sql-pg": "catalog:", |
| 14 | 14 | "@t3tools/client-runtime": "workspace:*", |
| 15 | 15 | "@t3tools/contracts": "workspace:*", |
packages/client-runtime/src/connection/resolver.test.ts+44| ... | ... | @@ -17,6 +17,7 @@ import { ConnectionResolver } from "./resolver.ts"; |
| 17 | 17 | import { connectionResolverLayer } from "./resolver.ts"; |
| 18 | 18 | import { |
| 19 | 19 | CloudSession, |
| 20 | PrimaryEnvironmentAuth, | |
| 20 | 21 | RelayDeviceIdentity, |
| 21 | 22 | SshEnvironmentGateway, |
| 22 | 23 | } from "../platform/capabilities.ts"; |
| ... | ... | @@ -101,6 +102,7 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o |
| 101 | 102 | readonly connectEnvironment?: ManagedRelayClient["Service"]["connectEnvironment"]; |
| 102 | 103 | readonly authorizeBearer?: RemoteEnvironmentAuthorization["Service"]["authorizeBearer"]; |
| 103 | 104 | readonly authorizeDpop?: RemoteEnvironmentAuthorization["Service"]["authorizeDpop"]; |
| 105 | readonly primaryBearerToken?: string; | |
| 104 | 106 | readonly prepareSsh?: SshEnvironmentGateway["Service"]["prepare"]; |
| 105 | 107 | }) => { |
| 106 | 108 | const profiles = new Map( |
| ... | ... | @@ -170,6 +172,12 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o |
| 170 | 172 | Layer.succeed(ConnectionProfileStore, profileStore), |
| 171 | 173 | Layer.succeed(ConnectionCredentialStore, credentialStore), |
| 172 | 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 | 181 | Layer.succeed( |
| 174 | 182 | RelayDeviceIdentity, |
| 175 | 183 | RelayDeviceIdentity.of({ deviceId: Effect.succeed(Option.some("device-1")) }), |
| ... | ... | @@ -217,6 +225,42 @@ describe("ConnectionResolver", () => { |
| 217 | 225 | }), |
| 218 | 226 | ); |
| 219 | 227 | |
| 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 | 264 | it.effect("uses the registered bearer profile without re-reading the profile store", () => |
| 221 | 265 | Effect.gen(function* () { |
| 222 | 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 | 10 | import { ManagedRelayClient } from "../relay/managedRelay.ts"; |
| 11 | 11 | import { |
| 12 | 12 | CloudSession, |
| 13 | PrimaryEnvironmentAuth, | |
| 13 | 14 | RelayDeviceIdentity, |
| 14 | 15 | SshEnvironmentGateway, |
| 15 | 16 | } from "../platform/capabilities.ts"; |
| ... | ... | @@ -58,17 +59,37 @@ function primarySocketUrl(target: PrimaryConnectionTarget): string { |
| 58 | 59 | return url.toString(); |
| 59 | 60 | } |
| 60 | 61 | |
| 61 | const primaryBroker = Effect.fn("clientRuntime.connection.broker.primary")( | |
| 62 | (target: PrimaryConnectionTarget) => | |
| 63 | Effect.succeed({ | |
| 64 | environmentId: target.environmentId, | |
| 65 | label: target.label, | |
| 62 | const makePrimaryBroker = Effect.fn("clientRuntime.connection.broker.makePrimary")(function* () { | |
| 63 | const auth = yield* PrimaryEnvironmentAuth; | |
| 64 | const remote = yield* RemoteEnvironmentAuthorization; | |
| 65 | ||
| 66 | return Effect.fn("clientRuntime.connection.broker.primary")(function* ( | |
| 67 | target: PrimaryConnectionTarget, | |
| 68 | ) { | |
| 69 | const bearerToken = yield* auth.bearerToken; | |
| 70 | if (Option.isNone(bearerToken)) { | |
| 71 | return { | |
| 72 | environmentId: target.environmentId, | |
| 73 | label: target.label, | |
| 74 | httpBaseUrl: target.httpBaseUrl, | |
| 75 | socketUrl: primarySocketUrl(target), | |
| 76 | httpAuthorization: null, | |
| 77 | target, | |
| 78 | } satisfies PreparedConnection; | |
| 79 | } | |
| 80 | ||
| 81 | const authorized = yield* remote.authorizeBearer({ | |
| 82 | expectedEnvironmentId: target.environmentId, | |
| 66 | 83 | httpBaseUrl: target.httpBaseUrl, |
| 67 | socketUrl: primarySocketUrl(target), | |
| 68 | httpAuthorization: null, | |
| 84 | wsBaseUrl: target.wsBaseUrl, | |
| 85 | bearerToken: bearerToken.value, | |
| 86 | }); | |
| 87 | return { | |
| 88 | ...authorized, | |
| 69 | 89 | target, |
| 70 | } satisfies PreparedConnection), | |
| 71 | ); | |
| 90 | } satisfies PreparedConnection; | |
| 91 | }); | |
| 92 | }); | |
| 72 | 93 | |
| 73 | 94 | const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () { |
| 74 | 95 | const credentials = yield* ConnectionCredentialStore; |
| ... | ... | @@ -228,6 +249,7 @@ const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(funct |
| 228 | 249 | export const connectionResolverLayer = Layer.effect( |
| 229 | 250 | ConnectionResolver, |
| 230 | 251 | Effect.gen(function* () { |
| 252 | const primary = yield* makePrimaryBroker(); | |
| 231 | 253 | const bearer = yield* makeBearerBroker(); |
| 232 | 254 | const relay = yield* makeRelayBroker(); |
| 233 | 255 | const ssh = yield* makeSshBroker(); |
| ... | ... | @@ -242,7 +264,7 @@ export const connectionResolverLayer = Layer.effect( |
| 242 | 264 | }); |
| 243 | 265 | switch (target._tag) { |
| 244 | 266 | case "PrimaryConnectionTarget": |
| 245 | return yield* primaryBroker(target); | |
| 267 | return yield* primary(target); | |
| 246 | 268 | case "BearerConnectionTarget": |
| 247 | 269 | return yield* bearer({ ...entry, target }); |
| 248 | 270 | case "RelayConnectionTarget": |
packages/client-runtime/src/platform/capabilities.ts+7| ... | ... | @@ -43,6 +43,13 @@ export class ClientPresentation extends Context.Service< |
| 43 | 43 | } |
| 44 | 44 | >()("@t3tools/client-runtime/platform/capabilities/ClientPresentation") {} |
| 45 | 45 | |
| 46 | export 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 | ||
| 46 | 53 | export class SshEnvironmentGateway extends Context.Service< |
| 47 | 54 | SshEnvironmentGateway, |
| 48 | 55 | { |
packages/contracts/src/ipc.ts+1-23| ... | ... | @@ -415,23 +415,6 @@ export const PickFolderOptionsSchema = Schema.Struct({ |
| 415 | 415 | initialPath: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 416 | 416 | }); |
| 417 | 417 | |
| 418 | export 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 | }); | |
| 424 | export type DesktopCloudAuthFetchInput = typeof DesktopCloudAuthFetchInputSchema.Type; | |
| 425 | ||
| 426 | export 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 | }); | |
| 433 | export type DesktopCloudAuthFetchResult = typeof DesktopCloudAuthFetchResultSchema.Type; | |
| 434 | ||
| 435 | 418 | /** |
| 436 | 419 | * Renderer-facing snapshot of a desktop preview tab. Mirrors the main-process |
| 437 | 420 | * PreviewTabState shape but uses serialisable primitives only. |
| ... | ... | @@ -897,6 +880,7 @@ export const DesktopPreviewAutomationWaitForInputSchema = Schema.Struct({ |
| 897 | 880 | export interface DesktopBridge { |
| 898 | 881 | getAppBranding: () => DesktopAppBranding | null; |
| 899 | 882 | getLocalEnvironmentBootstrap: () => DesktopEnvironmentBootstrap | null; |
| 883 | getLocalEnvironmentBearerToken: () => Promise<string>; | |
| 900 | 884 | getClientSettings: () => Promise<ClientSettings | null>; |
| 901 | 885 | setClientSettings: (settings: ClientSettings) => Promise<void>; |
| 902 | 886 | getConnectionCatalog?: () => Promise<string | null>; |
| ... | ... | @@ -935,12 +919,6 @@ export interface DesktopBridge { |
| 935 | 919 | position?: { x: number; y: number }, |
| 936 | 920 | ) => Promise<T | null>; |
| 937 | 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 | 922 | onMenuAction: (listener: (action: string) => void) => () => void; |
| 945 | 923 | getUpdateState: () => Promise<DesktopUpdateState>; |
| 946 | 924 | setUpdateChannel: (channel: DesktopUpdateChannel) => Promise<DesktopUpdateState>; |
pnpm-lock.yaml+510-284| ... | ... | @@ -35,6 +35,13 @@ catalogs: |
| 35 | 35 | version: 0.1.24 |
| 36 | 36 | |
| 37 | 37 | overrides: |
| 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 | 45 | '@clerk/clerk-js>@base-org/account': '-' |
| 39 | 46 | '@clerk/clerk-js>@coinbase/wallet-sdk': '-' |
| 40 | 47 | '@clerk/clerk-js>@solana/wallet-adapter-base': '-' |
| ... | ... | @@ -105,6 +112,12 @@ importers: |
| 105 | 112 | |
| 106 | 113 | apps/desktop: |
| 107 | 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 | 121 | '@effect/platform-node': |
| 109 | 122 | specifier: 4.0.0-beta.78 |
| 110 | 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 | 142 | electron: |
| 130 | 143 | specifier: 41.5.0 |
| 131 | 144 | version: 41.5.0 |
| 145 | electron-store: | |
| 146 | specifier: ^8.2.0 | |
| 147 | version: 8.2.0 | |
| 132 | 148 | electron-updater: |
| 133 | 149 | specifier: ^6.6.2 |
| 134 | 150 | version: 6.8.3 |
| ... | ... | @@ -178,10 +194,10 @@ importers: |
| 178 | 194 | dependencies: |
| 179 | 195 | '@callstack/liquid-glass': |
| 180 | 196 | 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 | 198 | '@clerk/expo': |
| 183 | specifier: ^3.4.1 | |
| 184 | version: 3.4.1(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 199 | specifier: 3.4.8-snapshot.v20260619001138 | |
| 200 | version: 3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 185 | 201 | '@effect/atom-react': |
| 186 | 202 | specifier: 4.0.0-beta.78 |
| 187 | 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 | 206 | version: 0.4.2 |
| 191 | 207 | '@expo/ui': |
| 192 | 208 | specifier: ~56.0.8 |
| 193 | version: 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) | |
| 209 | version: 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | |
| 194 | 210 | '@legendapp/list': |
| 195 | 211 | 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 | 213 | '@noble/curves': |
| 198 | 214 | specifier: 'catalog:' |
| 199 | 215 | version: 1.9.1 |
| ... | ... | @@ -205,7 +221,7 @@ importers: |
| 205 | 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 | 222 | '@react-native-menu/menu': |
| 207 | 223 | 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 | 225 | '@shikijs/core': |
| 210 | 226 | specifier: 4.2.0 |
| 211 | 227 | version: 4.2.0 |
| ... | ... | @@ -226,7 +242,7 @@ importers: |
| 226 | 242 | version: link:../../packages/contracts |
| 227 | 243 | '@t3tools/mobile-markdown-text': |
| 228 | 244 | 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 | 246 | '@t3tools/mobile-review-diff-native': |
| 231 | 247 | specifier: file:./modules/t3-review-diff |
| 232 | 248 | version: file:apps/mobile/modules/t3-review-diff |
| ... | ... | @@ -247,40 +263,40 @@ importers: |
| 247 | 263 | version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5) |
| 248 | 264 | expo: |
| 249 | 265 | 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 | 267 | expo-asset: |
| 252 | 268 | 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 | 270 | expo-auth-session: |
| 255 | 271 | 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 | 273 | expo-build-properties: |
| 258 | 274 | specifier: ~56.0.15 |
| 259 | 275 | version: 56.0.16(expo@56.0.8) |
| 260 | 276 | expo-camera: |
| 261 | 277 | 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 | 279 | expo-clipboard: |
| 264 | 280 | 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 | 282 | expo-constants: |
| 267 | 283 | 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 | 285 | expo-crypto: |
| 270 | 286 | specifier: ~56.0.4 |
| 271 | 287 | version: 56.0.4(expo@56.0.8) |
| 272 | 288 | expo-dev-client: |
| 273 | 289 | 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 | 291 | expo-file-system: |
| 276 | 292 | 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 | 294 | expo-font: |
| 279 | 295 | 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 | 297 | expo-glass-effect: |
| 282 | 298 | 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 | 300 | expo-haptics: |
| 285 | 301 | specifier: ~56.0.3 |
| 286 | 302 | version: 56.0.3(expo@56.0.8) |
| ... | ... | @@ -289,19 +305,19 @@ importers: |
| 289 | 305 | version: 56.0.15(expo@56.0.8) |
| 290 | 306 | expo-linking: |
| 291 | 307 | 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 | 309 | expo-network: |
| 294 | 310 | specifier: ~56.0.5 |
| 295 | 311 | version: 56.0.5(expo@56.0.8)(react@19.2.3) |
| 296 | 312 | expo-notifications: |
| 297 | 313 | 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 | 315 | expo-paste-input: |
| 300 | 316 | 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 | 318 | expo-router: |
| 303 | 319 | specifier: ~56.2.7 |
| 304 | version: 56.2.8(c021de11d02907bd585610408f5252e8) | |
| 320 | version: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310) | |
| 305 | 321 | expo-secure-store: |
| 306 | 322 | specifier: ~56.0.4 |
| 307 | 323 | version: 56.0.4(expo@56.0.8) |
| ... | ... | @@ -310,16 +326,16 @@ importers: |
| 310 | 326 | version: 56.0.10(expo@56.0.8)(typescript@6.0.3) |
| 311 | 327 | expo-symbols: |
| 312 | 328 | 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 | 330 | expo-updates: |
| 315 | 331 | 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 | 333 | expo-web-browser: |
| 318 | 334 | 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 | 336 | expo-widgets: |
| 321 | 337 | specifier: ~56.0.15 |
| 322 | version: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584) | |
| 338 | version: 56.0.16(961c4aa6f32829b318e3c87ef20ad401) | |
| 323 | 339 | punycode: |
| 324 | 340 | specifier: ^2.3.1 |
| 325 | 341 | version: 2.3.1 |
| ... | ... | @@ -331,43 +347,43 @@ importers: |
| 331 | 347 | version: 19.2.3(react@19.2.3) |
| 332 | 348 | react-native: |
| 333 | 349 | 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 | 351 | react-native-gesture-handler: |
| 336 | 352 | 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 | 354 | react-native-image-viewing: |
| 339 | 355 | 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 | 357 | react-native-keyboard-controller: |
| 342 | 358 | 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 | 360 | react-native-nitro-markdown: |
| 345 | 361 | 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 | 363 | react-native-nitro-modules: |
| 348 | 364 | 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 | 366 | react-native-reanimated: |
| 351 | 367 | 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 | 369 | react-native-safe-area-context: |
| 354 | 370 | 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 | 372 | react-native-screens: |
| 357 | 373 | 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 | 375 | react-native-shiki-engine: |
| 360 | 376 | 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 | 378 | react-native-svg: |
| 363 | 379 | 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 | 381 | react-native-webview: |
| 366 | 382 | 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 | 384 | react-native-worklets: |
| 369 | 385 | 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 | 387 | shiki: |
| 372 | 388 | specifier: 4.2.0 |
| 373 | 389 | version: 4.2.0 |
| ... | ... | @@ -376,7 +392,7 @@ importers: |
| 376 | 392 | version: 3.6.0 |
| 377 | 393 | uniwind: |
| 378 | 394 | 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 | 396 | devDependencies: |
| 381 | 397 | '@effect/vitest': |
| 382 | 398 | specifier: 4.0.0-beta.78 |
| ... | ... | @@ -466,12 +482,12 @@ importers: |
| 466 | 482 | '@base-ui/react': |
| 467 | 483 | specifier: ^1.4.1 |
| 468 | 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': | |
| 470 | specifier: ^6.16.0 | |
| 471 | version: 6.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 485 | '@clerk/electron': | |
| 486 | specifier: 0.0.1-snapshot.v20260619001138 | |
| 487 | version: 0.0.1-snapshot.v20260619001138(@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 472 | 488 | '@clerk/react': |
| 473 | specifier: ^6.9.0 | |
| 474 | version: 6.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 489 | specifier: 6.10.4-snapshot.v20260619001138 | |
| 490 | version: 6.10.4-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 475 | 491 | '@dnd-kit/core': |
| 476 | 492 | specifier: ^6.3.1 |
| 477 | 493 | version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| ... | ... | @@ -621,8 +637,8 @@ importers: |
| 621 | 637 | infra/relay: |
| 622 | 638 | dependencies: |
| 623 | 639 | '@clerk/backend': |
| 624 | specifier: 3.6.1 | |
| 625 | version: 3.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 640 | specifier: 3.8.2-snapshot.v20260619001138 | |
| 641 | version: 3.8.2-snapshot.v20260619001138(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 626 | 642 | '@effect/sql-pg': |
| 627 | 643 | specifier: 4.0.0-beta.78 |
| 628 | 644 | version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)) |
| ... | ... | @@ -1516,19 +1532,60 @@ packages: |
| 1516 | 1532 | resolution: {integrity: sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA==} |
| 1517 | 1533 | engines: {node: '>= 20.12.0'} |
| 1518 | 1534 | |
| 1519 | '@clerk/backend@3.6.1': | |
| 1520 | resolution: {integrity: sha512-LkfekzF/0UMXacX+17xy3ExRraO0mm+thXejC8Q32gWHd1wLdxK3YXDsLDF00E1r1InWBKIt2ZOxs6hTwZPJjA==} | |
| 1535 | '@clerk/backend@3.8.2-snapshot.v20260619001138': | |
| 1536 | resolution: {integrity: sha512-nT6M7rKTuvoDnSZwO3Th2NMjcWZy/0ZfXYyqd/o/lFpUFcQO0J4fM2e2wF9kNCl99EPvDQQUAR8APNNy/j40rg==} | |
| 1521 | 1537 | engines: {node: '>=20.9.0'} |
| 1522 | 1538 | |
| 1523 | '@clerk/clerk-js@6.16.0': | |
| 1524 | resolution: {integrity: sha512-8xv/XDsxhOZd1n4DNIRJ2EehIRUg6UiqKAnfd0L88R2t1g6sVnLi1FInJ5i8Qyx5oY/creXx6X1AZ1V5PobRkA==} | |
| 1539 | '@clerk/clerk-js@6.18.2-snapshot.v20260619001138': | |
| 1540 | resolution: {integrity: sha512-BpRSi2QXdfR5nnzC7/YCCqK40m1M4A/rN5unau7QKHj6V7xChl2fOvxYjekpH+DEyw6NAe/2jdqQv35iv3T5oA==} | |
| 1525 | 1541 | engines: {node: '>=20.9.0'} |
| 1526 | 1542 | |
| 1527 | '@clerk/expo@3.4.1': | |
| 1528 | resolution: {integrity: sha512-gpAXsuUnsUdUD0/2XjyxaC9quF5rT+2umkmV74nBLVAFurGMMMMHvnHqrQEtZ7tH5GHNXYw5+pgmnzd1HiMQbQ==} | |
| 1543 | '@clerk/electron-passkeys-darwin-arm64@0.0.1-snapshot.v20260619001138': | |
| 1544 | resolution: {integrity: sha512-dbQ/0ZtfDQgYKCSzu3AMxTnGSrdZxulurZMT4Jpvin48Etc27PdcT7VvwOGIa7R+Ab8yMeaoLJbfwHTZv35F+Q==} | |
| 1545 | cpu: [arm64] | |
| 1546 | os: [darwin] | |
| 1547 | ||
| 1548 | '@clerk/electron-passkeys-darwin-x64@0.0.1-snapshot.v20260619001138': | |
| 1549 | resolution: {integrity: sha512-g9tbni7yKIJ/Xpollm25Gf7YLyFP24VyqRHcGDnEsHC1tIffG41spjLr10NgsBoRvFLMlaQJMSrfpjVOpBhAjw==} | |
| 1550 | cpu: [x64] | |
| 1551 | os: [darwin] | |
| 1552 | ||
| 1553 | '@clerk/electron-passkeys-win32-arm64-msvc@0.0.1-snapshot.v20260619001138': | |
| 1554 | resolution: {integrity: sha512-mFDQ1vQ9dLIUrnjNGhIGxqyK0iiym919gYDPO3orYEcICdEiE8xZyEbCtKSVaeX6RWGJAcqFzCxbLVG6V+1k4Q==} | |
| 1555 | cpu: [arm64] | |
| 1556 | os: [win32] | |
| 1557 | ||
| 1558 | '@clerk/electron-passkeys-win32-x64-msvc@0.0.1-snapshot.v20260619001138': | |
| 1559 | resolution: {integrity: sha512-IwkLw+d73bd7YJPRR8LAkqP42VIIpbJXCCbs5eVKbmam9M0vSUyeLWEtSiWUEypmLK13xv+7316K4GIA2tmDGQ==} | |
| 1560 | cpu: [x64] | |
| 1561 | os: [win32] | |
| 1562 | ||
| 1563 | '@clerk/electron-passkeys@0.0.1-snapshot.v20260619001138': | |
| 1564 | resolution: {integrity: sha512-diU5Q9Nx+30mesrLFOmr0OCnmxE0ogUHXktZBTQx2nQvZb/n2UGOpGNLbY1p9edKMcSc5LfJEsQ8NogfLBBvPg==} | |
| 1565 | engines: {node: '>=20.9.0'} | |
| 1566 | ||
| 1567 | '@clerk/electron@0.0.1-snapshot.v20260619001138': | |
| 1568 | resolution: {integrity: sha512-wrBEdMMRqhMF4a7aQpZaBXKJfONIpaYLcgBl0m1b2r+Xg4yuZ47YUoDxhI2Ksvbx2KQPd4i9HP0enL6gEcoqfA==} | |
| 1529 | 1569 | engines: {node: '>=20.9.0'} |
| 1530 | 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 | 1589 | expo: '>=53 <57' |
| 1533 | 1590 | expo-apple-authentication: '>=7.0.0' |
| 1534 | 1591 | expo-auth-session: '>=5' |
| ... | ... | @@ -1560,15 +1617,15 @@ packages: |
| 1560 | 1617 | react-dom: |
| 1561 | 1618 | optional: true |
| 1562 | 1619 | |
| 1563 | '@clerk/react@6.9.0': | |
| 1564 | resolution: {integrity: sha512-M0QGyGS732tYBXeG+28UgElXM2TfoSZ+4mWGisC8yxJX8NjH4hEPJTAQuZmYRLNaCyGQCuzjYVQiQRC+GbDtmA==} | |
| 1620 | '@clerk/react@6.10.4-snapshot.v20260619001138': | |
| 1621 | resolution: {integrity: sha512-Z7Otjly14SoxadMmk8d9ZdbaXU0me9B1zGdCtnNIcQ7X2GCbeyKm/lOM27IzWYXZKO6o+sXlqsq9A9tcxet5nA==} | |
| 1565 | 1622 | engines: {node: '>=20.9.0'} |
| 1566 | 1623 | peerDependencies: |
| 1567 | 1624 | react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0 |
| 1568 | 1625 | react-dom: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0 |
| 1569 | 1626 | |
| 1570 | '@clerk/shared@4.17.0': | |
| 1571 | resolution: {integrity: sha512-YeQ+6zDmqyor1mPHjZx18j+LssL6Pobvid8hb7HQMioSo8sGDBEVi/Z12bs+gUhe9KbdP+ygHsKOqqeGAPuPZA==} | |
| 1627 | '@clerk/shared@4.19.2-snapshot.v20260619001138': | |
| 1628 | resolution: {integrity: sha512-NAIz0L6+CaRrYn0DoXclUP1R0g6C2ljOzHogQ3rx9/SWUVpfaYX2lxPhURU89xvPoOGBZQb2xwk6xFh/7cjJfQ==} | |
| 1572 | 1629 | engines: {node: '>=20.9.0'} |
| 1573 | 1630 | peerDependencies: |
| 1574 | 1631 | react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0 |
| ... | ... | @@ -4962,6 +5019,14 @@ packages: |
| 4962 | 5019 | ajv: |
| 4963 | 5020 | optional: true |
| 4964 | 5021 | |
| 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 | 5030 | ajv-formats@3.0.1: |
| 4966 | 5031 | resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} |
| 4967 | 5032 | peerDependencies: |
| ... | ... | @@ -5127,6 +5192,10 @@ packages: |
| 5127 | 5192 | resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} |
| 5128 | 5193 | engines: {node: '>= 4.0.0'} |
| 5129 | 5194 | |
| 5195 | atomically@1.7.0: | |
| 5196 | resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} | |
| 5197 | engines: {node: '>=10.12.0'} | |
| 5198 | ||
| 5130 | 5199 | auto-bind@5.0.1: |
| 5131 | 5200 | resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} |
| 5132 | 5201 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} |
| ... | ... | @@ -5535,6 +5604,10 @@ packages: |
| 5535 | 5604 | concat-map@0.0.1: |
| 5536 | 5605 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} |
| 5537 | 5606 | |
| 5607 | conf@10.2.0: | |
| 5608 | resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} | |
| 5609 | engines: {node: '>=12'} | |
| 5610 | ||
| 5538 | 5611 | connect@3.7.0: |
| 5539 | 5612 | resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} |
| 5540 | 5613 | engines: {node: '>= 0.10.0'} |
| ... | ... | @@ -5643,6 +5716,10 @@ packages: |
| 5643 | 5716 | resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==} |
| 5644 | 5717 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} |
| 5645 | 5718 | |
| 5719 | debounce-fn@4.0.0: | |
| 5720 | resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} | |
| 5721 | engines: {node: '>=10'} | |
| 5722 | ||
| 5646 | 5723 | debug@2.6.9: |
| 5647 | 5724 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} |
| 5648 | 5725 | peerDependencies: |
| ... | ... | @@ -5782,6 +5859,10 @@ packages: |
| 5782 | 5859 | domutils@3.2.2: |
| 5783 | 5860 | resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} |
| 5784 | 5861 | |
| 5862 | dot-prop@6.0.1: | |
| 5863 | resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} | |
| 5864 | engines: {node: '>=10'} | |
| 5865 | ||
| 5785 | 5866 | dotenv-expand@11.0.7: |
| 5786 | 5867 | resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} |
| 5787 | 5868 | engines: {node: '>=12'} |
| ... | ... | @@ -5940,6 +6021,9 @@ packages: |
| 5940 | 6021 | electron-publish@26.8.1: |
| 5941 | 6022 | resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==} |
| 5942 | 6023 | |
| 6024 | electron-store@8.2.0: | |
| 6025 | resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==} | |
| 6026 | ||
| 5943 | 6027 | electron-to-chromium@1.5.364: |
| 5944 | 6028 | resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} |
| 5945 | 6029 | |
| ... | ... | @@ -6488,6 +6572,10 @@ packages: |
| 6488 | 6572 | find-my-way-ts@0.1.6: |
| 6489 | 6573 | resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} |
| 6490 | 6574 | |
| 6575 | find-up@3.0.0: | |
| 6576 | resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} | |
| 6577 | engines: {node: '>=6'} | |
| 6578 | ||
| 6491 | 6579 | flattie@1.1.1: |
| 6492 | 6580 | resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} |
| 6493 | 6581 | engines: {node: '>=8'} |
| ... | ... | @@ -6917,6 +7005,10 @@ packages: |
| 6917 | 7005 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} |
| 6918 | 7006 | engines: {node: '>=0.12.0'} |
| 6919 | 7007 | |
| 7008 | is-obj@2.0.0: | |
| 7009 | resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} | |
| 7010 | engines: {node: '>=8'} | |
| 7011 | ||
| 6920 | 7012 | is-plain-obj@4.1.0: |
| 6921 | 7013 | resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} |
| 6922 | 7014 | engines: {node: '>=12'} |
| ... | ... | @@ -7040,6 +7132,9 @@ packages: |
| 7040 | 7132 | json-schema-traverse@1.0.0: |
| 7041 | 7133 | resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} |
| 7042 | 7134 | |
| 7135 | json-schema-typed@7.0.3: | |
| 7136 | resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} | |
| 7137 | ||
| 7043 | 7138 | json-schema-typed@8.0.2: |
| 7044 | 7139 | resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} |
| 7045 | 7140 | |
| ... | ... | @@ -7323,6 +7418,10 @@ packages: |
| 7323 | 7418 | resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} |
| 7324 | 7419 | engines: {node: '>= 12.0.0'} |
| 7325 | 7420 | |
| 7421 | locate-path@3.0.0: | |
| 7422 | resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} | |
| 7423 | engines: {node: '>=6'} | |
| 7424 | ||
| 7326 | 7425 | lodash.debounce@4.0.8: |
| 7327 | 7426 | resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} |
| 7328 | 7427 | |
| ... | ... | @@ -7677,6 +7776,10 @@ packages: |
| 7677 | 7776 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} |
| 7678 | 7777 | engines: {node: '>=6'} |
| 7679 | 7778 | |
| 7779 | mimic-fn@3.1.0: | |
| 7780 | resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} | |
| 7781 | engines: {node: '>=8'} | |
| 7782 | ||
| 7680 | 7783 | mimic-function@5.0.1: |
| 7681 | 7784 | resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} |
| 7682 | 7785 | engines: {node: '>=18'} |
| ... | ... | @@ -7985,6 +8088,10 @@ packages: |
| 7985 | 8088 | resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} |
| 7986 | 8089 | engines: {node: '>=8'} |
| 7987 | 8090 | |
| 8091 | p-limit@2.3.0: | |
| 8092 | resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} | |
| 8093 | engines: {node: '>=6'} | |
| 8094 | ||
| 7988 | 8095 | p-limit@3.1.0: |
| 7989 | 8096 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} |
| 7990 | 8097 | engines: {node: '>=10'} |
| ... | ... | @@ -7993,6 +8100,10 @@ packages: |
| 7993 | 8100 | resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} |
| 7994 | 8101 | engines: {node: '>=20'} |
| 7995 | 8102 | |
| 8103 | p-locate@3.0.0: | |
| 8104 | resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} | |
| 8105 | engines: {node: '>=6'} | |
| 8106 | ||
| 7996 | 8107 | p-queue@9.3.0: |
| 7997 | 8108 | resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==} |
| 7998 | 8109 | engines: {node: '>=20'} |
| ... | ... | @@ -8001,6 +8112,10 @@ packages: |
| 8001 | 8112 | resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} |
| 8002 | 8113 | engines: {node: '>=20'} |
| 8003 | 8114 | |
| 8115 | p-try@2.2.0: | |
| 8116 | resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} | |
| 8117 | engines: {node: '>=6'} | |
| 8118 | ||
| 8004 | 8119 | package-manager-detector@1.6.0: |
| 8005 | 8120 | resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} |
| 8006 | 8121 | |
| ... | ... | @@ -8031,6 +8146,10 @@ packages: |
| 8031 | 8146 | path-browserify@1.0.1: |
| 8032 | 8147 | resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} |
| 8033 | 8148 | |
| 8149 | path-exists@3.0.0: | |
| 8150 | resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} | |
| 8151 | engines: {node: '>=4'} | |
| 8152 | ||
| 8034 | 8153 | path-expression-matcher@1.5.0: |
| 8035 | 8154 | resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} |
| 8036 | 8155 | engines: {node: '>=14.0.0'} |
| ... | ... | @@ -8141,6 +8260,10 @@ packages: |
| 8141 | 8260 | resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} |
| 8142 | 8261 | engines: {node: '>=16.20.0'} |
| 8143 | 8262 | |
| 8263 | pkg-up@3.1.0: | |
| 8264 | resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} | |
| 8265 | engines: {node: '>=8'} | |
| 8266 | ||
| 8144 | 8267 | playwright-core@1.60.0: |
| 8145 | 8268 | resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} |
| 8146 | 8269 | engines: {node: '>=18'} |
| ... | ... | @@ -9211,6 +9334,10 @@ packages: |
| 9211 | 9334 | resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} |
| 9212 | 9335 | engines: {node: '>=8'} |
| 9213 | 9336 | |
| 9337 | type-fest@2.19.0: | |
| 9338 | resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} | |
| 9339 | engines: {node: '>=12.20'} | |
| 9340 | ||
| 9214 | 9341 | type-fest@5.7.0: |
| 9215 | 9342 | resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} |
| 9216 | 9343 | engines: {node: '>=20'} |
| ... | ... | @@ -10712,10 +10839,10 @@ snapshots: |
| 10712 | 10839 | optionalDependencies: |
| 10713 | 10840 | '@types/react': 19.2.16 |
| 10714 | 10841 | |
| 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 | 10843 | dependencies: |
| 10717 | 10844 | 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) | |
| 10719 | 10846 | |
| 10720 | 10847 | '@capsizecss/unpack@4.0.0': |
| 10721 | 10848 | dependencies: |
| ... | ... | @@ -10744,18 +10871,18 @@ snapshots: |
| 10744 | 10871 | fast-wrap-ansi: 0.2.2 |
| 10745 | 10872 | sisteransi: 1.0.5 |
| 10746 | 10873 | |
| 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 | 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 | 10877 | standardwebhooks: 1.0.0 |
| 10751 | 10878 | tslib: 2.8.1 |
| 10752 | 10879 | transitivePeerDependencies: |
| 10753 | 10880 | - react |
| 10754 | 10881 | - react-dom |
| 10755 | 10882 | |
| 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 | 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 | 10886 | '@stripe/stripe-js': 5.6.0 |
| 10760 | 10887 | '@swc/helpers': 0.5.21 |
| 10761 | 10888 | '@tanstack/query-core': 5.100.14 |
| ... | ... | @@ -10770,9 +10897,9 @@ snapshots: |
| 10770 | 10897 | - react |
| 10771 | 10898 | - react-dom |
| 10772 | 10899 | |
| 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 | 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 | 10903 | '@stripe/stripe-js': 5.6.0 |
| 10777 | 10904 | '@swc/helpers': 0.5.21 |
| 10778 | 10905 | '@tanstack/query-core': 5.100.14 |
| ... | ... | @@ -10787,40 +10914,72 @@ snapshots: |
| 10787 | 10914 | - react |
| 10788 | 10915 | - react-dom |
| 10789 | 10916 | |
| 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 | 10950 | dependencies: |
| 10792 | '@clerk/clerk-js': 6.16.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10793 | '@clerk/react': 6.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10794 | '@clerk/shared': 4.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10951 | '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10952 | '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10953 | '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | |
| 10795 | 10954 | 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 | 10956 | react: 19.2.3 |
| 10798 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 10799 | react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 10957 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 10958 | react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 10800 | 10959 | tslib: 2.8.1 |
| 10801 | 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) | |
| 10803 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 10961 | expo-auth-session: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 10962 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 10804 | 10963 | expo-crypto: 56.0.4(expo@56.0.8) |
| 10805 | 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 | 10966 | react-dom: 19.2.3(react@19.2.3) |
| 10808 | 10967 | |
| 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 | 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 | 10971 | react: 19.2.3 |
| 10813 | 10972 | react-dom: 19.2.3(react@19.2.3) |
| 10814 | 10973 | tslib: 2.8.1 |
| 10815 | 10974 | |
| 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 | 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 | 10978 | react: 19.2.6 |
| 10820 | 10979 | react-dom: 19.2.6(react@19.2.6) |
| 10821 | 10980 | tslib: 2.8.1 |
| 10822 | 10981 | |
| 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 | 10983 | dependencies: |
| 10825 | 10984 | '@tanstack/query-core': 5.100.14 |
| 10826 | 10985 | dequal: 2.0.3 |
| ... | ... | @@ -10830,7 +10989,7 @@ snapshots: |
| 10830 | 10989 | react: 19.2.3 |
| 10831 | 10990 | react-dom: 19.2.3(react@19.2.3) |
| 10832 | 10991 | |
| 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 | 10993 | dependencies: |
| 10835 | 10994 | '@tanstack/query-core': 5.100.14 |
| 10836 | 10995 | dequal: 2.0.3 |
| ... | ... | @@ -11370,7 +11529,7 @@ snapshots: |
| 11370 | 11529 | |
| 11371 | 11530 | '@expo-google-fonts/material-symbols@0.4.38': {} |
| 11372 | 11531 | |
| 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 | 11533 | dependencies: |
| 11375 | 11534 | '@expo/code-signing-certificates': 0.0.6 |
| 11376 | 11535 | '@expo/config': 56.0.9(typescript@6.0.3) |
| ... | ... | @@ -11380,7 +11539,7 @@ snapshots: |
| 11380 | 11539 | '@expo/image-utils': 0.10.1(typescript@6.0.3) |
| 11381 | 11540 | '@expo/inline-modules': 0.0.10(typescript@6.0.3) |
| 11382 | 11541 | '@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 | 11543 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 11385 | 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 | 11545 | '@expo/metro-file-map': 56.0.3 |
| ... | ... | @@ -11405,7 +11564,7 @@ snapshots: |
| 11405 | 11564 | connect: 3.7.0 |
| 11406 | 11565 | debug: 4.4.3 |
| 11407 | 11566 | 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 | 11568 | expo-server: 56.0.4 |
| 11410 | 11569 | fetch-nodeshim: 0.4.10 |
| 11411 | 11570 | getenv: 2.0.0 |
| ... | ... | @@ -11431,8 +11590,8 @@ snapshots: |
| 11431 | 11590 | ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 11432 | 11591 | zod: 3.25.76 |
| 11433 | 11592 | optionalDependencies: |
| 11434 | expo-router: 56.2.8(c021de11d02907bd585610408f5252e8) | |
| 11435 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 11593 | expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310) | |
| 11594 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 11436 | 11595 | transitivePeerDependencies: |
| 11437 | 11596 | - '@expo/dom-webview' |
| 11438 | 11597 | - '@expo/metro-runtime' |
| ... | ... | @@ -11494,18 +11653,18 @@ snapshots: |
| 11494 | 11653 | transitivePeerDependencies: |
| 11495 | 11654 | - supports-color |
| 11496 | 11655 | |
| 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 | 11657 | dependencies: |
| 11499 | 11658 | chalk: 4.1.2 |
| 11500 | 11659 | optionalDependencies: |
| 11501 | 11660 | 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) | |
| 11503 | 11662 | |
| 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 | 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 | 11666 | 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) | |
| 11509 | 11668 | |
| 11510 | 11669 | '@expo/env@2.3.0': |
| 11511 | 11670 | dependencies: |
| ... | ... | @@ -11566,13 +11725,13 @@ snapshots: |
| 11566 | 11725 | - supports-color |
| 11567 | 11726 | - typescript |
| 11568 | 11727 | |
| 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 | 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 | 11731 | 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 | 11733 | 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 | 11735 | stacktrace-parser: 0.1.11 |
| 11577 | 11736 | |
| 11578 | 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 | 11761 | postcss: 8.5.15 |
| 11603 | 11762 | resolve-from: 5.0.0 |
| 11604 | 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 | 11765 | transitivePeerDependencies: |
| 11607 | 11766 | - bufferutil |
| 11608 | 11767 | - supports-color |
| ... | ... | @@ -11620,14 +11779,14 @@ snapshots: |
| 11620 | 11779 | transitivePeerDependencies: |
| 11621 | 11780 | - supports-color |
| 11622 | 11781 | |
| 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 | 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 | 11785 | 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 | 11787 | pretty-format: 29.7.0 |
| 11629 | 11788 | 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 | 11790 | stacktrace-parser: 0.1.11 |
| 11632 | 11791 | whatwg-fetch: 3.6.20 |
| 11633 | 11792 | optionalDependencies: |
| ... | ... | @@ -11702,14 +11861,14 @@ snapshots: |
| 11702 | 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 | 11862 | dependencies: |
| 11704 | 11863 | debug: 4.4.3 |
| 11705 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 11706 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 11707 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11864 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 11865 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 11866 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11708 | 11867 | expo-server: 56.0.4 |
| 11709 | 11868 | react: 19.2.3 |
| 11710 | 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) | |
| 11712 | expo-router: 56.2.8(c021de11d02907bd585610408f5252e8) | |
| 11870 | '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11871 | expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310) | |
| 11713 | 11872 | react-dom: 19.2.3(react@19.2.3) |
| 11714 | 11873 | transitivePeerDependencies: |
| 11715 | 11874 | - supports-color |
| ... | ... | @@ -11724,18 +11883,18 @@ snapshots: |
| 11724 | 11883 | |
| 11725 | 11884 | '@expo/sudo-prompt@9.3.2': {} |
| 11726 | 11885 | |
| 11727 | '@expo/ui@56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)': | |
| 11886 | '@expo/ui@56.0.15(961c4aa6f32829b318e3c87ef20ad401)': | |
| 11728 | 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 | 11889 | 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 | 11891 | sf-symbols-typescript: 2.2.0 |
| 11733 | 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 | 11893 | optionalDependencies: |
| 11735 | 11894 | '@babel/core': 7.29.7 |
| 11736 | 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) | |
| 11738 | react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11896 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11897 | react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 11739 | 11898 | transitivePeerDependencies: |
| 11740 | 11899 | - '@types/react' |
| 11741 | 11900 | - '@types/react-dom' |
| ... | ... | @@ -11996,13 +12155,13 @@ snapshots: |
| 11996 | 12155 | dependencies: |
| 11997 | 12156 | jsbi: 4.3.2 |
| 11998 | 12157 | |
| 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 | 12159 | dependencies: |
| 12001 | 12160 | react: 19.2.3 |
| 12002 | 12161 | use-sync-external-store: 1.6.0(react@19.2.3) |
| 12003 | 12162 | optionalDependencies: |
| 12004 | 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) | |
| 12006 | 12165 | |
| 12007 | 12166 | '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': |
| 12008 | 12167 | dependencies: |
| ... | ... | @@ -12825,15 +12984,15 @@ snapshots: |
| 12825 | 12984 | prompts: 2.4.2 |
| 12826 | 12985 | tinyexec: 1.2.4 |
| 12827 | 12986 | |
| 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 | 12988 | dependencies: |
| 12830 | 12989 | 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) | |
| 12832 | 12991 | |
| 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 | 12993 | dependencies: |
| 12835 | 12994 | 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) | |
| 12837 | 12996 | |
| 12838 | 12997 | '@react-native/assets-registry@0.85.3': {} |
| 12839 | 12998 | |
| ... | ... | @@ -12893,7 +13052,7 @@ snapshots: |
| 12893 | 13052 | tinyglobby: 0.2.17 |
| 12894 | 13053 | yargs: 17.7.2 |
| 12895 | 13054 | |
| 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 | 13056 | dependencies: |
| 12898 | 13057 | '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 12899 | 13058 | debug: 4.4.3 |
| ... | ... | @@ -12903,7 +13062,7 @@ snapshots: |
| 12903 | 13062 | metro-core: 0.84.4 |
| 12904 | 13063 | semver: 7.8.1 |
| 12905 | 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 | 13066 | transitivePeerDependencies: |
| 12908 | 13067 | - bufferutil |
| 12909 | 13068 | - supports-color |
| ... | ... | @@ -12951,7 +13110,7 @@ snapshots: |
| 12951 | 13110 | transitivePeerDependencies: |
| 12952 | 13111 | - supports-color |
| 12953 | 13112 | |
| 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 | 13114 | dependencies: |
| 12956 | 13115 | '@react-native/js-polyfills': 0.85.3 |
| 12957 | 13116 | '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7) |
| ... | ... | @@ -12959,16 +13118,18 @@ snapshots: |
| 12959 | 13118 | metro-runtime: 0.84.4 |
| 12960 | 13119 | transitivePeerDependencies: |
| 12961 | 13120 | - '@babel/core' |
| 13121 | - bufferutil | |
| 12962 | 13122 | - supports-color |
| 13123 | - utf-8-validate | |
| 12963 | 13124 | |
| 12964 | 13125 | '@react-native/normalize-colors@0.85.3': {} |
| 12965 | 13126 | |
| 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 | 13128 | dependencies: |
| 12968 | 13129 | invariant: 2.2.4 |
| 12969 | 13130 | nullthrows: 1.1.1 |
| 12970 | 13131 | 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 | 13133 | optionalDependencies: |
| 12973 | 13134 | '@types/react': 19.2.16 |
| 12974 | 13135 | |
| ... | ... | @@ -13381,15 +13542,15 @@ snapshots: |
| 13381 | 13542 | dependencies: |
| 13382 | 13543 | defer-to-connect: 2.0.1 |
| 13383 | 13544 | |
| 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 | 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) | |
| 13387 | expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 13547 | expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3) | |
| 13548 | expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 13388 | 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 | 13551 | react: 19.2.3 |
| 13391 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 13392 | react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 13552 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 13553 | react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 13393 | 13554 | |
| 13394 | 13555 | '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {} |
| 13395 | 13556 | |
| ... | ... | @@ -14060,6 +14221,10 @@ snapshots: |
| 14060 | 14221 | optionalDependencies: |
| 14061 | 14222 | ajv: 8.20.0 |
| 14062 | 14223 | |
| 14224 | ajv-formats@2.1.1(ajv@8.20.0): | |
| 14225 | optionalDependencies: | |
| 14226 | ajv: 8.20.0 | |
| 14227 | ||
| 14063 | 14228 | ajv-formats@3.0.1(ajv@8.20.0): |
| 14064 | 14229 | optionalDependencies: |
| 14065 | 14230 | ajv: 8.20.0 |
| ... | ... | @@ -14355,6 +14520,8 @@ snapshots: |
| 14355 | 14520 | |
| 14356 | 14521 | at-least-node@1.0.0: {} |
| 14357 | 14522 | |
| 14523 | atomically@1.7.0: {} | |
| 14524 | ||
| 14358 | 14525 | auto-bind@5.0.1: {} |
| 14359 | 14526 | |
| 14360 | 14527 | aws-ssl-profiles@1.1.2: {} |
| ... | ... | @@ -14459,8 +14626,8 @@ snapshots: |
| 14459 | 14626 | react-refresh: 0.14.2 |
| 14460 | 14627 | optionalDependencies: |
| 14461 | 14628 | '@babel/runtime': 7.29.7 |
| 14462 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 14463 | expo-widgets: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584) | |
| 14629 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 14630 | expo-widgets: 56.0.16(961c4aa6f32829b318e3c87ef20ad401) | |
| 14464 | 14631 | transitivePeerDependencies: |
| 14465 | 14632 | - '@babel/core' |
| 14466 | 14633 | - supports-color |
| ... | ... | @@ -14821,6 +14988,19 @@ snapshots: |
| 14821 | 14988 | |
| 14822 | 14989 | concat-map@0.0.1: {} |
| 14823 | 14990 | |
| 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 | 15004 | connect@3.7.0: |
| 14825 | 15005 | dependencies: |
| 14826 | 15006 | debug: 2.6.9 |
| ... | ... | @@ -14923,6 +15103,10 @@ snapshots: |
| 14923 | 15103 | |
| 14924 | 15104 | culori@4.0.2: {} |
| 14925 | 15105 | |
| 15106 | debounce-fn@4.0.0: | |
| 15107 | dependencies: | |
| 15108 | mimic-fn: 3.1.0 | |
| 15109 | ||
| 14926 | 15110 | debug@2.6.9: |
| 14927 | 15111 | dependencies: |
| 14928 | 15112 | ms: 2.0.0 |
| ... | ... | @@ -15052,6 +15236,10 @@ snapshots: |
| 15052 | 15236 | domelementtype: 2.3.0 |
| 15053 | 15237 | domhandler: 5.0.3 |
| 15054 | 15238 | |
| 15239 | dot-prop@6.0.1: | |
| 15240 | dependencies: | |
| 15241 | is-obj: 2.0.0 | |
| 15242 | ||
| 15055 | 15243 | dotenv-expand@11.0.7: |
| 15056 | 15244 | dependencies: |
| 15057 | 15245 | dotenv: 16.6.1 |
| ... | ... | @@ -15142,6 +15330,11 @@ snapshots: |
| 15142 | 15330 | transitivePeerDependencies: |
| 15143 | 15331 | - supports-color |
| 15144 | 15332 | |
| 15333 | electron-store@8.2.0: | |
| 15334 | dependencies: | |
| 15335 | conf: 10.2.0 | |
| 15336 | type-fest: 2.19.0 | |
| 15337 | ||
| 15145 | 15338 | electron-to-chromium@1.5.364: {} |
| 15146 | 15339 | |
| 15147 | 15340 | electron-updater@6.8.3: |
| ... | ... | @@ -15325,29 +15518,29 @@ snapshots: |
| 15325 | 15518 | |
| 15326 | 15519 | expo-application@56.0.3(expo@56.0.8): |
| 15327 | 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) | |
| 15329 | 15522 | |
| 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 | 15524 | dependencies: |
| 15332 | 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) | |
| 15334 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15526 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15527 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15335 | 15528 | 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 | 15530 | transitivePeerDependencies: |
| 15338 | 15531 | - supports-color |
| 15339 | 15532 | - typescript |
| 15340 | 15533 | |
| 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 | 15535 | dependencies: |
| 15343 | 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 | 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) | |
| 15347 | expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15539 | expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15540 | expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15348 | 15541 | invariant: 2.2.4 |
| 15349 | 15542 | 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 | 15544 | transitivePeerDependencies: |
| 15352 | 15545 | - expo |
| 15353 | 15546 | - supports-color |
| ... | ... | @@ -15355,119 +15548,119 @@ snapshots: |
| 15355 | 15548 | expo-build-properties@56.0.16(expo@56.0.8): |
| 15356 | 15549 | dependencies: |
| 15357 | 15550 | '@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 | 15552 | resolve-from: 5.0.0 |
| 15360 | 15553 | semver: 7.8.1 |
| 15361 | 15554 | |
| 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 | 15556 | dependencies: |
| 15364 | 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 | 15559 | 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 | 15561 | transitivePeerDependencies: |
| 15369 | 15562 | - '@types/emscripten' |
| 15370 | 15563 | |
| 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 | 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 | 15567 | 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) | |
| 15376 | 15569 | |
| 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 | 15571 | dependencies: |
| 15379 | 15572 | '@expo/env': 2.3.0 |
| 15380 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 15381 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15573 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15574 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15382 | 15575 | transitivePeerDependencies: |
| 15383 | 15576 | - supports-color |
| 15384 | 15577 | |
| 15385 | 15578 | expo-crypto@56.0.4(expo@56.0.8): |
| 15386 | 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) | |
| 15388 | 15581 | |
| 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 | 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) | |
| 15392 | expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15393 | expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15584 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15585 | expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15586 | expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15394 | 15587 | expo-dev-menu-interface: 56.0.1(expo@56.0.8) |
| 15395 | 15588 | expo-manifests: 56.0.4(expo@56.0.8) |
| 15396 | 15589 | expo-updates-interface: 56.0.2(expo@56.0.8) |
| 15397 | 15590 | transitivePeerDependencies: |
| 15398 | 15591 | - react-native |
| 15399 | 15592 | |
| 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 | 15594 | dependencies: |
| 15402 | 15595 | '@expo/schema-utils': 56.0.1 |
| 15403 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 15404 | expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15596 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15597 | expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15405 | 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) | |
| 15407 | 15600 | |
| 15408 | 15601 | expo-dev-menu-interface@56.0.1(expo@56.0.8): |
| 15409 | 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) | |
| 15411 | 15604 | |
| 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 | 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 | 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) | |
| 15417 | 15610 | |
| 15418 | 15611 | expo-eas-client@56.0.1: {} |
| 15419 | 15612 | |
| 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 | 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) | |
| 15423 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15615 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15616 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15424 | 15617 | |
| 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 | 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 | 15621 | fontfaceobserver: 2.3.0 |
| 15429 | 15622 | 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) | |
| 15431 | 15624 | |
| 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 | 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 | 15628 | 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) | |
| 15437 | 15630 | |
| 15438 | 15631 | expo-haptics@56.0.3(expo@56.0.8): |
| 15439 | 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) | |
| 15441 | 15634 | |
| 15442 | 15635 | expo-image-loader@56.0.3(expo@56.0.8): |
| 15443 | 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) | |
| 15445 | 15638 | |
| 15446 | 15639 | expo-image-picker@56.0.15(expo@56.0.8): |
| 15447 | 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 | 15642 | expo-image-loader: 56.0.3(expo@56.0.8) |
| 15450 | 15643 | |
| 15451 | 15644 | expo-json-utils@56.0.0: {} |
| 15452 | 15645 | |
| 15453 | 15646 | expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3): |
| 15454 | 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 | 15649 | react: 19.2.3 |
| 15457 | 15650 | |
| 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 | 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 | 15654 | invariant: 2.2.4 |
| 15462 | 15655 | 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 | 15657 | transitivePeerDependencies: |
| 15465 | 15658 | - expo |
| 15466 | 15659 | - supports-color |
| 15467 | 15660 | |
| 15468 | 15661 | expo-manifests@56.0.4(expo@56.0.8): |
| 15469 | 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 | 15664 | expo-json-utils: 56.0.0 |
| 15472 | 15665 | |
| 15473 | 15666 | expo-modules-autolinking@56.0.14(typescript@6.0.3): |
| ... | ... | @@ -15480,66 +15673,66 @@ snapshots: |
| 15480 | 15673 | - supports-color |
| 15481 | 15674 | - typescript |
| 15482 | 15675 | |
| 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 | 15677 | dependencies: |
| 15485 | 15678 | '@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 | 15680 | invariant: 2.2.4 |
| 15488 | 15681 | 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 | 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) | |
| 15492 | 15685 | |
| 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 | 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) | |
| 15496 | 15689 | |
| 15497 | 15690 | expo-network@56.0.5(expo@56.0.8)(react@19.2.3): |
| 15498 | 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 | 15693 | react: 19.2.3 |
| 15501 | 15694 | |
| 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 | 15696 | dependencies: |
| 15504 | 15697 | '@expo/image-utils': 0.10.1(typescript@6.0.3) |
| 15505 | 15698 | abort-controller: 3.0.0 |
| 15506 | 15699 | 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 | 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 | 15703 | 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 | 15705 | transitivePeerDependencies: |
| 15513 | 15706 | - supports-color |
| 15514 | 15707 | - typescript |
| 15515 | 15708 | |
| 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 | 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 | 15712 | 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) | |
| 15521 | 15714 | |
| 15522 | expo-router@56.2.8(c021de11d02907bd585610408f5252e8): | |
| 15715 | expo-router@56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310): | |
| 15523 | 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) | |
| 15525 | '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15717 | '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15718 | '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15526 | 15719 | '@expo/schema-utils': 56.0.1 |
| 15527 | '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) | |
| 15720 | '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | |
| 15528 | 15721 | '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3) |
| 15529 | 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 | 15724 | '@testing-library/jest-dom': 6.9.1 |
| 15532 | 15725 | '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) |
| 15533 | 15726 | client-only: 0.0.1 |
| 15534 | 15727 | color: 4.2.3 |
| 15535 | 15728 | debug: 4.4.3 |
| 15536 | 15729 | escape-string-regexp: 4.0.0 |
| 15537 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 15538 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15539 | expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15540 | expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15730 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15731 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15732 | expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15733 | expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15541 | 15734 | 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 | 15736 | fast-deep-equal: 3.1.3 |
| 15544 | 15737 | invariant: 2.2.4 |
| 15545 | 15738 | nanoid: 3.3.12 |
| ... | ... | @@ -15547,18 +15740,18 @@ snapshots: |
| 15547 | 15740 | react: 19.2.3 |
| 15548 | 15741 | react-fast-compare: 3.2.2 |
| 15549 | 15742 | react-is: 19.2.7 |
| 15550 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15551 | react-native-drawer-layout: 4.2.4(react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15552 | react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15553 | react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15743 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15744 | react-native-drawer-layout: 4.2.4(0e9729601f58a7a7ae26c76fe6017455) | |
| 15745 | react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15746 | react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15554 | 15747 | server-only: 0.0.1 |
| 15555 | 15748 | sf-symbols-typescript: 2.2.0 |
| 15556 | 15749 | shallowequal: 1.1.0 |
| 15557 | 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 | 15751 | optionalDependencies: |
| 15559 | 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) | |
| 15561 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15753 | react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15754 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15562 | 15755 | transitivePeerDependencies: |
| 15563 | 15756 | - '@babel/core' |
| 15564 | 15757 | - '@testing-library/dom' |
| ... | ... | @@ -15570,7 +15763,7 @@ snapshots: |
| 15570 | 15763 | |
| 15571 | 15764 | expo-secure-store@56.0.4(expo@56.0.8): |
| 15572 | 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) | |
| 15574 | 15767 | |
| 15575 | 15768 | expo-server@56.0.4: {} |
| 15576 | 15769 | |
| ... | ... | @@ -15578,7 +15771,7 @@ snapshots: |
| 15578 | 15771 | dependencies: |
| 15579 | 15772 | '@expo/config-plugins': 56.0.8(typescript@6.0.3) |
| 15580 | 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 | 15775 | xml2js: 0.6.0 |
| 15583 | 15776 | transitivePeerDependencies: |
| 15584 | 15777 | - supports-color |
| ... | ... | @@ -15586,20 +15779,20 @@ snapshots: |
| 15586 | 15779 | |
| 15587 | 15780 | expo-structured-headers@56.0.0: {} |
| 15588 | 15781 | |
| 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 | 15783 | dependencies: |
| 15591 | 15784 | '@expo-google-fonts/material-symbols': 0.4.38 |
| 15592 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 15593 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15785 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15786 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15594 | 15787 | 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 | 15789 | sf-symbols-typescript: 2.2.0 |
| 15597 | 15790 | |
| 15598 | 15791 | expo-updates-interface@56.0.2(expo@56.0.8): |
| 15599 | 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) | |
| 15601 | 15794 | |
| 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 | 15796 | dependencies: |
| 15604 | 15797 | '@expo/code-signing-certificates': 0.0.6 |
| 15605 | 15798 | '@expo/plist': 0.7.0 |
| ... | ... | @@ -15607,7 +15800,7 @@ snapshots: |
| 15607 | 15800 | arg: 4.1.3 |
| 15608 | 15801 | chalk: 4.1.2 |
| 15609 | 15802 | 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 | 15804 | expo-eas-client: 56.0.1 |
| 15612 | 15805 | expo-manifests: 56.0.4(expo@56.0.8) |
| 15613 | 15806 | expo-structured-headers: 56.0.0 |
| ... | ... | @@ -15617,25 +15810,25 @@ snapshots: |
| 15617 | 15810 | ignore: 5.3.2 |
| 15618 | 15811 | nullthrows: 1.1.1 |
| 15619 | 15812 | 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 | 15814 | resolve-from: 5.0.0 |
| 15622 | 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 | 15817 | transitivePeerDependencies: |
| 15625 | 15818 | - supports-color |
| 15626 | 15819 | |
| 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 | 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) | |
| 15630 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15822 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15823 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 15631 | 15824 | |
| 15632 | expo-widgets@56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584): | |
| 15825 | expo-widgets@56.0.16(961c4aa6f32829b318e3c87ef20ad401): | |
| 15633 | 15826 | dependencies: |
| 15634 | 15827 | '@expo/plist': 0.7.0 |
| 15635 | '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) | |
| 15636 | expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6) | |
| 15828 | '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | |
| 15829 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | |
| 15637 | 15830 | 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 | 15832 | transitivePeerDependencies: |
| 15640 | 15833 | - '@babel/core' |
| 15641 | 15834 | - '@types/react' |
| ... | ... | @@ -15644,37 +15837,37 @@ snapshots: |
| 15644 | 15837 | - react-native-reanimated |
| 15645 | 15838 | - react-native-worklets |
| 15646 | 15839 | |
| 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 | 15841 | dependencies: |
| 15649 | 15842 | '@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 | 15844 | '@expo/config': 56.0.9(typescript@6.0.3) |
| 15652 | 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 | 15847 | '@expo/fingerprint': 0.19.3 |
| 15655 | 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 | 15850 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 15658 | 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 | 15852 | '@ungap/structured-clone': 1.3.1 |
| 15660 | 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) | |
| 15662 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15663 | expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15664 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15854 | expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3) | |
| 15855 | expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15856 | expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) | |
| 15857 | expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15665 | 15858 | expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3) |
| 15666 | 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 | 15861 | pretty-format: 29.7.0 |
| 15669 | 15862 | 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 | 15864 | react-refresh: 0.14.2 |
| 15672 | 15865 | whatwg-url-minimum: 0.1.2 |
| 15673 | 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) | |
| 15675 | '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15867 | '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15868 | '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 15676 | 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 | 15871 | transitivePeerDependencies: |
| 15679 | 15872 | - '@babel/core' |
| 15680 | 15873 | - bufferutil |
| ... | ... | @@ -15862,6 +16055,10 @@ snapshots: |
| 15862 | 16055 | |
| 15863 | 16056 | find-my-way-ts@0.1.6: {} |
| 15864 | 16057 | |
| 16058 | find-up@3.0.0: | |
| 16059 | dependencies: | |
| 16060 | locate-path: 3.0.0 | |
| 16061 | ||
| 15865 | 16062 | flattie@1.1.1: {} |
| 15866 | 16063 | |
| 15867 | 16064 | flow-enums-runtime@0.0.6: {} |
| ... | ... | @@ -16388,6 +16585,8 @@ snapshots: |
| 16388 | 16585 | |
| 16389 | 16586 | is-number@7.0.0: {} |
| 16390 | 16587 | |
| 16588 | is-obj@2.0.0: {} | |
| 16589 | ||
| 16391 | 16590 | is-plain-obj@4.1.0: {} |
| 16392 | 16591 | |
| 16393 | 16592 | is-promise@4.0.0: {} |
| ... | ... | @@ -16488,6 +16687,8 @@ snapshots: |
| 16488 | 16687 | |
| 16489 | 16688 | json-schema-traverse@1.0.0: {} |
| 16490 | 16689 | |
| 16690 | json-schema-typed@7.0.3: {} | |
| 16691 | ||
| 16491 | 16692 | json-schema-typed@8.0.2: {} |
| 16492 | 16693 | |
| 16493 | 16694 | json-stringify-safe@5.0.1: |
| ... | ... | @@ -16715,6 +16916,11 @@ snapshots: |
| 16715 | 16916 | lightningcss-win32-arm64-msvc: 1.32.0 |
| 16716 | 16917 | lightningcss-win32-x64-msvc: 1.32.0 |
| 16717 | 16918 | |
| 16919 | locate-path@3.0.0: | |
| 16920 | dependencies: | |
| 16921 | p-locate: 3.0.0 | |
| 16922 | path-exists: 3.0.0 | |
| 16923 | ||
| 16718 | 16924 | lodash.debounce@4.0.8: {} |
| 16719 | 16925 | |
| 16720 | 16926 | lodash.escaperegexp@4.1.2: {} |
| ... | ... | @@ -17363,6 +17569,8 @@ snapshots: |
| 17363 | 17569 | |
| 17364 | 17570 | mimic-fn@2.1.0: {} |
| 17365 | 17571 | |
| 17572 | mimic-fn@3.1.0: {} | |
| 17573 | ||
| 17366 | 17574 | mimic-function@5.0.1: {} |
| 17367 | 17575 | |
| 17368 | 17576 | mimic-response@1.0.1: {} |
| ... | ... | @@ -17703,6 +17911,10 @@ snapshots: |
| 17703 | 17911 | |
| 17704 | 17912 | p-cancelable@2.1.1: {} |
| 17705 | 17913 | |
| 17914 | p-limit@2.3.0: | |
| 17915 | dependencies: | |
| 17916 | p-try: 2.2.0 | |
| 17917 | ||
| 17706 | 17918 | p-limit@3.1.0: |
| 17707 | 17919 | dependencies: |
| 17708 | 17920 | yocto-queue: 0.1.0 |
| ... | ... | @@ -17711,6 +17923,10 @@ snapshots: |
| 17711 | 17923 | dependencies: |
| 17712 | 17924 | yocto-queue: 1.2.2 |
| 17713 | 17925 | |
| 17926 | p-locate@3.0.0: | |
| 17927 | dependencies: | |
| 17928 | p-limit: 2.3.0 | |
| 17929 | ||
| 17714 | 17930 | p-queue@9.3.0: |
| 17715 | 17931 | dependencies: |
| 17716 | 17932 | eventemitter3: 5.0.4 |
| ... | ... | @@ -17718,6 +17934,8 @@ snapshots: |
| 17718 | 17934 | |
| 17719 | 17935 | p-timeout@7.0.1: {} |
| 17720 | 17936 | |
| 17937 | p-try@2.2.0: {} | |
| 17938 | ||
| 17721 | 17939 | package-manager-detector@1.6.0: {} |
| 17722 | 17940 | |
| 17723 | 17941 | pako@1.0.11: {} |
| ... | ... | @@ -17755,6 +17973,8 @@ snapshots: |
| 17755 | 17973 | |
| 17756 | 17974 | path-browserify@1.0.1: {} |
| 17757 | 17975 | |
| 17976 | path-exists@3.0.0: {} | |
| 17977 | ||
| 17758 | 17978 | path-expression-matcher@1.5.0: {} |
| 17759 | 17979 | |
| 17760 | 17980 | path-is-absolute@1.0.1: {} |
| ... | ... | @@ -17847,6 +18067,10 @@ snapshots: |
| 17847 | 18067 | |
| 17848 | 18068 | pkce-challenge@5.0.1: {} |
| 17849 | 18069 | |
| 18070 | pkg-up@3.1.0: | |
| 18071 | dependencies: | |
| 18072 | find-up: 3.0.0 | |
| 18073 | ||
| 17850 | 18074 | playwright-core@1.60.0: {} |
| 17851 | 18075 | |
| 17852 | 18076 | plist@3.1.0: |
| ... | ... | @@ -18061,102 +18285,102 @@ snapshots: |
| 18061 | 18285 | transitivePeerDependencies: |
| 18062 | 18286 | - supports-color |
| 18063 | 18287 | |
| 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 | 18289 | dependencies: |
| 18066 | 18290 | color: 4.2.3 |
| 18067 | 18291 | react: 19.2.3 |
| 18068 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18069 | react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18070 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18292 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18293 | react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18294 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18071 | 18295 | use-latest-callback: 0.2.6(react@19.2.3) |
| 18072 | 18296 | |
| 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 | 18298 | dependencies: |
| 18075 | 18299 | '@egjs/hammerjs': 2.0.17 |
| 18076 | 18300 | '@types/react-test-renderer': 19.1.0 |
| 18077 | 18301 | hoist-non-react-statics: 3.3.2 |
| 18078 | 18302 | invariant: 2.2.4 |
| 18079 | 18303 | 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) | |
| 18081 | 18305 | |
| 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 | 18307 | dependencies: |
| 18084 | 18308 | 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) | |
| 18086 | 18310 | |
| 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 | 18312 | dependencies: |
| 18089 | 18313 | 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) | |
| 18091 | 18315 | |
| 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 | 18317 | dependencies: |
| 18094 | 18318 | react: 19.2.3 |
| 18095 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18096 | react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18097 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18319 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18320 | react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18321 | react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18098 | 18322 | |
| 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 | 18324 | dependencies: |
| 18101 | 18325 | react: 19.2.3 |
| 18102 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18103 | react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18326 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18327 | react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18104 | 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) | |
| 18106 | 18330 | |
| 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 | 18332 | dependencies: |
| 18109 | 18333 | 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) | |
| 18111 | 18335 | |
| 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 | 18337 | dependencies: |
| 18114 | 18338 | react: 19.2.3 |
| 18115 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18116 | react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18117 | react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18339 | react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) | |
| 18340 | react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18341 | react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) | |
| 18118 | 18342 | semver: 7.8.1 |
| 18119 | 18343 | |
| 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 | 18345 | dependencies: |
| 18122 | 18346 | 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) | |
| 18124 | 18348 | |
| 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 | 18350 | dependencies: |
| 18127 | 18351 | react: 19.2.3 |
| 18128 | 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 | 18354 | warn-once: 0.1.1 |
| 18131 | 18355 | |
| 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 | 18357 | dependencies: |
| 18134 | 18358 | '@shikijs/types': 4.2.0 |
| 18135 | 18359 | '@shikijs/vscode-textmate': 10.0.2 |
| 18136 | 18360 | 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) | |
| 18138 | 18362 | |
| 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 | 18364 | dependencies: |
| 18141 | 18365 | css-select: 5.2.2 |
| 18142 | 18366 | css-tree: 1.1.3 |
| 18143 | 18367 | 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 | 18369 | warn-once: 0.1.1 |
| 18146 | 18370 | |
| 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 | 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 | 18374 | whatwg-url-without-unicode: 8.0.0-3 |
| 18151 | 18375 | |
| 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 | 18377 | dependencies: |
| 18154 | 18378 | escape-string-regexp: 4.0.0 |
| 18155 | 18379 | invariant: 2.2.4 |
| 18156 | 18380 | 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) | |
| 18158 | 18382 | |
| 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 | 18384 | dependencies: |
| 18161 | 18385 | '@babel/core': 7.29.7 |
| 18162 | 18386 | '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) |
| ... | ... | @@ -18168,23 +18392,23 @@ snapshots: |
| 18168 | 18392 | '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) |
| 18169 | 18393 | '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) |
| 18170 | 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 | 18396 | convert-source-map: 2.0.0 |
| 18173 | 18397 | 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 | 18399 | semver: 7.8.1 |
| 18176 | 18400 | transitivePeerDependencies: |
| 18177 | 18401 | - supports-color |
| 18178 | 18402 | |
| 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 | 18404 | dependencies: |
| 18181 | 18405 | '@react-native/assets-registry': 0.85.3 |
| 18182 | 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 | 18408 | '@react-native/gradle-plugin': 0.85.3 |
| 18185 | 18409 | '@react-native/js-polyfills': 0.85.3 |
| 18186 | 18410 | '@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 | 18412 | abort-controller: 3.0.0 |
| 18189 | 18413 | anser: 1.4.10 |
| 18190 | 18414 | ansi-regex: 5.0.1 |
| ... | ... | @@ -19100,6 +19324,8 @@ snapshots: |
| 19100 | 19324 | |
| 19101 | 19325 | type-fest@0.7.1: {} |
| 19102 | 19326 | |
| 19327 | type-fest@2.19.0: {} | |
| 19328 | ||
| 19103 | 19329 | type-fest@5.7.0: |
| 19104 | 19330 | dependencies: |
| 19105 | 19331 | tagged-tag: 1.0.0 |
| ... | ... | @@ -19211,14 +19437,14 @@ snapshots: |
| 19211 | 19437 | |
| 19212 | 19438 | universalify@2.0.1: {} |
| 19213 | 19439 | |
| 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 | 19441 | dependencies: |
| 19216 | 19442 | '@tailwindcss/node': 4.2.1 |
| 19217 | 19443 | '@tailwindcss/oxide': 4.2.1 |
| 19218 | 19444 | culori: 4.0.2 |
| 19219 | 19445 | lightningcss: 1.30.1 |
| 19220 | 19446 | 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 | 19448 | tailwindcss: 4.3.0 |
| 19223 | 19449 | |
| 19224 | 19450 | unpipe@1.0.0: {} |
pnpm-workspace.yaml+17| ... | ... | @@ -6,6 +6,13 @@ packages: |
| 6 | 6 | - scripts |
| 7 | 7 | |
| 8 | 8 | catalog: |
| 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 | 16 | "@effect/atom-react": 4.0.0-beta.78 |
| 10 | 17 | "@effect/openapi-generator": 4.0.0-beta.78 |
| 11 | 18 | "@effect/platform-bun": 4.0.0-beta.78 |
| ... | ... | @@ -40,6 +47,16 @@ onlyBuiltDependencies: |
| 40 | 47 | - sharp |
| 41 | 48 | |
| 42 | 49 | overrides: |
| 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 | 60 | "@clerk/clerk-js>@base-org/account": "-" |
| 44 | 61 | "@clerk/clerk-js>@coinbase/wallet-sdk": "-" |
| 45 | 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 | 8 | import { |
| 9 | 9 | createStageWorkspaceConfig, |
| 10 | 10 | createStagePnpmConfig, |
| 11 | createBuildConfig, | |
| 11 | 12 | DESKTOP_ASAR_UNPACK, |
| 13 | renderMacPasskeyEntitlements, | |
| 14 | resolveClerkPasskeyNativeArtifacts, | |
| 15 | resolveMacPasskeySigningConfiguration, | |
| 12 | 16 | resolveDesktopRuntimeDependencies, |
| 13 | 17 | resolveFffNativeDependencies, |
| 14 | 18 | resolveBuildOptions, |
| ... | ... | @@ -175,6 +179,86 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 175 | 179 | assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]); |
| 176 | 180 | }); |
| 177 | 181 | |
| 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 | 262 | it("promotes target fff binaries to direct staged dependencies", () => { |
| 179 | 263 | assert.deepStrictEqual(resolveFffNativeDependencies("mac", "arm64", "0.9.4"), { |
| 180 | 264 | "@ff-labs/fff-bin-darwin-arm64": "0.9.4", |
| ... | ... | @@ -192,6 +276,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 192 | 276 | }); |
| 193 | 277 | }); |
| 194 | 278 | |
| 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 | 299 | it("falls back to the default mock update port when the configured port is blank", () => { |
| 196 | 300 | assert.equal(resolveMockUpdateServerUrl(undefined), "http://localhost:3000"); |
| 197 | 301 | assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123"); |
scripts/build-desktop-artifact.ts+232-3| ... | ... | @@ -1,7 +1,10 @@ |
| 1 | 1 | #!/usr/bin/env node |
| 2 | 2 | |
| 3 | import { createRequire } from "node:module"; | |
| 4 | ||
| 3 | 5 | import { fromYaml } from "@t3tools/shared/schemaYaml"; |
| 4 | 6 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 7 | import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth"; | |
| 5 | 8 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; |
| 6 | 9 | import rootPackageJson from "../package.json" with { type: "json" }; |
| 7 | 10 | import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; |
| ... | ... | @@ -9,6 +12,7 @@ import serverPackageJson from "../apps/server/package.json" with { type: "json" |
| 9 | 12 | |
| 10 | 13 | import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; |
| 11 | 14 | import { getDefaultBuildArch } from "./lib/build-target-arch.ts"; |
| 15 | import { loadRepoEnv } from "./lib/public-config.ts"; | |
| 12 | 16 | import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; |
| 13 | 17 | |
| 14 | 18 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| ... | ... | @@ -27,6 +31,8 @@ import { Command, Flag } from "effect/unstable/cli"; |
| 27 | 31 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 28 | 32 | |
| 29 | 33 | const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const; |
| 34 | const DESKTOP_APP_ID = "com.t3tools.t3code"; | |
| 35 | const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u; | |
| 30 | 36 | |
| 31 | 37 | const BuildPlatform = Schema.Literals(["mac", "linux", "win"]); |
| 32 | 38 | const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); |
| ... | ... | @@ -293,6 +299,121 @@ interface StagePackageJson { |
| 293 | 299 | export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const; |
| 294 | 300 | export const DESKTOP_ASAR_UNPACK = ["node_modules/@ff-labs/fff-bin-*/**/*"] as const; |
| 295 | 301 | |
| 302 | export interface MacPasskeySigningConfiguration { | |
| 303 | readonly appId: string; | |
| 304 | readonly teamId: string; | |
| 305 | readonly rpDomains: readonly string[]; | |
| 306 | readonly provisioningProfilePath: string; | |
| 307 | } | |
| 308 | ||
| 309 | function 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 | ||
| 334 | export 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 | ||
| 378 | function escapeXml(value: string): string { | |
| 379 | return value | |
| 380 | .replaceAll("&", "&amp;") | |
| 381 | .replaceAll("<", "&lt;") | |
| 382 | .replaceAll(">", "&gt;") | |
| 383 | .replaceAll('"', "&quot;") | |
| 384 | .replaceAll("'", "&apos;"); | |
| 385 | } | |
| 386 | ||
| 387 | export 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 | ||
| 296 | 417 | export function resolveFffNativeDependencies( |
| 297 | 418 | platform: typeof BuildPlatform.Type, |
| 298 | 419 | arch: typeof BuildArch.Type, |
| ... | ... | @@ -319,6 +440,63 @@ export function resolveFffNativeDependencies( |
| 319 | 440 | ); |
| 320 | 441 | } |
| 321 | 442 | |
| 443 | export interface ClerkPasskeyNativeArtifact { | |
| 444 | readonly packageName: string; | |
| 445 | readonly binaryFileName: string; | |
| 446 | } | |
| 447 | ||
| 448 | export 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. | |
| 474 | const 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 | ||
| 322 | 500 | export function createStageWorkspaceConfig( |
| 323 | 501 | platform: typeof BuildPlatform.Type, |
| 324 | 502 | arch: typeof BuildArch.Type, |
| ... | ... | @@ -739,16 +917,22 @@ export function resolveDesktopProductName(version: string): string { |
| 739 | 917 | : (desktopPackageJson.productName ?? "T3 Code"); |
| 740 | 918 | } |
| 741 | 919 | |
| 742 | const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | |
| 920 | export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | |
| 743 | 921 | platform: typeof BuildPlatform.Type, |
| 744 | 922 | target: string, |
| 745 | 923 | version: string, |
| 746 | 924 | signed: boolean, |
| 747 | 925 | mockUpdates: boolean, |
| 748 | 926 | mockUpdateServerPort: number | undefined, |
| 927 | macPasskeySigning: | |
| 928 | | { | |
| 929 | readonly entitlementsPath: string; | |
| 930 | readonly provisioningProfilePath: string; | |
| 931 | } | |
| 932 | | undefined, | |
| 749 | 933 | ) { |
| 750 | 934 | const buildConfig: Record<string, unknown> = { |
| 751 | appId: "com.t3tools.t3code", | |
| 935 | appId: DESKTOP_APP_ID, | |
| 752 | 936 | productName: resolveDesktopProductName(version), |
| 753 | 937 | artifactName: "T3-Code-${version}-${arch}.${ext}", |
| 754 | 938 | asarUnpack: [...DESKTOP_ASAR_UNPACK], |
| ... | ... | @@ -777,9 +961,15 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 777 | 961 | protocols: [ |
| 778 | 962 | { |
| 779 | 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 | } |
| 785 | 975 | |
| ... | ... | @@ -956,6 +1146,38 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 956 | 1146 | // electron-builder is filtering out stageResourcesDir directory in the AppImage for production |
| 957 | 1147 | yield* fs.copy(stageResourcesDir, path.join(stageAppDir, "apps/desktop/prod-resources")); |
| 958 | 1148 | |
| 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 | 1181 | const stageDependencies = { |
| 960 | 1182 | ...resolvedServerDependencies, |
| 961 | 1183 | ...resolvedDesktopRuntimeDependencies, |
| ... | ... | @@ -983,6 +1205,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 983 | 1205 | options.signed, |
| 984 | 1206 | options.mockUpdates, |
| 985 | 1207 | options.mockUpdateServerPort, |
| 1208 | macPasskeySigning && macEntitlementsPath | |
| 1209 | ? { | |
| 1210 | entitlementsPath: macEntitlementsPath, | |
| 1211 | provisioningProfilePath: macPasskeySigning.provisioningProfilePath, | |
| 1212 | } | |
| 1213 | : undefined, | |
| 986 | 1214 | ), |
| 987 | 1215 | dependencies: stageDependencies, |
| 988 | 1216 | devDependencies: { |
| ... | ... | @@ -1014,6 +1242,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1014 | 1242 | }), |
| 1015 | 1243 | { label: "vp install --prod", verbose: options.verbose }, |
| 1016 | 1244 | ); |
| 1245 | yield* stageClerkPasskeyNativeBinaries(stageAppDir, options.platform, options.arch); | |
| 1017 | 1246 | |
| 1018 | 1247 | // electron-builder treats several set-but-empty variables (e.g. CSC_LINK="") |
| 1019 | 1248 | // as enabled, so copy the host env and scrub empty values instead of relying |