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