Skip to content
Source
Sign inSign up

background-activity-scopes.ts

91 lines2.9 KB

1import type { EnvironmentRpcSubscriptionObservation } from "@t3tools/client-runtime/rpc";
2import { type BackgroundScope, type EnvironmentId, WS_METHODS } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4​
5interface RetainedScope {
6 readonly environmentId: EnvironmentId;
7 readonly scope: BackgroundScope;
8 refCount: number;
9}
10​
11const retainedScopes = new Map<string, RetainedScope>();
12const listeners = new Set<() => void>();
13​
14function notify(): void {
15 for (const listener of listeners) {
16 try {
17 listener();
18 } catch {}
19 }
21​
22function stableScopeKey(environmentId: EnvironmentId, scope: BackgroundScope): string {
23 switch (scope.type) {
24 case "server-config":
25 case "diagnostics":
26 return JSON.stringify([environmentId, scope.type]);
27 case "provider-status":
28 return JSON.stringify([environmentId, scope.type, scope.instanceId ?? null]);
29 case "vcs-status":
30 case "git-refs":
31 return JSON.stringify([environmentId, scope.type, scope.cwd]);
32 case "thread":
33 return JSON.stringify([environmentId, scope.type, scope.threadId]);
34 }
36​
37function scopeForSubscription(
38 observation: EnvironmentRpcSubscriptionObservation,
39): BackgroundScope | null {
40 if (observation.method === WS_METHODS.subscribeResourceTelemetry) {
41 return { type: "diagnostics" };
42 }
43 if (observation.method !== WS_METHODS.subscribeVcsStatus) {
44 return null;
45 }
46 const input = observation.input as { readonly cwd?: unknown; readonly includeRemote?: unknown };
47 if (input.includeRemote === false) return null;
48 return typeof input.cwd === "string" ? { type: "vcs-status", cwd: input.cwd } : null;
50​
51export function retainedMobileBackgroundScopes(
52 environmentId: EnvironmentId,
53): ReadonlyArray<BackgroundScope> {
54 return Array.from(retainedScopes.values(), (entry) =>
55 entry.environmentId === environmentId ? entry.scope : null,
56 ).filter((scope): scope is BackgroundScope => scope !== null);
58​
59export function observeMobileBackgroundActivitySubscription(
60 observation: EnvironmentRpcSubscriptionObservation,
61): Effect.Effect<Effect.Effect<void>> {
62 const scope = scopeForSubscription(observation);
63 if (scope === null) return Effect.succeed(Effect.void);
64 return Effect.sync(() => {
65 const environmentId = observation.environmentId as EnvironmentId;
66 const key = stableScopeKey(environmentId, scope);
67 const current = retainedScopes.get(key);
68 if (current) {
69 current.refCount += 1;
70 } else {
71 retainedScopes.set(key, { environmentId, scope, refCount: 1 });
72 notify();
73 }
74 return Effect.sync(() => {
75 const retained = retainedScopes.get(key);
76 if (!retained) return;
77 retained.refCount -= 1;
78 if (retained.refCount <= 0) {
79 retainedScopes.delete(key);
80 notify();
81 }
82 });
83 });
85​
86export function onRetainedMobileBackgroundScopesChange(listener: () => void): () => void {
87 listeners.add(listener);
88 return () => {
89 listeners.delete(listener);
90 };