Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mobile/src/connection/catalog-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const LEGACY_CONNECTIONS_KEY = "t3code.connections";
function catalogError(operation: string, cause: unknown) {
return new ConnectionTransientError({
reason: "remote-unavailable",
message: `Could not ${operation} the local connection catalog: ${String(cause)}`,
detail: `Could not ${operation} the local connection catalog: ${String(cause)}`,
});
}

Expand Down
100 changes: 53 additions & 47 deletions apps/mobile/src/connection/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import {
ConnectionBlockedError,
ConnectionTransientError,
ConnectionWakeups,
Connectivity,
Wakeups,
} from "@t3tools/client-runtime/connection";
import { managedRelayAccountChanges, managedRelaySessionAtom } from "@t3tools/client-runtime/relay";
import { AuthStandardClientScopes } from "@t3tools/contracts";
Expand Down Expand Up @@ -41,53 +41,47 @@ function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "on
return "unknown";
}

const connectivityLayer = Layer.succeed(
Connectivity,
Connectivity.of({
status: Effect.tryPromise({
try: () => Network.getNetworkStateAsync(),
catch: () => undefined,
}).pipe(
Effect.match({
onFailure: () => "unknown" as const,
onSuccess: networkStatus,
}),
),
changes: Stream.callback((queue) =>
const connectivityLayer = Connectivity.layer({
status: Effect.tryPromise({
try: () => Network.getNetworkStateAsync(),
catch: () => undefined,
}).pipe(
Effect.match({
onFailure: () => "unknown" as const,
onSuccess: networkStatus,
}),
),
changes: Stream.callback((queue) =>
Effect.acquireRelease(
Effect.sync(() =>
Network.addNetworkStateListener((state) => {
Queue.offerUnsafe(queue, networkStatus(state));
}),
),
(subscription) => Effect.sync(() => subscription.remove()),
).pipe(Effect.asVoid),
),
});

const wakeupsLayer = Wakeups.layer({
changes: Stream.merge(
Stream.callback<"application-active">((queue) =>
Effect.acquireRelease(
Effect.sync(() =>
Network.addNetworkStateListener((state) => {
Queue.offerUnsafe(queue, networkStatus(state));
AppState.addEventListener("change", (state) => {
if (state === "active") {
Queue.offerUnsafe(queue, "application-active");
}
}),
),
(subscription) => Effect.sync(() => subscription.remove()),
).pipe(Effect.asVoid),
),
}),
);

const wakeupsLayer = Layer.succeed(
ConnectionWakeups,
ConnectionWakeups.of({
changes: Stream.merge(
Stream.callback<"application-active">((queue) =>
Effect.acquireRelease(
Effect.sync(() =>
AppState.addEventListener("change", (state) => {
if (state === "active") {
Queue.offerUnsafe(queue, "application-active");
}
}),
),
(subscription) => Effect.sync(() => subscription.remove()),
).pipe(Effect.asVoid),
),
managedRelayAccountChanges(appAtomRegistry).pipe(
Stream.map(() => "credentials-changed" as const),
),
managedRelayAccountChanges(appAtomRegistry).pipe(
Stream.map(() => "credentials-changed" as const),
),
}),
);
),
});

const capabilitiesLayer = Layer.succeedContext(
Context.make(
Expand All @@ -98,22 +92,22 @@ const capabilitiesLayer = Layer.succeedContext(
if (session === null) {
return yield* new ConnectionBlockedError({
reason: "authentication",
message: "Sign in to T3 Cloud to connect this environment.",
detail: "Sign in to T3 Cloud to connect this environment.",
});
}
const token = yield* session.readClerkToken().pipe(
Effect.mapError(
(error) =>
new ConnectionTransientError({
reason: "network",
message: error.message,
detail: error.message,
}),
),
);
if (token === null) {
return yield* new ConnectionBlockedError({
reason: "authentication",
message: "The T3 Cloud session is unavailable.",
detail: "The T3 Cloud session is unavailable.",
});
}
return token;
Expand All @@ -132,7 +126,7 @@ const capabilitiesLayer = Layer.succeedContext(
catch: (cause) =>
new ConnectionTransientError({
reason: "remote-unavailable",
message: `Could not load the mobile device identity: ${String(cause)}`,
detail: `Could not load the mobile device identity: ${String(cause)}`,
}),
}).pipe(Effect.map(Option.some)),
}),
Expand All @@ -151,14 +145,14 @@ const capabilitiesLayer = Layer.succeedContext(
Effect.fail(
new ConnectionBlockedError({
reason: "unsupported",
message: "SSH environments are only available in the desktop app.",
detail: "SSH environments are only available in the desktop app.",
}),
),
prepare: () =>
Effect.fail(
new ConnectionBlockedError({
reason: "unsupported",
message: "SSH environments are only available in the desktop app.",
detail: "SSH environments are only available in the desktop app.",
}),
),
disconnect: () => Effect.void,
Expand Down Expand Up @@ -195,7 +189,19 @@ const environmentOwnedDataCleanupLayer = Layer.succeed(
}),
);

export const connectionPlatformLayer = Layer.mergeAll(
type ConnectionPlatformLayerSource =
| typeof connectionStorageLayer
| typeof connectivityLayer
| typeof wakeupsLayer
| typeof capabilitiesLayer
| typeof platformConnectionSourceLayer
| typeof environmentOwnedDataCleanupLayer;

export const connectionPlatformLayer: Layer.Layer<
Layer.Success<ConnectionPlatformLayerSource>,
Layer.Error<ConnectionPlatformLayerSource>,
Layer.Services<ConnectionPlatformLayerSource>
> = Layer.mergeAll(
connectionStorageLayer,
connectivityLayer,
wakeupsLayer,
Expand Down
24 changes: 18 additions & 6 deletions apps/mobile/src/connection/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { connectionLayer as clientConnectionLayer } from "@t3tools/client-runtime/connection";
import { Connection } from "@t3tools/client-runtime/connection";
import * as Layer from "effect/Layer";
import { Atom } from "effect/unstable/reactivity";

import { runtimeContextLayer } from "../lib/runtime";
import { connectionPlatformLayer } from "./platform";

const providedConnectionPlatformLayer = connectionPlatformLayer.pipe(
Layer.provide(runtimeContextLayer),
);
const providedConnectionPlatformLayer: Layer.Layer<
Layer.Success<typeof connectionPlatformLayer>,
Layer.Error<typeof connectionPlatformLayer>
> = connectionPlatformLayer.pipe(Layer.provide(runtimeContextLayer));

type ConnectionLayerSource =
| typeof Connection.layer
| typeof runtimeContextLayer
| typeof providedConnectionPlatformLayer;

export const connectionLayer = clientConnectionLayer.pipe(
export const connectionLayer: Layer.Layer<
Layer.Success<ConnectionLayerSource>,
Layer.Error<ConnectionLayerSource>
> = Connection.layer.pipe(
Layer.provideMerge(Layer.mergeAll(runtimeContextLayer, providedConnectionPlatformLayer)),
);

export const connectionAtomRuntime = Atom.runtime(connectionLayer);
export const connectionAtomRuntime: Atom.AtomRuntime<
Layer.Success<typeof connectionLayer>,
Layer.Error<typeof connectionLayer>
> = Atom.runtime(connectionLayer);
20 changes: 10 additions & 10 deletions apps/mobile/src/connection/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
removeCatalogValue,
replaceCatalogValue,
} from "@t3tools/client-runtime/platform";
import { RemoteDpopAccessTokenStore } from "@t3tools/client-runtime/authorization";
import { TokenStore } from "@t3tools/client-runtime/authorization";
import {
ConnectionCredentialStore,
ConnectionProfileStore,
ConnectionTransientError,
CredentialStore,
ProfileStore,
} from "@t3tools/client-runtime/connection";
import {
EnvironmentId,
Expand Down Expand Up @@ -58,7 +58,7 @@ const LegacyStoredShellSnapshot = Schema.Struct({
function catalogError(operation: string, cause: unknown) {
return new ConnectionTransientError({
reason: "remote-unavailable",
message: `Could not ${operation} the local connection catalog: ${String(cause)}`,
detail: `Could not ${operation} the local connection catalog: ${String(cause)}`,
});
}

Expand Down Expand Up @@ -197,7 +197,7 @@ export const connectionStorageLayer = Layer.effectContext(
.update((document) => removeConnectionFromCatalog(document, target))
.pipe(Effect.mapError((error) => targetPersistenceError("remove-connection", error))),
});
const profileStore = ConnectionProfileStore.of({
const profileStore = ProfileStore.make({
get: (connectionId) =>
catalog.read.pipe(
Effect.map((document) =>
Expand All @@ -221,7 +221,7 @@ export const connectionStorageLayer = Layer.effectContext(
),
})),
});
const credentialStore = ConnectionCredentialStore.of({
const credentialStore = CredentialStore.make({
get: (connectionId) =>
catalog.read.pipe(
Effect.map((document) =>
Expand All @@ -248,7 +248,7 @@ export const connectionStorageLayer = Layer.effectContext(
),
})),
});
const remoteTokenStore = RemoteDpopAccessTokenStore.of({
const remoteTokenStore = TokenStore.make({
get: (environmentId) =>
catalog.read.pipe(
Effect.map((document) =>
Expand Down Expand Up @@ -423,9 +423,9 @@ export const connectionStorageLayer = Layer.effectContext(

return Context.make(ConnectionTargetStore, targetStore).pipe(
Context.add(ConnectionRegistrationStore, registrationStore),
Context.add(ConnectionProfileStore, profileStore),
Context.add(ConnectionCredentialStore, credentialStore),
Context.add(RemoteDpopAccessTokenStore, remoteTokenStore),
Context.add(ProfileStore.ConnectionProfileStore, profileStore),
Context.add(CredentialStore.ConnectionCredentialStore, credentialStore),
Context.add(TokenStore.RemoteDpopAccessTokenStore, remoteTokenStore),
Context.add(EnvironmentCacheStore, cacheStore),
);
}),
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/cloud/linkEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import { HttpClient } from "effect/unstable/http";
import { afterEach, beforeEach, vi } from "vite-plus/test";
import {
AVAILABLE_CONNECTION_STATE,
type EnvironmentRegistryService,
EnvironmentSupervisor,
type EnvironmentSupervisorService,
type PreparedConnection,
PrimaryConnectionTarget,
} from "@t3tools/client-runtime/connection";
Expand Down Expand Up @@ -114,13 +112,13 @@ function registryLayer(options?: {
connect: Effect.void,
disconnect: Effect.void,
retryNow: Effect.void,
} satisfies EnvironmentSupervisorService);
} satisfies EnvironmentSupervisor["Service"]);
const registry = {
run: <A, E, R>(_environmentId: EnvironmentId, effect: Effect.Effect<A, E, R>) =>
Effect.provideService(effect, EnvironmentSupervisor, supervisor),
runStream: <A, E, R>(_environmentId: EnvironmentId, stream: Stream.Stream<A, E, R>) =>
Stream.provideService(stream, EnvironmentSupervisor, supervisor),
} as unknown as EnvironmentRegistryService;
} as unknown as EnvironmentRegistry["Service"];
return EnvironmentRegistry.of(registry);
}),
);
Expand Down
Loading
Loading