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
7 changes: 4 additions & 3 deletions apps/desktop/src/preview/BrowserImport/BrowserImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export class BrowserCookieWriteError extends Schema.TaggedError<BrowserCookieWri
"BrowserCookieWriteError",
{
url: Schema.String,
name: Schema.String,
cookieName: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Could not write imported cookie ${this.name} for ${this.url}.`;
return `Could not write imported cookie ${this.cookieName} for ${this.url}.`;
}
}

Expand Down Expand Up @@ -167,7 +167,8 @@ export const writeCookies = Effect.fn("BrowserImport.writeCookies")(function* (
sameSite: cookie.sameSite,
...(cookie.expirationDate === undefined ? {} : { expirationDate: cookie.expirationDate }),
}),
catch: (cause) => new BrowserCookieWriteError({ url: cookie.url, name: cookie.name, cause }),
catch: (cause) =>
new BrowserCookieWriteError({ url: cookie.url, cookieName: cookie.name, cause }),
}).pipe(
Effect.as(true),
Effect.tapError((error) => Effect.logDebug(error.message, { cause: error.cause })),
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/components/PdfPreview.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ const preparedPdfAtom = Atom.family((uri: string) =>
},
catch: (cause) => (cause instanceof Error ? cause.message : "The PDF could not be loaded."),
}).pipe(
Effect.flatMap((file) => (file === null ? Effect.interrupt : Effect.succeed(file))),
Effect.filterOrElse(
(file) => file !== null,
() => Effect.interrupt,
),
Effect.interruptible,
),
(file) => Effect.sync(file.dispose),
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/connection/environment-cache-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ function makeDatabase() {
}),
clearAllCaches: Effect.sync(() => values.clear()),
inspectCaches: Effect.succeed([]),
loadPreferencesJson: Effect.succeed(Option.none()),
loadPreferencesJson: Effect.succeedNone,
savePreferencesJson: () => Effect.void,
});
return { database, removed, schemaVersions, values };
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/connection/environment-cache-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ function mapDatabaseError(operation: CacheOperation) {
return (error: MobileDatabase.MobileDatabaseError) => persistenceError(operation, error);
}

function loadDecodedCache<A, B>(input: {
function loadDecodedCache<A, B, E>(input: {
readonly database: MobileDatabase.MobileDatabase["Service"];
readonly environmentId: EnvironmentId;
readonly kind: MobileDatabase.ClientCacheKind;
readonly cacheKey: string;
readonly operation: CacheOperation;
readonly decode: (raw: string) => Effect.Effect<A, unknown>;
readonly decode: (raw: string) => Effect.Effect<A, E>;
readonly select: (value: A) => Option.Option<B>;
}): Effect.Effect<Option.Option<B>, Persistence.ConnectionPersistenceError> {
return input.database.loadCache(input.environmentId, input.kind, input.cacheKey).pipe(
Expand All @@ -81,7 +81,7 @@ function loadDecodedCache<A, B>(input: {
Effect.andThen(
input.database
.removeCache(input.environmentId, input.kind, input.cacheKey)
.pipe(Effect.catch(() => Effect.void)),
.pipe(Effect.ignore),
),
Effect.as(Option.none<B>()),
),
Expand Down
17 changes: 10 additions & 7 deletions apps/mobile/src/connection/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,19 @@ function migrateConnection(
);
}

const decodeLegacyCatalogJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));

export const migrateLegacyConnectionCatalog = Effect.fn(
"mobile.connectionMigration.migrateCatalog",
)(function* (raw: string) {
const parsed = yield* Effect.try({
try: () => JSON.parse(raw) as unknown,
catch: (cause) =>
new LegacyConnectionMigrationError({
message: `Could not parse the legacy mobile connection catalog: ${String(cause)}`,
}),
});
const parsed = yield* decodeLegacyCatalogJson(raw).pipe(
Effect.mapError(
(cause) =>
new LegacyConnectionMigrationError({
message: `Could not parse the legacy mobile connection catalog: ${String(cause)}`,
}),
),
);
const legacy = yield* decodeLegacyConnectionDocument(parsed).pipe(
Effect.mapError(
(cause) =>
Expand Down
30 changes: 15 additions & 15 deletions apps/mobile/src/connection/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Wakeups,
} from "@t3tools/client-runtime/connection";
import { managedRelayAccountChanges, managedRelaySessionAtom } from "@t3tools/client-runtime/relay";
import * as Clock from "effect/Clock";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -120,8 +121,9 @@ const layerWakeups = Wakeups.layer({
[
Stream.callback<"application-active-probe" | "application-active-long">((queue) =>
Effect.acquireRelease(
Effect.sync(() => {
let backgroundedAtMs = AppState.currentState === "background" ? Date.now() : null;
Effect.gen(function* () {
const nowMs = yield* Clock.currentTimeMillis;
let backgroundedAtMs = AppState.currentState === "background" ? nowMs : null;
return AppState.addEventListener("change", (state) => {
if (state === "background") {
backgroundedAtMs = Date.now();
Expand Down Expand Up @@ -187,7 +189,7 @@ const layerCapabilities = Layer.effectContext(
Context.add(
ClientCapabilities.PrimaryEnvironmentAuth,
ClientCapabilities.PrimaryEnvironmentAuth.of({
bearerToken: Effect.succeed(Option.none()),
bearerToken: Effect.succeedNone,
}),
),
// The relay device id already persists in secure storage; analytics only sends its hash.
Expand All @@ -205,7 +207,7 @@ const layerCapabilities = Layer.effectContext(
detail: `Could not load the mobile device identity: ${String(cause)}`,
}),
),
Effect.map(Option.some),
Effect.asSome,
),
}),
),
Expand All @@ -218,21 +220,25 @@ const layerCapabilities = Layer.effectContext(
Context.add(
ClientCapabilities.SshEnvironmentGateway,
ClientCapabilities.SshEnvironmentGateway.of({
provision: () =>
provision: Effect.fn("ClientCapabilities.SshEnvironmentGateway.provision")(() =>
Effect.fail(
new ConnectionBlockedError({
reason: "unsupported",
detail: "SSH environments are only available in the desktop app.",
}),
),
prepare: () =>
),
prepare: Effect.fn("ClientCapabilities.SshEnvironmentGateway.prepare")(() =>
Effect.fail(
new ConnectionBlockedError({
reason: "unsupported",
detail: "SSH environments are only available in the desktop app.",
}),
),
disconnect: () => Effect.void,
),
disconnect: Effect.fn("ClientCapabilities.SshEnvironmentGateway.disconnect")(
() => Effect.void,
),
}),
),
);
Expand All @@ -252,22 +258,16 @@ const layerProvidedCapabilities = layerCapabilities.pipe(Layer.provide(Runtime.l
const layerEnvironmentOwnedDataCleanup = Layer.succeed(
Persistence.EnvironmentOwnedDataCleanup,
Persistence.EnvironmentOwnedDataCleanup.of({
clear: (environmentId) =>
clear: Effect.fn("Persistence.EnvironmentOwnedDataCleanup.clear")((environmentId) =>
Effect.all(
[
Effect.promise(() => clearThreadOutboxEnvironment(environmentId)),
Effect.promise(() => clearComposerDraftsEnvironment(environmentId)),
Effect.sync(() => clearThreadComposerErrorsForEnvironment(environmentId)),
],
{ concurrency: "unbounded", discard: true },
).pipe(
Effect.catch((cause) =>
Effect.logWarning("Could not clear mobile environment-owned data.", {
environmentId,
cause,
}),
),
),
),
}),
);

Expand Down
39 changes: 24 additions & 15 deletions apps/mobile/src/connection/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,33 @@ export const layer = Layer.effectContext(
),
});
const registrationStore = Persistence.ConnectionRegistrationStore.of({
register: (registration, routes) =>
catalog
.update((document) => registerConnectionInCatalog(document, registration, routes))
.pipe(Effect.mapError((error) => targetPersistenceError("register-connection", error))),
setRoutes: (environmentId, routes) =>
catalog
.update((document) => setRoutesInCatalog(document, environmentId, routes))
.pipe(Effect.mapError((error) => targetPersistenceError("set-connection-routes", error))),
remove: (environmentId) =>
register: Effect.fn("Persistence.ConnectionRegistrationStore.register")(
(registration, routes) =>
catalog
.update((document) => registerConnectionInCatalog(document, registration, routes))
.pipe(Effect.mapError((error) => targetPersistenceError("register-connection", error))),
),
setRoutes: Effect.fn("Persistence.ConnectionRegistrationStore.setRoutes")(
(environmentId, routes) =>
catalog
.update((document) => setRoutesInCatalog(document, environmentId, routes))
.pipe(
Effect.mapError((error) => targetPersistenceError("set-connection-routes", error)),
),
),
remove: Effect.fn("Persistence.ConnectionRegistrationStore.remove")((environmentId) =>
catalog
.update((document) => removeConnectionFromCatalog(document, environmentId))
.pipe(Effect.mapError((error) => targetPersistenceError("remove-connection", error))),
setEnabled: (environmentId, enabled) =>
catalog
.update((document) => setConnectionEnabledInCatalog(document, environmentId, enabled))
.pipe(
Effect.mapError((error) => targetPersistenceError("set-connection-enabled", error)),
),
),
setEnabled: Effect.fn("Persistence.ConnectionRegistrationStore.setEnabled")(
(environmentId, enabled) =>
catalog
.update((document) => setConnectionEnabledInCatalog(document, environmentId, enabled))
.pipe(
Effect.mapError((error) => targetPersistenceError("set-connection-enabled", error)),
),
),
});
const profileStore = ProfileStore.make({
get: (connectionId) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
releaseAgentAwarenessRelayTokenProvider,
setAgentAwarenessRelayTokenProvider,
updateAgentAwarenessRegistrationPreferences,
type AgentAwarenessRegistrationError,
} from "./remoteRegistration";
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";
Expand Down Expand Up @@ -216,7 +217,11 @@ const runBackgroundOperations = Effect.fn("TestRemoteRegistration.runBackgroundO
}
idlePasses = 0;
const exit = yield* Effect.exit(
pending.operation as Effect.Effect<unknown, unknown, ManagedRelay.ManagedRelayClient>,
pending.operation as Effect.Effect<
unknown,
AgentAwarenessRegistrationError,
ManagedRelay.ManagedRelayClient
>,
);
yield* Effect.sync(() => {
pending.resolve(exit);
Expand Down Expand Up @@ -394,7 +399,11 @@ describe("makeRelayDeviceRegistrationRequest", () => {
// activity lock, or, without one, at its device-id lookup.
const rotatedRegistration = yield* Effect.forkChild(
Effect.exit(
rotated.operation as Effect.Effect<unknown, unknown, ManagedRelay.ManagedRelayClient>,
rotated.operation as Effect.Effect<
unknown,
AgentAwarenessRegistrationError,
ManagedRelay.ManagedRelayClient
>,
),
{ startImmediately: true },
);
Expand Down
Loading
Loading