-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[codex] Rewrite client connection architecture #2978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
juliusmarminge
wants to merge
19
commits into
codex/hosted-web-tracing-poc
Choose a base branch
from
codex/connection-state-audit
base: codex/hosted-web-tracing-poc
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
808a470
Rewrite client connection architecture
juliusmarminge f0dc04a
Trace relay connection flows in new runtime
juliusmarminge 6f347ae
refactor: Enhance connection supervisor with improved state managemen…
juliusmarminge b6e9fd3
refactor: update environment registry and runtime services
juliusmarminge d80058c
refactor: remove filesystem and source control discovery state manage…
juliusmarminge c91f395
refactor: reorganize shared client runtime
juliusmarminge 79c74b7
feat: add state management for environment queries and terminal sessions
juliusmarminge 181b2e2
Refactor session and event handling across the app
juliusmarminge 008c428
Harden shared connection runtime
juliusmarminge 11a05eb
Repair mobile connection integration
juliusmarminge 7f57a38
Restore web connection projections
juliusmarminge 7cbbf84
Harden persisted catalogs and relay tracing
juliusmarminge bcbb655
Refactor session handling and event streaming
juliusmarminge 9c921ea
Consolidate Clerk auth sheet routing
juliusmarminge cd73767
Resolve rebase integration conflicts
juliusmarminge f04258c
Polish mobile chat and relay startup
juliusmarminge 25717b6
Align mobile turn folds with main
juliusmarminge c99480f
Add native composer editor and token styling
juliusmarminge f32e94b
Refine mobile composer and UITextView patch
juliusmarminge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import * as NodeServices from "@effect/platform-node/NodeServices"; | ||
| import { assert, describe, it } from "@effect/vitest"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as FileSystem from "effect/FileSystem"; | ||
| import * as Layer from "effect/Layer"; | ||
| import * as Option from "effect/Option"; | ||
| import * as Ref from "effect/Ref"; | ||
|
|
||
| import * as ElectronSafeStorage from "../electron/ElectronSafeStorageService.ts"; | ||
| import * as DesktopConfig from "./DesktopConfig.ts"; | ||
| import * as DesktopConnectionCatalogStore from "./DesktopConnectionCatalogStore.ts"; | ||
| import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | ||
|
|
||
| const textDecoder = new TextDecoder(); | ||
| const textEncoder = new TextEncoder(); | ||
|
|
||
| function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) { | ||
| return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, { | ||
| isEncryptionAvailable: Effect.succeed(available), | ||
| encryptString: (value) => Effect.succeed(textEncoder.encode(`encrypted:${value}`)), | ||
| decryptString: (value) => { | ||
| return Effect.gen(function* () { | ||
| const decoded = textDecoder.decode(value); | ||
| if ( | ||
| !decoded.startsWith("encrypted:") || | ||
| (failDecrypt !== null && (yield* Ref.get(failDecrypt))) | ||
| ) { | ||
| return yield* new ElectronSafeStorage.ElectronSafeStorageDecryptError({ | ||
| cause: new Error("invalid encrypted catalog"), | ||
| }); | ||
| } | ||
| return decoded.slice("encrypted:".length); | ||
| }); | ||
| }, | ||
| } satisfies ElectronSafeStorage.ElectronSafeStorageShape); | ||
| } | ||
|
|
||
| function makeLayer( | ||
| baseDir: string, | ||
| encryptionAvailable = true, | ||
| failDecrypt: Ref.Ref<boolean> | null = null, | ||
| ) { | ||
| const environmentLayer = DesktopEnvironment.layer({ | ||
| dirname: "/repo/apps/desktop/src", | ||
| homeDirectory: baseDir, | ||
| platform: "darwin", | ||
| processArch: "arm64", | ||
| appVersion: "1.2.3", | ||
| appPath: "/repo", | ||
| isPackaged: true, | ||
| resourcesPath: "/missing/resources", | ||
| runningUnderArm64Translation: false, | ||
| }).pipe( | ||
| Layer.provide( | ||
| Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })), | ||
| ), | ||
| ); | ||
|
|
||
| return DesktopConnectionCatalogStore.layer.pipe( | ||
| Layer.provideMerge(environmentLayer), | ||
| Layer.provideMerge(makeSafeStorageLayer(encryptionAvailable, failDecrypt)), | ||
| Layer.provideMerge(NodeServices.layer), | ||
| ); | ||
| } | ||
|
|
||
| const withStore = <A, E, R>( | ||
| effect: Effect.Effect<A, E, R | DesktopConnectionCatalogStore.DesktopConnectionCatalogStore>, | ||
| encryptionAvailable = true, | ||
| ) => | ||
| Effect.gen(function* () { | ||
| const fileSystem = yield* FileSystem.FileSystem; | ||
| const baseDir = yield* fileSystem.makeTempDirectoryScoped({ | ||
| prefix: "t3-desktop-connection-catalog-test-", | ||
| }); | ||
| return yield* effect.pipe(Effect.provide(makeLayer(baseDir, encryptionAvailable))); | ||
| }).pipe(Effect.provide(NodeServices.layer), Effect.scoped); | ||
|
|
||
| describe("DesktopConnectionCatalogStore", () => { | ||
| it.effect("persists, reads, and clears an encrypted connection catalog", () => | ||
| withStore( | ||
| Effect.gen(function* () { | ||
| const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore; | ||
| const catalog = '{"schemaVersion":1,"targets":[]}'; | ||
|
|
||
| assert.isTrue(yield* store.set(catalog)); | ||
| assert.deepStrictEqual(yield* store.get, Option.some(catalog)); | ||
|
|
||
| yield* store.clear; | ||
| assert.deepStrictEqual(yield* store.get, Option.none()); | ||
| }), | ||
| ), | ||
| ); | ||
|
|
||
| it.effect("does not persist when secure storage is unavailable", () => | ||
| withStore( | ||
| Effect.gen(function* () { | ||
| const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore; | ||
| assert.isFalse(yield* store.set("{}")); | ||
| assert.deepStrictEqual(yield* store.get, Option.none()); | ||
| }), | ||
| false, | ||
| ), | ||
| ); | ||
|
|
||
| it.effect("discards a catalog that can no longer be decrypted", () => | ||
| Effect.gen(function* () { | ||
| const fileSystem = yield* FileSystem.FileSystem; | ||
| const baseDir = yield* fileSystem.makeTempDirectoryScoped({ | ||
| prefix: "t3-desktop-connection-catalog-test-", | ||
| }); | ||
| const failDecrypt = yield* Ref.make(false); | ||
| const layer = makeLayer(baseDir, true, failDecrypt); | ||
| const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe( | ||
| Effect.provide(layer), | ||
| ); | ||
|
|
||
| assert.isTrue(yield* store.set('{"schemaVersion":1,"targets":[]}')); | ||
| yield* Ref.set(failDecrypt, true); | ||
| assert.deepStrictEqual(yield* store.get, Option.none()); | ||
| assert.deepStrictEqual(yield* store.get, Option.none()); | ||
| }).pipe(Effect.provide(NodeServices.layer), Effect.scoped), | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| import { fromLenientJson } from "@t3tools/shared/schemaJson"; | ||
| import * as Context from "effect/Context"; | ||
| import * as Crypto from "effect/Crypto"; | ||
| import * as Data from "effect/Data"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as Encoding from "effect/Encoding"; | ||
| import * as FileSystem from "effect/FileSystem"; | ||
| import * as Layer from "effect/Layer"; | ||
| import * as Option from "effect/Option"; | ||
| import * as Path from "effect/Path"; | ||
| import * as PlatformError from "effect/PlatformError"; | ||
| import * as Schema from "effect/Schema"; | ||
|
|
||
| import * as ElectronSafeStorage from "../electron/ElectronSafeStorageService.ts"; | ||
| import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | ||
|
|
||
| const ConnectionCatalogDocument = Schema.Struct({ | ||
| version: Schema.Literal(1), | ||
| encryptedCatalog: Schema.String, | ||
| }); | ||
| type ConnectionCatalogDocument = typeof ConnectionCatalogDocument.Type; | ||
|
|
||
| const ConnectionCatalogDocumentJson = fromLenientJson(ConnectionCatalogDocument); | ||
| const decodeConnectionCatalogDocumentJson = Schema.decodeEffect(ConnectionCatalogDocumentJson); | ||
| const encodeConnectionCatalogDocumentJson = Schema.encodeEffect(ConnectionCatalogDocumentJson); | ||
|
|
||
| export class DesktopConnectionCatalogStoreWriteError extends Data.TaggedError( | ||
| "DesktopConnectionCatalogStoreWriteError", | ||
| )<{ | ||
| readonly cause: PlatformError.PlatformError | Schema.SchemaError; | ||
| }> { | ||
| override get message() { | ||
| return `Failed to write desktop connection catalog: ${this.cause.message}`; | ||
| } | ||
| } | ||
|
|
||
| export class DesktopConnectionCatalogStoreDecodeError extends Data.TaggedError( | ||
| "DesktopConnectionCatalogStoreDecodeError", | ||
| )<{ | ||
| readonly cause: Encoding.EncodingError; | ||
| }> { | ||
| override get message() { | ||
| return "Failed to decode the desktop connection catalog."; | ||
| } | ||
| } | ||
|
|
||
| export interface DesktopConnectionCatalogStoreShape { | ||
| readonly get: Effect.Effect< | ||
| Option.Option<string>, | ||
| | DesktopConnectionCatalogStoreDecodeError | ||
| | ElectronSafeStorage.ElectronSafeStorageAvailabilityError | ||
| | ElectronSafeStorage.ElectronSafeStorageDecryptError | ||
| >; | ||
| readonly set: ( | ||
| catalog: string, | ||
| ) => Effect.Effect< | ||
| boolean, | ||
| | DesktopConnectionCatalogStoreWriteError | ||
| | ElectronSafeStorage.ElectronSafeStorageAvailabilityError | ||
| | ElectronSafeStorage.ElectronSafeStorageEncryptError | ||
| >; | ||
| readonly clear: Effect.Effect<void>; | ||
| } | ||
|
|
||
| export class DesktopConnectionCatalogStore extends Context.Service< | ||
| DesktopConnectionCatalogStore, | ||
| DesktopConnectionCatalogStoreShape | ||
| >()("@t3tools/desktop/app/DesktopConnectionCatalogStore") {} | ||
|
|
||
| function decodeSecretBytes( | ||
| encoded: string, | ||
| ): Effect.Effect<Uint8Array, DesktopConnectionCatalogStoreDecodeError> { | ||
| return Effect.fromResult(Encoding.decodeBase64(encoded)).pipe( | ||
| Effect.mapError((cause) => new DesktopConnectionCatalogStoreDecodeError({ cause })), | ||
| ); | ||
| } | ||
|
|
||
| const readDocument = ( | ||
| fileSystem: FileSystem.FileSystem, | ||
| catalogPath: string, | ||
| ): Effect.Effect<Option.Option<ConnectionCatalogDocument>> => | ||
| fileSystem.readFileString(catalogPath).pipe( | ||
| Effect.option, | ||
| Effect.flatMap( | ||
| Option.match({ | ||
| onNone: () => Effect.succeed(Option.none<ConnectionCatalogDocument>()), | ||
| onSome: (raw) => decodeConnectionCatalogDocumentJson(raw).pipe(Effect.option), | ||
| }), | ||
| ), | ||
| ); | ||
|
|
||
| const writeDocument = Effect.fn("desktop.connectionCatalogStore.writeDocument")(function* (input: { | ||
| readonly fileSystem: FileSystem.FileSystem; | ||
| readonly path: Path.Path; | ||
| readonly catalogPath: string; | ||
| readonly document: ConnectionCatalogDocument; | ||
| readonly suffix: string; | ||
| }): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> { | ||
| const directory = input.path.dirname(input.catalogPath); | ||
| const tempPath = `${input.catalogPath}.${process.pid}.${input.suffix}.tmp`; | ||
| const encoded = yield* encodeConnectionCatalogDocumentJson(input.document); | ||
| yield* input.fileSystem.makeDirectory(directory, { recursive: true }); | ||
| yield* Effect.gen(function* () { | ||
| yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`); | ||
| yield* input.fileSystem.rename(tempPath, input.catalogPath); | ||
| }).pipe( | ||
| Effect.ensuring( | ||
| input.fileSystem.remove(tempPath, { force: true }).pipe( | ||
| Effect.catch((error) => | ||
| Effect.logWarning("Could not remove a temporary connection catalog file.", { | ||
| tempPath, | ||
| error, | ||
| }), | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| }); | ||
|
|
||
| export const layer = Layer.effect( | ||
| DesktopConnectionCatalogStore, | ||
| Effect.gen(function* () { | ||
| const environment = yield* DesktopEnvironment.DesktopEnvironment; | ||
| const fileSystem = yield* FileSystem.FileSystem; | ||
| const path = yield* Path.Path; | ||
| const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage; | ||
| const crypto = yield* Crypto.Crypto; | ||
| const catalogPath = path.join(environment.stateDir, "connection-catalog.json"); | ||
|
|
||
| return DesktopConnectionCatalogStore.of({ | ||
| get: Effect.gen(function* () { | ||
| const document = yield* readDocument(fileSystem, catalogPath); | ||
| if (Option.isNone(document) || !(yield* safeStorage.isEncryptionAvailable)) { | ||
| return Option.none<string>(); | ||
| } | ||
| return yield* decodeSecretBytes(document.value.encryptedCatalog).pipe( | ||
| Effect.flatMap(safeStorage.decryptString), | ||
| Effect.map(Option.some), | ||
| Effect.catchTags({ | ||
| DesktopConnectionCatalogStoreDecodeError: (error) => | ||
| Effect.logWarning("Discarding an unreadable desktop connection catalog.", { | ||
| error, | ||
| }).pipe( | ||
| Effect.andThen(fileSystem.remove(catalogPath, { force: true })), | ||
| Effect.catch(() => Effect.void), | ||
| Effect.as(Option.none<string>()), | ||
| ), | ||
| ElectronSafeStorageDecryptError: (error) => | ||
| Effect.logWarning("Discarding an undecryptable desktop connection catalog.", { | ||
| error, | ||
| }).pipe( | ||
| Effect.andThen(fileSystem.remove(catalogPath, { force: true })), | ||
| Effect.catch(() => Effect.void), | ||
| Effect.as(Option.none<string>()), | ||
| ), | ||
| }), | ||
| ); | ||
| }).pipe(Effect.withSpan("desktop.connectionCatalogStore.get")), | ||
| set: Effect.fn("desktop.connectionCatalogStore.set")(function* (catalog) { | ||
| if (!(yield* safeStorage.isEncryptionAvailable)) { | ||
| return false; | ||
| } | ||
| const encryptedCatalog = Encoding.encodeBase64(yield* safeStorage.encryptString(catalog)); | ||
| const suffix = (yield* crypto.randomUUIDv4.pipe( | ||
| Effect.mapError((cause) => new DesktopConnectionCatalogStoreWriteError({ cause })), | ||
| )).replace(/-/g, ""); | ||
| yield* writeDocument({ | ||
| fileSystem, | ||
| path, | ||
| catalogPath, | ||
| document: { version: 1, encryptedCatalog }, | ||
| suffix, | ||
| }).pipe(Effect.mapError((cause) => new DesktopConnectionCatalogStoreWriteError({ cause }))); | ||
| return true; | ||
| }), | ||
| clear: fileSystem.remove(catalogPath, { force: true }).pipe( | ||
| Effect.catch((error) => | ||
| Effect.logWarning("Could not clear the desktop connection catalog.", { | ||
| catalogPath, | ||
| error, | ||
| }), | ||
| ), | ||
| Effect.withSpan("desktop.connectionCatalogStore.clear"), | ||
| ), | ||
| }); | ||
| }), | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.