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/CHANGELOG.md +20 -0
- package/CONTRIBUTORS.md +2 -2
- package/README.en.md +33 -12
- package/README.md +33 -12
- package/SECURITY.md +6 -0
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/apps/mobile/README.md +10 -6
- package/apps/mobile/README.zh-CN.md +10 -6
- package/apps/mobile/release.json +3 -3
- package/assets/mobile-push/icon-180.png +0 -0
- package/assets/mobile-push/icon-192.png +0 -0
- package/assets/mobile-push/icon-512.png +0 -0
- package/docs/README.md +2 -0
- package/docs/SLOW_CONNECTIONS.en.md +7 -3
- package/docs/SLOW_CONNECTIONS.md +7 -3
- package/docs/WEB_PUSH.en.md +118 -0
- package/docs/WEB_PUSH.md +118 -0
- package/lib/client.js +1084 -10
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +124 -1
- package/lib/index.mjs +3669 -86
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +12 -2
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +17 -5
- package/packages/question-fixes/package.json +1 -1
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;
|