@blue-soda/dsh-remote 0.4.28

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.
Files changed (225) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +81 -0
  3. package/bin/ds-harness-remote.js +11 -0
  4. package/cordis.patch.yml +32 -0
  5. package/dist/acp.d.ts +75 -0
  6. package/dist/acp.d.ts.map +1 -0
  7. package/dist/acp.js +61 -0
  8. package/dist/acp.js.map +1 -0
  9. package/dist/api-proxy-switch.d.ts +33 -0
  10. package/dist/api-proxy-switch.d.ts.map +1 -0
  11. package/dist/api-proxy-switch.js +109 -0
  12. package/dist/api-proxy-switch.js.map +1 -0
  13. package/dist/bin/doctor.d.ts +3 -0
  14. package/dist/bin/doctor.d.ts.map +1 -0
  15. package/dist/bin/doctor.js +13 -0
  16. package/dist/bin/doctor.js.map +1 -0
  17. package/dist/cli.d.ts +32 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +289 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/client-runtime.d.ts +239 -0
  22. package/dist/client-runtime.d.ts.map +1 -0
  23. package/dist/client-runtime.js +1391 -0
  24. package/dist/client-runtime.js.map +1 -0
  25. package/dist/client-secure-transport.d.ts +23 -0
  26. package/dist/client-secure-transport.d.ts.map +1 -0
  27. package/dist/client-secure-transport.js +143 -0
  28. package/dist/client-secure-transport.js.map +1 -0
  29. package/dist/client.d.ts +13 -0
  30. package/dist/client.d.ts.map +1 -0
  31. package/dist/client.github.js +4231 -0
  32. package/dist/client.js +4231 -0
  33. package/dist/codex/app-server.d.ts +71 -0
  34. package/dist/codex/app-server.d.ts.map +1 -0
  35. package/dist/codex/app-server.js +268 -0
  36. package/dist/codex/app-server.js.map +1 -0
  37. package/dist/codex/codex-image-codec.d.ts +7 -0
  38. package/dist/codex/codex-image-codec.d.ts.map +1 -0
  39. package/dist/codex/codex-image-codec.js +288 -0
  40. package/dist/codex/codex-image-codec.js.map +1 -0
  41. package/dist/codex/domain.d.ts +93 -0
  42. package/dist/codex/domain.d.ts.map +1 -0
  43. package/dist/codex/domain.js +1282 -0
  44. package/dist/codex/domain.js.map +1 -0
  45. package/dist/codex/method-policy.d.ts +338 -0
  46. package/dist/codex/method-policy.d.ts.map +1 -0
  47. package/dist/codex/method-policy.js +141 -0
  48. package/dist/codex/method-policy.js.map +1 -0
  49. package/dist/codex/peer-bridge.d.ts +57 -0
  50. package/dist/codex/peer-bridge.d.ts.map +1 -0
  51. package/dist/codex/peer-bridge.js +299 -0
  52. package/dist/codex/peer-bridge.js.map +1 -0
  53. package/dist/codex/permissions.d.ts +4 -0
  54. package/dist/codex/permissions.d.ts.map +1 -0
  55. package/dist/codex/permissions.js +21 -0
  56. package/dist/codex/permissions.js.map +1 -0
  57. package/dist/codex/session-id.d.ts +10 -0
  58. package/dist/codex/session-id.d.ts.map +1 -0
  59. package/dist/codex/session-id.js +15 -0
  60. package/dist/codex/session-id.js.map +1 -0
  61. package/dist/codex/virtual-harness.d.ts +209 -0
  62. package/dist/codex/virtual-harness.d.ts.map +1 -0
  63. package/dist/codex/virtual-harness.js +2616 -0
  64. package/dist/codex/virtual-harness.js.map +1 -0
  65. package/dist/codex-workspace-bridge.d.ts +108 -0
  66. package/dist/codex-workspace-bridge.d.ts.map +1 -0
  67. package/dist/codex-workspace-bridge.js +502 -0
  68. package/dist/codex-workspace-bridge.js.map +1 -0
  69. package/dist/config.d.ts +99 -0
  70. package/dist/config.d.ts.map +1 -0
  71. package/dist/config.js +127 -0
  72. package/dist/config.js.map +1 -0
  73. package/dist/connection-controller.d.ts +44 -0
  74. package/dist/connection-controller.d.ts.map +1 -0
  75. package/dist/connection-controller.js +185 -0
  76. package/dist/connection-controller.js.map +1 -0
  77. package/dist/control-route.d.ts +34 -0
  78. package/dist/control-route.d.ts.map +1 -0
  79. package/dist/control-route.js +136 -0
  80. package/dist/control-route.js.map +1 -0
  81. package/dist/control-runtime.d.ts +138 -0
  82. package/dist/control-runtime.d.ts.map +1 -0
  83. package/dist/control-runtime.js +543 -0
  84. package/dist/control-runtime.js.map +1 -0
  85. package/dist/control-stream.d.ts +67 -0
  86. package/dist/control-stream.d.ts.map +1 -0
  87. package/dist/control-stream.js +179 -0
  88. package/dist/control-stream.js.map +1 -0
  89. package/dist/defaults.d.ts +9 -0
  90. package/dist/defaults.d.ts.map +1 -0
  91. package/dist/defaults.js +9 -0
  92. package/dist/defaults.js.map +1 -0
  93. package/dist/file-viewer-bridge.d.ts +30 -0
  94. package/dist/file-viewer-bridge.d.ts.map +1 -0
  95. package/dist/file-viewer-bridge.js +128 -0
  96. package/dist/file-viewer-bridge.js.map +1 -0
  97. package/dist/file-viewer-contract.d.ts +5 -0
  98. package/dist/file-viewer-contract.d.ts.map +1 -0
  99. package/dist/file-viewer-contract.js +4 -0
  100. package/dist/file-viewer-contract.js.map +1 -0
  101. package/dist/harness-api-bridge.d.ts +79 -0
  102. package/dist/harness-api-bridge.d.ts.map +1 -0
  103. package/dist/harness-api-bridge.js +852 -0
  104. package/dist/harness-api-bridge.js.map +1 -0
  105. package/dist/harness-api-history.d.ts +3 -0
  106. package/dist/harness-api-history.d.ts.map +1 -0
  107. package/dist/harness-api-history.js +59 -0
  108. package/dist/harness-api-history.js.map +1 -0
  109. package/dist/harness-remote-bridge.d.ts +52 -0
  110. package/dist/harness-remote-bridge.d.ts.map +1 -0
  111. package/dist/harness-remote-bridge.js +529 -0
  112. package/dist/harness-remote-bridge.js.map +1 -0
  113. package/dist/harness-version.d.ts +23 -0
  114. package/dist/harness-version.d.ts.map +1 -0
  115. package/dist/harness-version.js +95 -0
  116. package/dist/harness-version.js.map +1 -0
  117. package/dist/identity-store.d.ts +45 -0
  118. package/dist/identity-store.d.ts.map +1 -0
  119. package/dist/identity-store.js +183 -0
  120. package/dist/identity-store.js.map +1 -0
  121. package/dist/ids.d.ts +2 -0
  122. package/dist/ids.d.ts.map +1 -0
  123. package/dist/ids.js +17 -0
  124. package/dist/ids.js.map +1 -0
  125. package/dist/index.d.ts +78 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +28870 -0
  128. package/dist/index.js.map +7 -0
  129. package/dist/logging.d.ts +18 -0
  130. package/dist/logging.d.ts.map +1 -0
  131. package/dist/logging.js +30 -0
  132. package/dist/logging.js.map +1 -0
  133. package/dist/loopback-host.d.ts +20 -0
  134. package/dist/loopback-host.d.ts.map +1 -0
  135. package/dist/loopback-host.js +245 -0
  136. package/dist/loopback-host.js.map +1 -0
  137. package/dist/loopback-preview.d.ts +19 -0
  138. package/dist/loopback-preview.d.ts.map +1 -0
  139. package/dist/loopback-preview.js +232 -0
  140. package/dist/loopback-preview.js.map +1 -0
  141. package/dist/native-rtc-helper.d.ts +14 -0
  142. package/dist/native-rtc-helper.d.ts.map +1 -0
  143. package/dist/native-rtc-helper.js +575 -0
  144. package/dist/native-rtc-helper.js.map +1 -0
  145. package/dist/remote-api-proxy.d.ts +17 -0
  146. package/dist/remote-api-proxy.d.ts.map +1 -0
  147. package/dist/remote-api-proxy.js +400 -0
  148. package/dist/remote-api-proxy.js.map +1 -0
  149. package/dist/remote-directory-browser.d.ts +16 -0
  150. package/dist/remote-directory-browser.d.ts.map +1 -0
  151. package/dist/remote-directory-browser.js +48 -0
  152. package/dist/remote-directory-browser.js.map +1 -0
  153. package/dist/remote-file-content-provider.d.ts +48 -0
  154. package/dist/remote-file-content-provider.d.ts.map +1 -0
  155. package/dist/remote-file-content-provider.js +93 -0
  156. package/dist/remote-file-content-provider.js.map +1 -0
  157. package/dist/remote-typert-gateway.d.ts +20 -0
  158. package/dist/remote-typert-gateway.d.ts.map +1 -0
  159. package/dist/remote-typert-gateway.js +332 -0
  160. package/dist/remote-typert-gateway.js.map +1 -0
  161. package/dist/rpc-router.d.ts +31 -0
  162. package/dist/rpc-router.d.ts.map +1 -0
  163. package/dist/rpc-router.js +201 -0
  164. package/dist/rpc-router.js.map +1 -0
  165. package/dist/safe-error.d.ts +8 -0
  166. package/dist/safe-error.d.ts.map +1 -0
  167. package/dist/safe-error.js +20 -0
  168. package/dist/safe-error.js.map +1 -0
  169. package/dist/server-api.d.ts +109 -0
  170. package/dist/server-api.d.ts.map +1 -0
  171. package/dist/server-api.js +520 -0
  172. package/dist/server-api.js.map +1 -0
  173. package/dist/server-connection.d.ts +79 -0
  174. package/dist/server-connection.d.ts.map +1 -0
  175. package/dist/server-connection.js +1023 -0
  176. package/dist/server-connection.js.map +1 -0
  177. package/dist/server-credentials.d.ts +55 -0
  178. package/dist/server-credentials.d.ts.map +1 -0
  179. package/dist/server-credentials.js +105 -0
  180. package/dist/server-credentials.js.map +1 -0
  181. package/dist/service.d.ts +141 -0
  182. package/dist/service.d.ts.map +1 -0
  183. package/dist/service.js +432 -0
  184. package/dist/service.js.map +1 -0
  185. package/dist/session-format-compat.d.ts +3 -0
  186. package/dist/session-format-compat.d.ts.map +1 -0
  187. package/dist/session-format-compat.js +183 -0
  188. package/dist/session-format-compat.js.map +1 -0
  189. package/dist/status-stream.d.ts +48 -0
  190. package/dist/status-stream.d.ts.map +1 -0
  191. package/dist/status-stream.js +133 -0
  192. package/dist/status-stream.js.map +1 -0
  193. package/dist/terminal-policy.d.ts +21 -0
  194. package/dist/terminal-policy.d.ts.map +1 -0
  195. package/dist/terminal-policy.js +68 -0
  196. package/dist/terminal-policy.js.map +1 -0
  197. package/dist/tui-command.d.ts +13 -0
  198. package/dist/tui-command.d.ts.map +1 -0
  199. package/dist/tui-command.js +390 -0
  200. package/dist/tui-command.js.map +1 -0
  201. package/dist/typert-gateway-contract.d.ts +59 -0
  202. package/dist/typert-gateway-contract.d.ts.map +1 -0
  203. package/dist/typert-gateway-contract.js +2 -0
  204. package/dist/typert-gateway-contract.js.map +1 -0
  205. package/dist/typert-gateway-switch.d.ts +85 -0
  206. package/dist/typert-gateway-switch.d.ts.map +1 -0
  207. package/dist/typert-gateway-switch.js +315 -0
  208. package/dist/typert-gateway-switch.js.map +1 -0
  209. package/dist/types.d.ts +20 -0
  210. package/dist/types.d.ts.map +1 -0
  211. package/dist/types.js +2 -0
  212. package/dist/types.js.map +1 -0
  213. package/dist/version.d.ts +12 -0
  214. package/dist/version.d.ts.map +1 -0
  215. package/dist/version.js +12 -0
  216. package/dist/version.js.map +1 -0
  217. package/dist/werift-rtc.d.ts +121 -0
  218. package/dist/werift-rtc.d.ts.map +1 -0
  219. package/dist/werift-rtc.js +490 -0
  220. package/dist/werift-rtc.js.map +1 -0
  221. package/dsh-plugin.json +41 -0
  222. package/locale/en.json +6 -0
  223. package/locale/zh.json +6 -0
  224. package/package.json +165 -0
  225. package/public.d.ts +26 -0
@@ -0,0 +1,8 @@
1
+ export declare class RpcError extends Error {
2
+ readonly code: string;
3
+ readonly details?: unknown | undefined;
4
+ readonly retryable: boolean;
5
+ constructor(code: string, message: string, details?: unknown | undefined, retryable?: boolean);
6
+ }
7
+ export declare function safeErrorCode(error: unknown): string;
8
+ //# sourceMappingURL=safe-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-error.d.ts","sourceRoot":"","sources":["../src/safe-error.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAS,SAAQ,KAAK;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAAmB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO;IAAE,QAAQ,CAAC,SAAS;gBAA7E,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAW,OAAO,CAAC,EAAE,OAAO,YAAA,EAAW,SAAS,UAAQ;CAC3G;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIpD"}
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export class RpcError extends Error {
3
+ code;
4
+ details;
5
+ retryable;
6
+ constructor(code, message, details, retryable = false) {
7
+ super(message);
8
+ this.code = code;
9
+ this.details = details;
10
+ this.retryable = retryable;
11
+ }
12
+ }
13
+ export function safeErrorCode(error) {
14
+ if (error instanceof RpcError)
15
+ return error.code;
16
+ if (error instanceof z.ZodError)
17
+ return 'INVALID_MESSAGE';
18
+ return 'INTERNAL_ERROR';
19
+ }
20
+ //# sourceMappingURL=safe-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-error.js","sourceRoot":"","sources":["../src/safe-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,OAAO,QAAS,SAAQ,KAAK;IACZ;IAAwC;IAA4B;IAAzF,YAAqB,IAAY,EAAE,OAAe,EAAW,OAAiB,EAAW,YAAY,KAAK;QAAI,KAAK,CAAC,OAAO,CAAC,CAAA;QAAvG,SAAI,GAAJ,IAAI,CAAQ;QAA4B,YAAO,GAAP,OAAO,CAAU;QAAW,cAAS,GAAT,SAAS,CAAQ;IAAmB,CAAC;CAC/H;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,YAAY,QAAQ;QAAE,OAAO,KAAK,CAAC,IAAI,CAAA;IAChD,IAAI,KAAK,YAAY,CAAC,CAAC,QAAQ;QAAE,OAAO,iBAAiB,CAAA;IACzD,OAAO,gBAAgB,CAAA;AACzB,CAAC"}
@@ -0,0 +1,109 @@
1
+ import type { RtcIceServer } from '@dsh-remote/webrtc';
2
+ import type { HostIdentity } from './identity-store.js';
3
+ import { type ServerCredentialStore, type ServerCredentials } from './server-credentials.js';
4
+ export interface OAuthQrSession {
5
+ qrId: string;
6
+ scanUrl: string;
7
+ expiresIn: number;
8
+ }
9
+ export type OAuthProvider = 'wechat' | 'zhihu' | 'github';
10
+ /**
11
+ * QR-login providers this build offers. The self-hosted server implements
12
+ * WeChat; Zhihu and GitHub remain implemented for the hosted server but are not
13
+ * offered here. This is the one place to enable or retire a provider.
14
+ */
15
+ export declare const ENABLED_QR_PROVIDERS: readonly OAuthProvider[];
16
+ /**
17
+ * Whether a caller-supplied string names an offered provider. Narrows the value
18
+ * so downstream calls receive a validated `OAuthProvider` rather than `string`.
19
+ */
20
+ export declare function isEnabledQrProvider(value: string): value is OAuthProvider;
21
+ /** Human-readable provider name for diagnostics and prompts. */
22
+ export declare function oauthProviderName(provider: OAuthProvider): string;
23
+ export type OAuthQrPollResult = {
24
+ status: 'pending' | 'expired';
25
+ } | {
26
+ status: 'complete';
27
+ authorization: DeviceAuthorization;
28
+ };
29
+ export interface DeviceAuthorization {
30
+ method: 'account' | 'host_registration_code' | 'owned_device';
31
+ account?: string;
32
+ expiresAt?: number;
33
+ isAdmin?: boolean;
34
+ }
35
+ export type FetchImplementation = typeof fetch;
36
+ export interface ServerHostDevice {
37
+ deviceId: string;
38
+ name: string;
39
+ platform: string;
40
+ membershipId: string;
41
+ online?: boolean;
42
+ lastSeenAt?: number;
43
+ clientVersion?: string;
44
+ harnessVersion?: string;
45
+ }
46
+ export interface AuthorizedPeerDevice extends ServerHostDevice {
47
+ role: 'host' | 'client';
48
+ identityKey: string;
49
+ }
50
+ export declare class HostServerApi {
51
+ private readonly store;
52
+ private readonly fetchImplementation;
53
+ private readonly role;
54
+ readonly baseUrl: string;
55
+ private identity?;
56
+ private credentials?;
57
+ private credentialsPromise?;
58
+ private harnessVersion?;
59
+ constructor(serverUrl: string, store: ServerCredentialStore, fetchImplementation?: FetchImplementation, role?: 'host' | 'client');
60
+ bindIdentity(identity: HostIdentity): void;
61
+ setHarnessVersion(version: string | undefined): void;
62
+ currentAuthorization(): DeviceAuthorization | undefined;
63
+ /** Check the persisted device credential without issuing or refreshing one. */
64
+ hasStoredAuthorization(): Promise<boolean>;
65
+ clearAuthorization(): Promise<void>;
66
+ revokeCurrentDevice(): Promise<void>;
67
+ authorizeWithAccount(identity: HostIdentity, email: string, password: string): Promise<DeviceAuthorization>;
68
+ /**
69
+ * Authorize this device with the DSH DeepSeek account grant.
70
+ *
71
+ * The grant is forwarded once so the Server can ask the account platform who
72
+ * it belongs to; the Server then discards it and issues its own account
73
+ * session, exactly like a password sign-in.
74
+ */
75
+ authorizeWithDeepSeek(identity: HostIdentity, token: string): Promise<DeviceAuthorization>;
76
+ startOAuthQrLogin(provider?: OAuthProvider): Promise<OAuthQrSession>;
77
+ pollOAuthQrLogin(identity: HostIdentity, qrId: string, recoverIdentity?: () => Promise<HostIdentity>): Promise<OAuthQrPollResult>;
78
+ authorizeHostWithCode(identity: HostIdentity, code: string): Promise<DeviceAuthorization>;
79
+ authorizeOwnedRole(identity: HostIdentity, authorizingAccessToken: string, account?: string): Promise<DeviceAuthorization>;
80
+ authenticate(identity?: HostIdentity): Promise<ServerCredentials>;
81
+ refreshCredentials(rejectedAccessToken?: string | undefined): Promise<ServerCredentials>;
82
+ private withRefreshLock;
83
+ private rotateCredentials;
84
+ listDevices(): Promise<ServerHostDevice[]>;
85
+ deviceFor(peerDeviceId: string): Promise<AuthorizedPeerDevice>;
86
+ turnCredentials(connectionId: string): Promise<RtcIceServer[]>;
87
+ presenceFor(deviceId: string): Promise<{
88
+ online: boolean;
89
+ lastSeenAt?: number;
90
+ }>;
91
+ private loadOrIssue;
92
+ private register;
93
+ private deviceDescriptor;
94
+ private saveTokens;
95
+ private request;
96
+ private publicRequest;
97
+ private requireIdentity;
98
+ }
99
+ export declare class ClientServerApi extends HostServerApi {
100
+ constructor(serverUrl: string, store: ServerCredentialStore, fetchImplementation?: FetchImplementation);
101
+ }
102
+ export declare class ServerApiError extends Error {
103
+ readonly code: string;
104
+ readonly retryable: boolean;
105
+ readonly status?: number | undefined;
106
+ readonly phase?: "credential_refresh" | undefined;
107
+ constructor(code: string, message: string, retryable: boolean, status?: number | undefined, phase?: "credential_refresh" | undefined);
108
+ }
109
+ //# sourceMappingURL=server-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-api.d.ts","sourceRoot":"","sources":["../src/server-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,520 @@
1
+ import { platform } from 'node:os';
2
+ import { fromBase64Url, toBase64Url } from '@dsh-remote/crypto';
3
+ import { deviceTokenPairSchema } from '@dsh-remote/protocol';
4
+ import { ServerCredentialsBusyError } from './server-credentials.js';
5
+ import { normalizeServerUrl } from './config.js';
6
+ import { PLUGIN_VERSION } from './version.js';
7
+ /**
8
+ * QR-login providers this build offers. The self-hosted server implements
9
+ * WeChat; Zhihu and GitHub remain implemented for the hosted server but are not
10
+ * offered here. This is the one place to enable or retire a provider.
11
+ */
12
+ export const ENABLED_QR_PROVIDERS = ['github'];
13
+ /**
14
+ * Whether a caller-supplied string names an offered provider. Narrows the value
15
+ * so downstream calls receive a validated `OAuthProvider` rather than `string`.
16
+ */
17
+ export function isEnabledQrProvider(value) {
18
+ return ENABLED_QR_PROVIDERS.includes(value);
19
+ }
20
+ /** Human-readable provider name for diagnostics and prompts. */
21
+ export function oauthProviderName(provider) {
22
+ return provider === 'github' ? 'GitHub' : provider === 'zhihu' ? 'Zhihu' : 'WeChat';
23
+ }
24
+ const TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
25
+ export class HostServerApi {
26
+ store;
27
+ fetchImplementation;
28
+ role;
29
+ baseUrl;
30
+ identity;
31
+ credentials;
32
+ credentialsPromise;
33
+ harnessVersion;
34
+ constructor(serverUrl, store, fetchImplementation = fetch, role = 'host') {
35
+ this.store = store;
36
+ this.fetchImplementation = fetchImplementation;
37
+ this.role = role;
38
+ this.baseUrl = normalizeServerUrl(serverUrl);
39
+ }
40
+ bindIdentity(identity) { this.identity = identity; }
41
+ setHarnessVersion(version) { this.harnessVersion = version; }
42
+ currentAuthorization() {
43
+ if (this.credentials === undefined)
44
+ return undefined;
45
+ return {
46
+ method: this.credentials.authorizationMethod,
47
+ ...(this.credentials.account === undefined ? {} : { account: this.credentials.account }),
48
+ };
49
+ }
50
+ /** Check the persisted device credential without issuing or refreshing one. */
51
+ async hasStoredAuthorization() {
52
+ const identity = this.requireIdentity();
53
+ return await this.store.load(this.baseUrl, identity.deviceId) !== undefined;
54
+ }
55
+ async clearAuthorization() {
56
+ this.credentials = undefined;
57
+ this.credentialsPromise = undefined;
58
+ await this.store.clear();
59
+ }
60
+ async revokeCurrentDevice() {
61
+ const identity = this.requireIdentity();
62
+ if (await this.store.load(this.baseUrl, identity.deviceId) === undefined) {
63
+ await this.clearAuthorization();
64
+ return;
65
+ }
66
+ try {
67
+ await this.request('/api/v1/devices/self', { method: 'DELETE' });
68
+ }
69
+ finally {
70
+ await this.clearAuthorization();
71
+ }
72
+ }
73
+ async authorizeWithAccount(identity, email, password) {
74
+ this.bindIdentity(identity);
75
+ const account = email.trim();
76
+ if (account.length === 0 || password.length === 0) {
77
+ throw new ServerApiError('INVALID_MESSAGE', 'Email and password are required.', false);
78
+ }
79
+ const login = validateWebLogin(await this.publicRequest('/api/v1/auth/login', {
80
+ method: 'POST',
81
+ body: JSON.stringify({ email: account, password }),
82
+ }));
83
+ await this.register(identity, {
84
+ accountToken: login.token,
85
+ account: login.account,
86
+ authorizationMethod: 'account',
87
+ });
88
+ return {
89
+ method: 'account',
90
+ account: login.account,
91
+ expiresAt: login.expiresAt,
92
+ isAdmin: login.isAdmin,
93
+ };
94
+ }
95
+ /**
96
+ * Authorize this device with the DSH DeepSeek account grant.
97
+ *
98
+ * The grant is forwarded once so the Server can ask the account platform who
99
+ * it belongs to; the Server then discards it and issues its own account
100
+ * session, exactly like a password sign-in.
101
+ */
102
+ async authorizeWithDeepSeek(identity, token) {
103
+ this.bindIdentity(identity);
104
+ if (token.trim().length === 0) {
105
+ throw new ServerApiError('INVALID_MESSAGE', 'A DeepSeek account grant is required.', false);
106
+ }
107
+ const login = validateWebLogin(await this.publicRequest('/api/v1/auth/deepseek', {
108
+ method: 'POST',
109
+ body: JSON.stringify({ token }),
110
+ }));
111
+ await this.register(identity, {
112
+ accountToken: login.token,
113
+ account: login.account,
114
+ authorizationMethod: 'account',
115
+ });
116
+ return {
117
+ method: 'account',
118
+ account: login.account,
119
+ expiresAt: login.expiresAt,
120
+ isAdmin: login.isAdmin,
121
+ };
122
+ }
123
+ async startOAuthQrLogin(provider = 'wechat') {
124
+ const value = requireRecord(await this.publicRequest(`/api/v1/auth/oauth/qr/start?provider=${provider}`, {
125
+ method: 'POST',
126
+ body: '{}',
127
+ }), 'QR login');
128
+ const scanUrl = normalizeOAuthScanUrl(value.scanUrl, this.baseUrl);
129
+ if (typeof value.qrId !== 'string' || value.qrId.length < 20
130
+ || scanUrl === undefined
131
+ || !Number.isSafeInteger(value.expiresIn)
132
+ || (value.provider !== undefined && value.provider !== provider)) {
133
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid QR login session.', false);
134
+ }
135
+ return { qrId: value.qrId, scanUrl, expiresIn: value.expiresIn };
136
+ }
137
+ async pollOAuthQrLogin(identity, qrId, recoverIdentity) {
138
+ this.bindIdentity(identity);
139
+ const value = requireRecord(await this.publicRequest(`/api/v1/auth/oauth/qr/${encodeURIComponent(qrId)}`, { method: 'GET' }), 'QR login status');
140
+ if (value.status === 'pending' || value.status === 'expired')
141
+ return { status: value.status };
142
+ if (value.status !== 'complete' || typeof value.token !== 'string' || value.token.length < 16) {
143
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid QR login status.', false);
144
+ }
145
+ const account = requireRecord(await this.publicRequest('/api/v1/auth/me', { method: 'GET' }, value.token), 'account profile');
146
+ if (typeof account.account !== 'string' || account.account.length === 0 || typeof account.isAdmin !== 'boolean') {
147
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid account profile.', false);
148
+ }
149
+ const authorization = {
150
+ accountToken: value.token,
151
+ account: account.account,
152
+ authorizationMethod: 'account',
153
+ };
154
+ try {
155
+ await this.register(identity, authorization);
156
+ }
157
+ catch (error) {
158
+ if (!(error instanceof ServerApiError) || error.code !== 'DEVICE_REVOKED' || recoverIdentity === undefined)
159
+ throw error;
160
+ const nextIdentity = await recoverIdentity();
161
+ this.bindIdentity(nextIdentity);
162
+ await this.register(nextIdentity, authorization);
163
+ }
164
+ return {
165
+ status: 'complete',
166
+ authorization: { method: 'account', account: account.account, isAdmin: account.isAdmin },
167
+ };
168
+ }
169
+ async authorizeHostWithCode(identity, code) {
170
+ if (this.role !== 'host') {
171
+ throw new ServerApiError('METHOD_NOT_ALLOWED', 'Host registration codes can only authorize a Host device.', false);
172
+ }
173
+ const registrationCode = code.trim().toUpperCase();
174
+ if (registrationCode.length === 0) {
175
+ throw new ServerApiError('INVALID_MESSAGE', 'A Host registration code is required.', false);
176
+ }
177
+ this.bindIdentity(identity);
178
+ const tokens = await this.publicRequest('/api/v1/devices/register-with-code', {
179
+ method: 'POST',
180
+ body: JSON.stringify({ v: 1, code: registrationCode, device: this.deviceDescriptor(identity) }),
181
+ });
182
+ this.credentials = await this.saveTokens(identity, validateTokens(tokens), {
183
+ authorizationMethod: 'host_registration_code',
184
+ });
185
+ return { method: 'host_registration_code' };
186
+ }
187
+ async authorizeOwnedRole(identity, authorizingAccessToken, account) {
188
+ this.bindIdentity(identity);
189
+ const tokens = await this.publicRequest('/api/v1/devices/register-owned-role', {
190
+ method: 'POST',
191
+ body: JSON.stringify({ v: 1, device: this.deviceDescriptor(identity) }),
192
+ }, authorizingAccessToken);
193
+ this.credentials = await this.saveTokens(identity, validateTokens(tokens), {
194
+ authorizationMethod: 'owned_device',
195
+ ...(account === undefined ? {} : { account }),
196
+ });
197
+ return {
198
+ method: 'owned_device',
199
+ ...(account === undefined ? {} : { account }),
200
+ };
201
+ }
202
+ async authenticate(identity = this.requireIdentity()) {
203
+ this.bindIdentity(identity);
204
+ if (this.credentials !== undefined && this.credentials.accessTokenExpiresAt > Date.now() + 30_000) {
205
+ return this.credentials;
206
+ }
207
+ this.credentialsPromise ??= this.loadOrIssue(identity).finally(() => { this.credentialsPromise = undefined; });
208
+ this.credentials = await this.credentialsPromise;
209
+ return this.credentials;
210
+ }
211
+ async refreshCredentials(rejectedAccessToken = this.credentials?.accessToken) {
212
+ const identity = this.requireIdentity();
213
+ this.credentials = await this.withRefreshLock(async () => {
214
+ // Another process may have rotated the token while we waited for the lock.
215
+ const stored = await this.store.load(this.baseUrl, identity.deviceId);
216
+ if (stored === undefined || stored.refreshTokenExpiresAt <= Date.now())
217
+ return this.register(identity);
218
+ if (stored.accessToken !== rejectedAccessToken && stored.accessTokenExpiresAt > Date.now() + 30_000) {
219
+ return stored;
220
+ }
221
+ return this.rotateCredentials(identity, stored);
222
+ });
223
+ return this.credentials;
224
+ }
225
+ async withRefreshLock(operation) {
226
+ try {
227
+ return await this.store.withRefreshLock(operation);
228
+ }
229
+ catch (error) {
230
+ if (error instanceof ServerCredentialsBusyError) {
231
+ throw new ServerApiError(error.code, error.message, false);
232
+ }
233
+ throw error;
234
+ }
235
+ }
236
+ async rotateCredentials(identity, stored) {
237
+ let tokens;
238
+ try {
239
+ tokens = await this.publicRequest('/api/v1/auth/refresh', {
240
+ method: 'POST',
241
+ body: JSON.stringify({ deviceId: identity.deviceId, refreshToken: stored.refreshToken }),
242
+ });
243
+ }
244
+ catch (error) {
245
+ if (error instanceof ServerApiError) {
246
+ throw new ServerApiError(error.code, error.message, error.retryable, error.status, 'credential_refresh');
247
+ }
248
+ throw error;
249
+ }
250
+ return this.store.save({
251
+ serverUrl: this.baseUrl,
252
+ deviceId: identity.deviceId,
253
+ authorizationMethod: stored.authorizationMethod,
254
+ ...(stored.account === undefined ? {} : { account: stored.account }),
255
+ ...validateTokens(tokens),
256
+ });
257
+ }
258
+ async listDevices() {
259
+ const result = await this.request('/api/v1/devices');
260
+ if (!Array.isArray(result.items))
261
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid device list.', false);
262
+ return result.items.map(parseHostDevice);
263
+ }
264
+ async deviceFor(peerDeviceId) {
265
+ const result = await this.request(`/api/v1/devices/${encodeURIComponent(peerDeviceId)}`);
266
+ return parseAuthorizedPeer(result);
267
+ }
268
+ async turnCredentials(connectionId) {
269
+ const result = await this.request(`/api/v1/turn/credentials?connection_id=${encodeURIComponent(connectionId)}`);
270
+ if (!Array.isArray(result.iceServers))
271
+ return [];
272
+ return result.iceServers.map(parseIceServer);
273
+ }
274
+ async presenceFor(deviceId) {
275
+ const result = await this.request(`/api/v1/devices/${encodeURIComponent(deviceId)}/presence`);
276
+ if (typeof result.online !== 'boolean'
277
+ || (result.lastSeenAt !== null && result.lastSeenAt !== undefined && !Number.isSafeInteger(result.lastSeenAt))) {
278
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned invalid device presence.', false);
279
+ }
280
+ return { online: result.online, ...(typeof result.lastSeenAt === 'number' ? { lastSeenAt: result.lastSeenAt } : {}) };
281
+ }
282
+ async loadOrIssue(identity) {
283
+ return this.withRefreshLock(async () => {
284
+ const stored = await this.store.load(this.baseUrl, identity.deviceId);
285
+ if (stored === undefined || stored.refreshTokenExpiresAt <= Date.now() + 30_000) {
286
+ return this.register(identity);
287
+ }
288
+ if (stored.accessTokenExpiresAt > Date.now() + 30_000)
289
+ return stored;
290
+ return this.rotateCredentials(identity, stored);
291
+ });
292
+ }
293
+ async register(identity, authorization) {
294
+ const tokens = await this.publicRequest('/api/v1/devices/register', {
295
+ method: 'POST',
296
+ body: JSON.stringify({
297
+ v: 1,
298
+ device: this.deviceDescriptor(identity),
299
+ }),
300
+ }, authorization?.accountToken);
301
+ this.credentials = await this.saveTokens(identity, validateTokens(tokens), {
302
+ authorizationMethod: authorization?.authorizationMethod ?? 'account',
303
+ ...(authorization?.account === undefined ? {} : { account: authorization.account }),
304
+ });
305
+ return this.credentials;
306
+ }
307
+ deviceDescriptor(identity) {
308
+ return {
309
+ deviceId: identity.deviceId,
310
+ name: identity.name,
311
+ role: this.role,
312
+ platform: platform(),
313
+ identityKey: identity.publicKey,
314
+ clientVersion: PLUGIN_VERSION,
315
+ ...(this.role === 'host' && this.harnessVersion !== undefined ? { harnessVersion: this.harnessVersion } : {}),
316
+ };
317
+ }
318
+ saveTokens(identity, tokens, authorization) {
319
+ return this.store.save({
320
+ serverUrl: this.baseUrl,
321
+ deviceId: identity.deviceId,
322
+ authorizationMethod: authorization.authorizationMethod,
323
+ ...(authorization.account === undefined ? {} : { account: authorization.account }),
324
+ ...tokens,
325
+ });
326
+ }
327
+ async request(path, init = {}) {
328
+ const credentials = await this.authenticate();
329
+ return this.publicRequest(path, init, credentials.accessToken);
330
+ }
331
+ async publicRequest(path, init, accessToken) {
332
+ const controller = new AbortController();
333
+ const timer = setTimeout(() => controller.abort(), 10_000);
334
+ let response;
335
+ try {
336
+ response = await this.fetchImplementation(`${this.baseUrl}${path}`, {
337
+ ...init,
338
+ signal: controller.signal,
339
+ headers: {
340
+ Accept: 'application/json',
341
+ 'Content-Type': 'application/json',
342
+ ...(accessToken === undefined ? {} : { Authorization: `Bearer ${accessToken}` }),
343
+ ...init.headers,
344
+ },
345
+ });
346
+ }
347
+ catch (error) {
348
+ throw new ServerApiError('CONNECTION_FAILED', error instanceof Error ? error.message : 'Server request failed.', true);
349
+ }
350
+ finally {
351
+ clearTimeout(timer);
352
+ }
353
+ const body = await parseBody(response);
354
+ if (!response.ok) {
355
+ const envelope = (body ?? {});
356
+ throw new ServerApiError(typeof envelope.error?.code === 'string' ? envelope.error.code : mapStatus(response.status), typeof envelope.error?.message === 'string' ? envelope.error.message : 'The Server rejected the request.', envelope.error?.retryable === true || response.status >= 500, response.status);
357
+ }
358
+ return body;
359
+ }
360
+ requireIdentity() {
361
+ if (this.identity === undefined)
362
+ throw new ServerApiError('IDENTITY_INVALID', 'The device identity is not loaded.', false);
363
+ return this.identity;
364
+ }
365
+ }
366
+ /**
367
+ * Accept the authorization URL a server hands back for a QR login.
368
+ *
369
+ * A real OAuth provider's page lives on another origin (`github.com`,
370
+ * `open.weixin.qq.com`), so requiring the server's own origin would reject every
371
+ * provider. The guard is the scheme instead: HTTPS, or HTTP when the server
372
+ * itself is loopback. That still keeps `javascript:`, `data:` and obfuscated
373
+ * payloads out of the QR link and the rendered anchor.
374
+ */
375
+ function normalizeOAuthScanUrl(value, baseUrl) {
376
+ if (typeof value !== 'string' || TERMINAL_CONTROL_CHARACTERS.test(value))
377
+ return undefined;
378
+ try {
379
+ const normalized = new URL(value);
380
+ if (normalized.protocol === 'https:')
381
+ return normalized.href;
382
+ if (normalized.protocol !== 'http:')
383
+ return undefined;
384
+ const server = new URL(baseUrl);
385
+ const loopback = server.hostname === 'localhost' || server.hostname === '127.0.0.1' || server.hostname === '::1';
386
+ return loopback ? normalized.href : undefined;
387
+ }
388
+ catch {
389
+ return undefined;
390
+ }
391
+ }
392
+ export class ClientServerApi extends HostServerApi {
393
+ constructor(serverUrl, store, fetchImplementation = fetch) {
394
+ super(serverUrl, store, fetchImplementation, 'client');
395
+ }
396
+ }
397
+ export class ServerApiError extends Error {
398
+ code;
399
+ retryable;
400
+ status;
401
+ phase;
402
+ constructor(code, message, retryable, status, phase) {
403
+ super(message);
404
+ this.code = code;
405
+ this.retryable = retryable;
406
+ this.status = status;
407
+ this.phase = phase;
408
+ }
409
+ }
410
+ function validateTokens(value) {
411
+ const parsed = deviceTokenPairSchema.safeParse(value);
412
+ if (!parsed.success) {
413
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned invalid device credentials.', false);
414
+ }
415
+ return parsed.data;
416
+ }
417
+ function validateWebLogin(value) {
418
+ const item = requireRecord(value, 'account login');
419
+ if (typeof item.token !== 'string' || item.token.length < 16
420
+ || !Number.isSafeInteger(item.expiresAt)
421
+ || typeof item.account !== 'string' || item.account.length === 0 || item.account.length > 254
422
+ || typeof item.isAdmin !== 'boolean') {
423
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid account session.', false);
424
+ }
425
+ return {
426
+ token: item.token,
427
+ expiresAt: item.expiresAt,
428
+ account: item.account,
429
+ profile: item.profile,
430
+ isAdmin: item.isAdmin,
431
+ };
432
+ }
433
+ async function parseBody(response) {
434
+ const text = await response.text();
435
+ if (text.length === 0)
436
+ return undefined;
437
+ try {
438
+ return JSON.parse(text);
439
+ }
440
+ catch {
441
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned invalid JSON.', false, response.status);
442
+ }
443
+ }
444
+ function mapStatus(status) {
445
+ if (status === 401)
446
+ return 'AUTH_INVALID';
447
+ if (status === 403)
448
+ return 'AUTH_REQUIRED';
449
+ if (status === 404)
450
+ return 'DEVICE_NOT_FOUND';
451
+ if (status === 429)
452
+ return 'RATE_LIMITED';
453
+ return status >= 500 ? 'CONNECTION_FAILED' : 'INVALID_MESSAGE';
454
+ }
455
+ function parseHostDevice(value) {
456
+ const item = requireRecord(value, 'host device');
457
+ if (item.role !== 'host' || typeof item.deviceId !== 'string' || typeof item.name !== 'string'
458
+ || typeof item.platform !== 'string' || typeof item.membershipId !== 'string' || item.membershipId.length === 0) {
459
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned invalid host device data.', false);
460
+ }
461
+ return {
462
+ deviceId: item.deviceId,
463
+ name: item.name,
464
+ platform: item.platform,
465
+ membershipId: item.membershipId,
466
+ ...(typeof item.online === 'boolean' ? { online: item.online } : {}),
467
+ ...(typeof item.lastSeenAt === 'number' && Number.isSafeInteger(item.lastSeenAt) ? { lastSeenAt: item.lastSeenAt } : {}),
468
+ ...(typeof item.clientVersion === 'string' ? { clientVersion: item.clientVersion } : {}),
469
+ ...(typeof item.harnessVersion === 'string' ? { harnessVersion: item.harnessVersion } : {}),
470
+ };
471
+ }
472
+ function parseAuthorizedPeer(value) {
473
+ const item = requireRecord(value, 'authorized peer');
474
+ if ((item.role !== 'host' && item.role !== 'client')
475
+ || typeof item.deviceId !== 'string' || item.deviceId.length === 0
476
+ || typeof item.name !== 'string' || item.name.length === 0
477
+ || typeof item.platform !== 'string' || item.platform.length === 0
478
+ || typeof item.identityKey !== 'string' || !isIdentityKey(item.identityKey)
479
+ || typeof item.membershipId !== 'string' || item.membershipId.length === 0) {
480
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned invalid authorized peer data.', false);
481
+ }
482
+ return {
483
+ deviceId: item.deviceId,
484
+ name: item.name,
485
+ role: item.role,
486
+ platform: item.platform,
487
+ identityKey: item.identityKey,
488
+ membershipId: item.membershipId,
489
+ ...(typeof item.online === 'boolean' ? { online: item.online } : {}),
490
+ ...(typeof item.lastSeenAt === 'number' && Number.isSafeInteger(item.lastSeenAt) ? { lastSeenAt: item.lastSeenAt } : {}),
491
+ };
492
+ }
493
+ function parseIceServer(value) {
494
+ const item = requireRecord(value, 'ICE server');
495
+ const urls = item.urls;
496
+ if (typeof urls !== 'string' && !(Array.isArray(urls) && urls.every(url => typeof url === 'string'))) {
497
+ throw new ServerApiError('INVALID_MESSAGE', 'The Server returned an invalid ICE server.', false);
498
+ }
499
+ return {
500
+ urls,
501
+ ...(typeof item.username === 'string' ? { username: item.username } : {}),
502
+ ...(typeof item.credential === 'string' ? { credential: item.credential } : {}),
503
+ };
504
+ }
505
+ function isIdentityKey(value) {
506
+ try {
507
+ const decoded = fromBase64Url(value);
508
+ return decoded.length === 32 && toBase64Url(decoded) === value;
509
+ }
510
+ catch {
511
+ return false;
512
+ }
513
+ }
514
+ function requireRecord(value, name) {
515
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
516
+ throw new ServerApiError('INVALID_MESSAGE', `The Server returned invalid ${name} data.`, false);
517
+ }
518
+ return value;
519
+ }
520
+ //# sourceMappingURL=server-api.js.map