dsh-mobile 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.mts CHANGED
@@ -4,6 +4,7 @@ import { ChildProcessWithoutNullStreams } from "node:child_process";
4
4
  import { IncomingMessage } from "node:http";
5
5
  import { Readable } from "node:stream";
6
6
  import { Context, Service } from "@deepseek-ai/cordis";
7
+ import "zod";
7
8
  import { WebRoute } from "@deepseek-ai/dsh-host-webserver";
8
9
  //#region src/storage.d.ts
9
10
  /** Persistent record containing only a digest of the long-lived device credential. */
@@ -106,6 +107,13 @@ interface DeviceProbeResult {
106
107
  }
107
108
  /** Why a short-lived Session ended; only device revocation is sent to clients. */
108
109
  type SessionEndReason = 'logout' | 'expired' | 'evicted' | 'revoked';
110
+ /** Minimal persistent-device metadata safe for notification authorization decisions. */
111
+ interface PersistentDeviceAuthorization {
112
+ readonly id: string;
113
+ readonly expiresAt: number;
114
+ }
115
+ /** Why a persistent-device removal is durably committed. */
116
+ type DeviceRemovalReason = 'revoked' | 'reset';
109
117
  /** Safe device metadata returned by the loopback administration API. */
110
118
  interface DeviceSummary {
111
119
  readonly id: string;
@@ -140,6 +148,7 @@ export declare class AccessController {
140
148
  private pairPending;
141
149
  private readonly sessions;
142
150
  private readonly sessionEndedListeners;
151
+ private readonly devicesRemovedListeners;
143
152
  private mutation;
144
153
  private initialized;
145
154
  private closing;
@@ -152,6 +161,7 @@ export declare class AccessController {
152
161
  private snapshot;
153
162
  private nativeIdentity;
154
163
  private emitSessionEnded;
164
+ private emitDevicesRemoved;
155
165
  private removeSession;
156
166
  private pruneSessions;
157
167
  private createSession;
@@ -181,6 +191,11 @@ export declare class AccessController {
181
191
  revokeDevice(deviceId: string): Promise<boolean>;
182
192
  /** Remove every persistent credential and terminate every active Session. */
183
193
  resetDevices(): Promise<void>;
194
+ /**
195
+ * Read fresh persistent-device authorization metadata without changing credentials,
196
+ * last-seen state, Session state, or the backing store.
197
+ */
198
+ authorizedDevices(): Promise<readonly PersistentDeviceAuthorization[]>;
184
199
  /** Safe metadata for the loopback administration surface. */
185
200
  listDevices(): readonly DeviceSummary[];
186
201
  /** Pairing status without exposing the one-time secret. */
@@ -190,6 +205,8 @@ export declare class AccessController {
190
205
  };
191
206
  /** Subscribe gateway resources to Session logout, expiry, eviction, and device revocation. */
192
207
  onSessionEnded(listener: (authorization: SessionAuthorization, reason: SessionEndReason) => void): () => void;
208
+ /** Subscribe to durably committed removal of persistent-device authorization. */
209
+ onDevicesRemoved(listener: (ids: readonly string[], reason: DeviceRemovalReason) => void): () => void;
193
210
  /** Stop new operations, drain durable mutations, then clear volatile credentials. */
194
211
  close(): Promise<void>;
195
212
  private finishClose;
@@ -995,6 +1012,91 @@ export declare class MobileAccessService extends Service {
995
1012
  /** Construct the service in a Cordis plugin without importing DSH internals. */
996
1013
  export declare function createMobileAccessService(ctx: Context): MobileAccessService;
997
1014
  //#endregion
1015
+ //#region src/mobile-push/contracts.d.ts
1016
+ type PushCategory = 'question' | 'plan' | 'approval' | 'response' | 'goal' | 'failure' | 'test';
1017
+ type PushPreferences = Readonly<Record<Exclude<PushCategory, 'test'>, boolean>>;
1018
+ type PushLocale = 'en' | 'zh-CN' | 'it';
1019
+ interface PushMutationGuard {
1020
+ readonly ownerGeneration: number;
1021
+ readonly registrationGeneration: number;
1022
+ }
1023
+ interface PushOwner {
1024
+ readonly scopeId: string;
1025
+ readonly deviceId: string;
1026
+ readonly origin: string;
1027
+ }
1028
+ interface PushRecipientKey extends PushOwner {
1029
+ readonly installationId: string;
1030
+ }
1031
+ interface PushSubscriptionData {
1032
+ readonly endpoint: string;
1033
+ readonly keys: {
1034
+ readonly p256dh: string;
1035
+ readonly auth: string;
1036
+ };
1037
+ }
1038
+ interface PushRegistration extends PushRecipientKey {
1039
+ readonly subscription: PushSubscriptionData;
1040
+ readonly locale: PushLocale;
1041
+ readonly preferences: PushPreferences;
1042
+ readonly generation: number;
1043
+ readonly keyFingerprint: string;
1044
+ readonly enabled: boolean;
1045
+ readonly health: 'ready' | 'suspended' | 'repair-required';
1046
+ }
1047
+ interface PushTarget {
1048
+ readonly sessionId: string;
1049
+ readonly interaction: 'current' | 'resolved';
1050
+ }
1051
+ interface PushEnrollment extends PushMutationGuard {
1052
+ readonly installationId: string;
1053
+ readonly subscription: PushSubscriptionData;
1054
+ readonly locale: PushLocale;
1055
+ readonly preferences: PushPreferences;
1056
+ readonly keyFingerprint: string;
1057
+ }
1058
+ interface PushEnrollmentResult {
1059
+ readonly registration: PushRegistration;
1060
+ readonly mutationGeneration?: number;
1061
+ }
1062
+ interface PushConfig {
1063
+ readonly available: boolean;
1064
+ readonly publicKey?: string;
1065
+ readonly keyFingerprint?: string;
1066
+ readonly installationId: string;
1067
+ readonly ownerGeneration: number;
1068
+ readonly registrationGeneration: number;
1069
+ readonly status: 'disabled' | 'enabled' | 'suspended' | 'repair-required';
1070
+ readonly preferences: PushPreferences;
1071
+ }
1072
+ //#endregion
1073
+ //#region src/mobile-push/gateway.d.ts
1074
+ /** Trusted per-Gateway binding; authorization is freshly loaded from durable device storage. */
1075
+ interface PushGatewayOriginBinding {
1076
+ readonly scopeId: string;
1077
+ readonly origin: string;
1078
+ readonly authorizedDevices: () => Promise<readonly PersistentDeviceAuthorization[]>;
1079
+ }
1080
+ /** Authenticated Gateway facade; ownership fields are derived by trusted server code. */
1081
+ interface PushGatewayService {
1082
+ /** Attaches one exact scope/origin lane and returns its idempotent detach disposer. */
1083
+ attachOrigin?(binding: PushGatewayOriginBinding): () => void;
1084
+ /** Persistently removes notification state after a durable device-auth removal. */
1085
+ invalidateDevices?(scopeId: string, deviceIds: readonly string[]): Promise<void>;
1086
+ /** Explicit desktop-admin key reset; does not mutate pairing/device authorization. */
1087
+ reset?(): Promise<void>;
1088
+ config(owner: PushOwner, installationId: string): Promise<PushConfig>;
1089
+ enroll(owner: PushOwner, input: PushEnrollment): Promise<PushEnrollmentResult>;
1090
+ preferences(owner: PushOwner, installationId: string, value: PushPreferences, guard: PushMutationGuard): Promise<PushRegistration>;
1091
+ /** No generation is an unconditional owner disable; ifGeneration is stale-attempt cleanup only. */
1092
+ disable(owner: PushOwner, installationId: string, ifGeneration?: number): Promise<void>;
1093
+ /** Explicit logout disables persistent consent before the session is cleared. */
1094
+ logout(owner: PushOwner): Promise<void>;
1095
+ /** Resolves only an owned, still-valid opaque reference. */
1096
+ test(owner: PushOwner, installationId: string): Promise<void>;
1097
+ resolve(owner: PushOwner, reference: string): Promise<PushTarget | undefined>;
1098
+ }
1099
+ //#endregion
998
1100
  //#region src/gateway.d.ts
999
1101
  /** A graph-selection conflict with a bounded diagnostic safe for authenticated callers. */
1000
1102
  export declare class ClientModuleConflictError extends HttpError {
@@ -1011,6 +1113,7 @@ export declare class MobileAccessGateway {
1011
1113
  private readonly extraWebSocketPaths?;
1012
1114
  private readonly blockedUpgradeLog?;
1013
1115
  private readonly onDiscoveryDegraded?;
1116
+ private readonly pushGateway?;
1014
1117
  readonly access: AccessController;
1015
1118
  private readonly listenerTlsEnabled;
1016
1119
  private readonly tlsEnabled;
@@ -1058,6 +1161,10 @@ export declare class MobileAccessGateway {
1058
1161
  private started;
1059
1162
  private closeTask;
1060
1163
  private readonly removeSessionListener;
1164
+ private readonly removePushDeviceListener;
1165
+ private detachPushOrigin;
1166
+ private readonly pushDeviceRevocations;
1167
+ private readonly pushTestsInFlight;
1061
1168
  private readonly removeExtensionContentListener;
1062
1169
  private readonly renewFailureLimiter;
1063
1170
  private readonly renewDeviceLimiter;
@@ -1079,7 +1186,7 @@ export declare class MobileAccessGateway {
1079
1186
  private readonly upstreamAuthDiagnostics;
1080
1187
  constructor(config: ResolvedGatewayConfig, store: DeviceStore, extensions?: MobileAccessService | undefined, upstreamAuthenticatedUrl?: string | undefined, extraWebSocketPaths?: {
1081
1188
  has(pathname: string): boolean;
1082
- } | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void, httpFailureLog?: (entry: HttpFailureEntry) => void, upstreamAuthLog?: (entry: UpstreamAuthEntry) => void, apiLatencyLog?: (entry: ApiLatencyEntry) => void);
1189
+ } | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void, httpFailureLog?: (entry: HttpFailureEntry) => void, upstreamAuthLog?: (entry: UpstreamAuthEntry) => void, apiLatencyLog?: (entry: ApiLatencyEntry) => void, pushGateway?: PushGatewayService | undefined);
1083
1190
  /** Initialize durable state, validate TLS, and bind the externally reachable listener. */
1084
1191
  start(): Promise<void>;
1085
1192
  /** Install a newly signed provided leaf for future TLS handshakes without dropping sessions. */
@@ -1127,6 +1234,8 @@ export declare class MobileAccessGateway {
1127
1234
  private handleNativePair;
1128
1235
  private handleNativeRenew;
1129
1236
  private handleNativeProbe;
1237
+ private requireFreshPushOwner;
1238
+ private handleMobilePushRequest;
1130
1239
  private handleLogout;
1131
1240
  private handleExternalRequest;
1132
1241
  private handleExtensionRequest;
@@ -1200,6 +1309,20 @@ export declare class MobileAccessGateway {
1200
1309
  * cached for a short window.
1201
1310
  */
1202
1311
  private upstreamBundleSize;
1312
+ /**
1313
+ * Name the compatibility bundle by content, so a reload asks for it by revision.
1314
+ *
1315
+ * The digest is computed from the bytes on every page load rather than memoised on
1316
+ * a modification-time signature: an edit that keeps the length and restores the
1317
+ * timestamp (a build step preserving mtime, a same-size patch) is invisible to such
1318
+ * a signature, and the document would then name a revision the route refuses with
1319
+ * `404` — losing the shim exactly on the old WebViews it exists for. Hashing the
1320
+ * ~78 KB bundle costs far less than the page load it belongs to.
1321
+ *
1322
+ * A missing bundle keeps the plain path, where the route reports the usual `503`
1323
+ * instead of handing the document a URL that cannot exist.
1324
+ */
1325
+ private mobileCompatibilityUrl;
1203
1326
  private serveMobileBootBatch;
1204
1327
  private startMobileBootBatchAssembly;
1205
1328
  private assembleMobileBootBatch;