@the-open-engine/zeroshot 6.28.0 → 6.29.1

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 (283) hide show
  1. package/README.md +3 -3
  2. package/cli/index.js +20 -224
  3. package/docker/zeroshot-cluster/Dockerfile +2 -3
  4. package/lib/cluster/client.cjs +30 -7
  5. package/lib/cluster/client.d.cts +52 -0
  6. package/lib/cluster/client.d.mts +52 -0
  7. package/lib/cluster/client.d.ts +4 -6
  8. package/lib/cluster/client.mjs +32 -9
  9. package/lib/cluster/connection-state.cjs +25 -0
  10. package/lib/cluster/connection-state.d.cts +13 -0
  11. package/lib/cluster/connection-state.d.mts +13 -0
  12. package/lib/cluster/connection-state.d.ts +13 -0
  13. package/lib/cluster/connection-state.mjs +21 -0
  14. package/lib/cluster/connection-types.cjs +2 -0
  15. package/lib/cluster/connection-types.d.cts +33 -0
  16. package/lib/cluster/connection-types.d.mts +33 -0
  17. package/lib/cluster/connection-types.d.ts +33 -0
  18. package/lib/cluster/connection-types.mjs +1 -0
  19. package/lib/cluster/connection.cjs +18 -46
  20. package/lib/cluster/connection.d.cts +26 -0
  21. package/lib/cluster/connection.d.mts +26 -0
  22. package/lib/cluster/connection.d.ts +7 -25
  23. package/lib/cluster/connection.mjs +12 -42
  24. package/lib/cluster/errors.cjs +9 -1
  25. package/lib/cluster/errors.d.cts +29 -0
  26. package/lib/cluster/errors.d.mts +29 -0
  27. package/lib/cluster/errors.d.ts +4 -0
  28. package/lib/cluster/errors.mjs +7 -0
  29. package/lib/cluster/frames.d.cts +12 -0
  30. package/lib/cluster/frames.d.mts +12 -0
  31. package/lib/cluster/generated/protocol-schema.d.cts +1 -0
  32. package/lib/cluster/generated/protocol-schema.d.mts +1 -0
  33. package/lib/cluster/generated/protocol.d.cts +781 -0
  34. package/lib/cluster/generated/protocol.d.mts +781 -0
  35. package/lib/cluster/index.cjs +2 -2
  36. package/lib/cluster/index.d.cts +13 -0
  37. package/lib/cluster/index.d.mts +13 -0
  38. package/lib/cluster/index.d.ts +3 -3
  39. package/lib/cluster/index.mjs +2 -2
  40. package/lib/cluster/json-source.cjs +1 -0
  41. package/lib/cluster/json-source.d.cts +5 -0
  42. package/lib/cluster/json-source.d.mts +5 -0
  43. package/lib/cluster/json-source.d.ts +1 -0
  44. package/lib/cluster/json-source.mjs +1 -0
  45. package/lib/cluster/payload-value.cjs +1 -0
  46. package/lib/cluster/payload-value.d.cts +8 -0
  47. package/lib/cluster/payload-value.d.mts +8 -0
  48. package/lib/cluster/payload-value.d.ts +1 -0
  49. package/lib/cluster/payload-value.mjs +1 -0
  50. package/lib/cluster/queue.d.cts +15 -0
  51. package/lib/cluster/queue.d.mts +15 -0
  52. package/lib/cluster/socket.cjs +8 -0
  53. package/lib/cluster/socket.d.cts +13 -0
  54. package/lib/cluster/socket.d.mts +13 -0
  55. package/lib/cluster/socket.d.ts +2 -0
  56. package/lib/cluster/socket.mjs +7 -0
  57. package/lib/cluster/subscriptions.d.cts +73 -0
  58. package/lib/cluster/subscriptions.d.mts +73 -0
  59. package/lib/cluster/subscriptions.d.ts +5 -1
  60. package/lib/cluster/validators.cjs +1 -0
  61. package/lib/cluster/validators.d.cts +7 -0
  62. package/lib/cluster/validators.d.mts +7 -0
  63. package/lib/cluster/validators.d.ts +1 -0
  64. package/lib/cluster/validators.mjs +1 -0
  65. package/lib/cluster-worker/engine-adapter.js +1 -11
  66. package/lib/cluster-worker/engine-input.js +14 -0
  67. package/lib/hosted-session/authority.cjs +61 -0
  68. package/lib/hosted-session/authority.d.cts +3 -0
  69. package/lib/hosted-session/authority.d.mts +3 -0
  70. package/lib/hosted-session/authority.d.ts +3 -0
  71. package/lib/hosted-session/authority.mjs +57 -0
  72. package/lib/hosted-session/coordinator.cjs +78 -51
  73. package/lib/hosted-session/coordinator.d.cts +11 -0
  74. package/lib/hosted-session/coordinator.d.mts +11 -0
  75. package/lib/hosted-session/coordinator.d.ts +4 -2
  76. package/lib/hosted-session/coordinator.mjs +75 -51
  77. package/lib/hosted-session/errors.cjs +25 -0
  78. package/lib/hosted-session/errors.d.cts +10 -0
  79. package/lib/hosted-session/errors.d.mts +10 -0
  80. package/lib/hosted-session/errors.d.ts +10 -0
  81. package/lib/hosted-session/errors.mjs +19 -0
  82. package/lib/hosted-session/index.cjs +4 -1
  83. package/lib/hosted-session/index.d.cts +2 -0
  84. package/lib/hosted-session/index.d.mts +2 -0
  85. package/lib/hosted-session/index.d.ts +2 -2
  86. package/lib/hosted-session/index.mjs +1 -1
  87. package/lib/hosted-session/reconnecting-watch.cjs +131 -0
  88. package/lib/hosted-session/reconnecting-watch.d.cts +13 -0
  89. package/lib/hosted-session/reconnecting-watch.d.mts +13 -0
  90. package/lib/hosted-session/reconnecting-watch.d.ts +13 -0
  91. package/lib/hosted-session/reconnecting-watch.mjs +127 -0
  92. package/lib/hosted-session/types.d.cts +32 -0
  93. package/lib/hosted-session/types.d.mts +32 -0
  94. package/lib/hosted-session/types.d.ts +19 -7
  95. package/lib/hosted-target/access-url.cjs +44 -0
  96. package/lib/hosted-target/access-url.d.cts +2 -0
  97. package/lib/hosted-target/access-url.d.mts +2 -0
  98. package/lib/hosted-target/access-url.d.ts +2 -0
  99. package/lib/hosted-target/access-url.mjs +41 -0
  100. package/lib/hosted-target/adapter-request.cjs +12 -0
  101. package/lib/hosted-target/adapter-request.d.cts +17 -0
  102. package/lib/hosted-target/adapter-request.d.mts +17 -0
  103. package/lib/hosted-target/adapter-request.d.ts +17 -0
  104. package/lib/hosted-target/adapter-request.mjs +9 -0
  105. package/lib/hosted-target/adapter-types.cjs +2 -0
  106. package/lib/hosted-target/adapter-types.d.cts +27 -0
  107. package/lib/hosted-target/adapter-types.d.mts +27 -0
  108. package/lib/hosted-target/adapter-types.d.ts +27 -0
  109. package/lib/hosted-target/adapter-types.mjs +1 -0
  110. package/lib/hosted-target/bounds.cjs +10 -0
  111. package/lib/hosted-target/bounds.d.cts +7 -0
  112. package/lib/hosted-target/bounds.d.mts +7 -0
  113. package/lib/hosted-target/bounds.d.ts +7 -0
  114. package/lib/hosted-target/bounds.mjs +7 -0
  115. package/lib/hosted-target/capsule-error-response.cjs +76 -0
  116. package/lib/hosted-target/capsule-error-response.d.cts +2 -0
  117. package/lib/hosted-target/capsule-error-response.d.mts +2 -0
  118. package/lib/hosted-target/capsule-error-response.d.ts +2 -0
  119. package/lib/hosted-target/capsule-error-response.mjs +73 -0
  120. package/lib/hosted-target/errors.cjs +88 -0
  121. package/lib/hosted-target/errors.d.cts +36 -0
  122. package/lib/hosted-target/errors.d.mts +36 -0
  123. package/lib/hosted-target/errors.d.ts +36 -0
  124. package/lib/hosted-target/errors.mjs +75 -0
  125. package/lib/hosted-target/index.cjs +16 -0
  126. package/lib/hosted-target/index.d.cts +3 -0
  127. package/lib/hosted-target/index.d.mts +3 -0
  128. package/lib/hosted-target/index.d.ts +3 -0
  129. package/lib/hosted-target/index.mjs +2 -0
  130. package/lib/hosted-target/response-validation.cjs +169 -0
  131. package/lib/hosted-target/response-validation.d.cts +5 -0
  132. package/lib/hosted-target/response-validation.d.mts +5 -0
  133. package/lib/hosted-target/response-validation.d.ts +5 -0
  134. package/lib/hosted-target/response-validation.mjs +163 -0
  135. package/lib/hosted-target/retry-executor.cjs +51 -0
  136. package/lib/hosted-target/retry-executor.d.cts +8 -0
  137. package/lib/hosted-target/retry-executor.d.mts +8 -0
  138. package/lib/hosted-target/retry-executor.d.ts +8 -0
  139. package/lib/hosted-target/retry-executor.mjs +48 -0
  140. package/lib/hosted-target/retry.cjs +78 -0
  141. package/lib/hosted-target/retry.d.cts +9 -0
  142. package/lib/hosted-target/retry.d.mts +9 -0
  143. package/lib/hosted-target/retry.d.ts +9 -0
  144. package/lib/hosted-target/retry.mjs +73 -0
  145. package/lib/hosted-target/target-adapter.cjs +10 -0
  146. package/lib/hosted-target/target-adapter.d.cts +3 -0
  147. package/lib/hosted-target/target-adapter.d.mts +3 -0
  148. package/lib/hosted-target/target-adapter.d.ts +3 -0
  149. package/lib/hosted-target/target-adapter.mjs +7 -0
  150. package/lib/hosted-target/types.cjs +4 -0
  151. package/lib/hosted-target/types.d.cts +50 -0
  152. package/lib/hosted-target/types.d.mts +50 -0
  153. package/lib/hosted-target/types.d.ts +50 -0
  154. package/lib/hosted-target/types.mjs +1 -0
  155. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +185 -0
  156. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +13 -0
  157. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +13 -0
  158. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +13 -0
  159. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +181 -0
  160. package/lib/target/bounded-response.cjs +51 -0
  161. package/lib/target/bounded-response.d.cts +1 -0
  162. package/lib/target/bounded-response.d.mts +1 -0
  163. package/lib/target/bounded-response.d.ts +1 -0
  164. package/lib/target/bounded-response.js +51 -0
  165. package/lib/target/bounded-response.mjs +48 -0
  166. package/lib/target/credential-lock.js +1 -3
  167. package/lib/target/credential-store.js +9 -4
  168. package/lib/target/device-flow.cjs +213 -0
  169. package/lib/target/device-flow.d.cts +54 -0
  170. package/lib/target/device-flow.d.mts +54 -0
  171. package/lib/target/device-flow.d.ts +26 -10
  172. package/lib/target/device-flow.js +162 -53
  173. package/lib/target/device-flow.mjs +203 -0
  174. package/lib/target/discovery-errors.cjs +10 -0
  175. package/lib/target/discovery-errors.d.cts +3 -0
  176. package/lib/target/discovery-errors.d.mts +3 -0
  177. package/lib/target/discovery-errors.d.ts +3 -0
  178. package/lib/target/discovery-errors.js +10 -0
  179. package/lib/target/discovery-errors.mjs +6 -0
  180. package/lib/target/discovery-sections.cjs +191 -0
  181. package/lib/target/discovery-sections.d.cts +52 -0
  182. package/lib/target/discovery-sections.d.mts +52 -0
  183. package/lib/target/discovery-sections.d.ts +52 -0
  184. package/lib/target/discovery-sections.js +191 -0
  185. package/lib/target/discovery-sections.mjs +179 -0
  186. package/lib/target/discovery-validation.cjs +109 -0
  187. package/lib/target/discovery-validation.d.cts +28 -0
  188. package/lib/target/discovery-validation.d.mts +28 -0
  189. package/lib/target/discovery-validation.d.ts +28 -0
  190. package/lib/target/discovery-validation.js +109 -0
  191. package/lib/target/discovery-validation.mjs +99 -0
  192. package/lib/target/discovery.cjs +112 -0
  193. package/lib/target/discovery.d.cts +74 -0
  194. package/lib/target/discovery.d.mts +74 -0
  195. package/lib/target/discovery.d.ts +68 -5
  196. package/lib/target/discovery.js +93 -101
  197. package/lib/target/discovery.mjs +105 -0
  198. package/lib/target/index.d.ts +6 -6
  199. package/lib/target/index.js +36 -36
  200. package/lib/target/oauth-http.d.ts +2 -0
  201. package/lib/target/oauth-http.js +25 -0
  202. package/lib/target/refresh-exchange.d.ts +11 -0
  203. package/lib/target/refresh-exchange.js +24 -0
  204. package/lib/target/refresh-revocation.d.ts +8 -0
  205. package/lib/target/refresh-revocation.js +19 -0
  206. package/lib/target/route-template.cjs +85 -0
  207. package/lib/target/route-template.d.cts +7 -0
  208. package/lib/target/route-template.d.mts +7 -0
  209. package/lib/target/route-template.d.ts +7 -0
  210. package/lib/target/route-template.js +85 -0
  211. package/lib/target/route-template.mjs +81 -0
  212. package/lib/target/session-errors.d.ts +4 -0
  213. package/lib/target/session-errors.js +12 -0
  214. package/lib/target/session-verification.d.ts +10 -0
  215. package/lib/target/session-verification.js +42 -0
  216. package/lib/target/target-registry.d.ts +7 -3
  217. package/lib/target/target-registry.js +41 -10
  218. package/lib/target/target-session-manager.d.ts +48 -0
  219. package/lib/target/target-session-manager.js +226 -0
  220. package/lib/target/target-session.d.ts +39 -31
  221. package/lib/target/target-session.js +63 -150
  222. package/lib/target/token-response.cjs +51 -0
  223. package/lib/target/token-response.d.cts +7 -0
  224. package/lib/target/token-response.d.mts +7 -0
  225. package/lib/target/token-response.d.ts +7 -0
  226. package/lib/target/token-response.js +51 -0
  227. package/lib/target/token-response.mjs +48 -0
  228. package/package.json +16 -6
  229. package/scripts/audit-production-dependencies.js +150 -0
  230. package/scripts/build-cluster.js +23 -3
  231. package/scripts/opcore-agent-gate.js +159 -0
  232. package/scripts/opcore-agent-tool-overlays.js +154 -0
  233. package/scripts/opcore-introduced-check.js +288 -0
  234. package/src/cluster/client.ts +42 -15
  235. package/src/cluster/connection-state.ts +33 -0
  236. package/src/cluster/connection-types.ts +28 -0
  237. package/src/cluster/connection.ts +37 -68
  238. package/src/cluster/errors.ts +5 -0
  239. package/src/cluster/index.ts +3 -1
  240. package/src/cluster/json-source.ts +1 -0
  241. package/src/cluster/payload-value.ts +1 -0
  242. package/src/cluster/socket.ts +13 -0
  243. package/src/cluster/subscriptions.ts +7 -2
  244. package/src/cluster/validators.ts +1 -0
  245. package/src/cluster/ws.d.ts +1 -1
  246. package/src/hosted-session/authority.ts +77 -0
  247. package/src/hosted-session/coordinator.ts +113 -56
  248. package/src/hosted-session/errors.ts +21 -0
  249. package/src/hosted-session/index.ts +14 -2
  250. package/src/hosted-session/reconnecting-watch.ts +148 -0
  251. package/src/hosted-session/types.ts +22 -8
  252. package/src/hosted-target/access-url.ts +46 -0
  253. package/src/hosted-target/adapter-request.ts +28 -0
  254. package/src/hosted-target/adapter-types.ts +39 -0
  255. package/src/hosted-target/bounds.ts +1 -0
  256. package/src/hosted-target/capsule-error-response.ts +107 -0
  257. package/src/hosted-target/errors.ts +36 -35
  258. package/src/hosted-target/index.ts +10 -23
  259. package/src/hosted-target/response-validation.ts +170 -62
  260. package/src/hosted-target/retry-executor.ts +62 -0
  261. package/src/hosted-target/retry.ts +47 -11
  262. package/src/hosted-target/target-adapter.ts +12 -8
  263. package/src/hosted-target/types.ts +19 -18
  264. package/src/hosted-target/zero-cloud-v1-adapter.ts +229 -316
  265. package/src/target/bounded-response.ts +68 -0
  266. package/src/target/credential-lock.ts +1 -3
  267. package/src/target/credential-store.ts +14 -10
  268. package/src/target/device-flow.ts +206 -71
  269. package/src/target/discovery-errors.ts +6 -0
  270. package/src/target/discovery-sections.ts +251 -0
  271. package/src/target/discovery-validation.ts +144 -0
  272. package/src/target/discovery.ts +163 -115
  273. package/src/target/index.ts +15 -11
  274. package/src/target/oauth-http.ts +27 -0
  275. package/src/target/refresh-exchange.ts +36 -0
  276. package/src/target/refresh-revocation.ts +29 -0
  277. package/src/target/route-template.ts +105 -0
  278. package/src/target/session-errors.ts +9 -0
  279. package/src/target/session-verification.ts +51 -0
  280. package/src/target/target-registry.ts +60 -27
  281. package/src/target/target-session-manager.ts +285 -0
  282. package/src/target/target-session.ts +105 -222
  283. package/src/target/token-response.ts +62 -0
@@ -1,249 +1,132 @@
1
- import type { TargetCredentialStore } from './credential-store.ts';
2
- import type { TargetRecord, SettingsPort } from './target-registry.ts';
1
+ import type { TargetCredentialStore } from './credential-store.js';
2
+ import type { Clock } from './device-flow.js';
3
3
  import {
4
- requestDeviceCode,
5
- pollForToken,
6
- UnboundSessionError,
7
- type HttpTransport,
8
- type Clock,
9
- type TokenResponse,
10
- } from './device-flow.ts';
11
- import { targetServiceKey, TARGET_ACCOUNT } from './credential-store.ts';
12
- import { updateTargetOrganization } from './target-registry.ts';
4
+ TargetSessionManager,
5
+ type TargetSessionDeps,
6
+ } from './target-session-manager.js';
7
+ import type { SettingsPort, TargetRecord } from './target-registry.js';
8
+
9
+ export {
10
+ LoginRequiredError,
11
+ TargetSessionManager,
12
+ type BrowserOpener,
13
+ type TargetSessionDeps,
14
+ type TargetSessionManagerInit,
15
+ } from './target-session-manager.js';
13
16
 
14
- export class LoginRequiredError extends Error {
15
- readonly targetName: string;
16
- constructor(targetName: string) {
17
- super(`Login required. Run: zeroshot target login ${targetName}`);
18
- this.name = 'LoginRequiredError';
19
- this.targetName = targetName;
20
- }
21
- }
22
-
23
- export interface BrowserOpener {
24
- open(url: string): Promise<void>;
17
+ export interface TargetAccessTokenProvider {
18
+ getAccessToken(signal?: AbortSignal): Promise<string>;
25
19
  }
26
20
 
27
- export interface TargetSessionDeps {
28
- readonly http: HttpTransport;
29
- readonly clock: Clock;
30
- readonly browserOpener: BrowserOpener;
31
- readonly stderr: { write(s: string): void };
32
- readonly discoveryEndpoints: {
33
- readonly deviceAuthorizationEndpoint: string;
34
- readonly tokenEndpoint: string;
35
- readonly revocationEndpoint?: string;
36
- readonly clientId: string;
37
- };
38
- }
21
+ type LegacySessionDeps = Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'> & {
22
+ readonly settings?: SettingsPort;
23
+ readonly targetName?: string;
24
+ readonly clock?: Clock;
25
+ };
39
26
 
40
- export async function targetLogin(
27
+ type TargetLoginArgs = readonly [
41
28
  targetName: string,
42
29
  target: TargetRecord,
43
30
  credentialStore: TargetCredentialStore,
44
31
  acquireLock: () => Promise<() => Promise<void>>,
45
32
  settings: SettingsPort,
46
- deps: TargetSessionDeps
47
- ): Promise<{ organization: { id: string; name: string } }> {
48
- const { http, clock, browserOpener, stderr, discoveryEndpoints } = deps;
49
- const { deviceAuthorizationEndpoint, tokenEndpoint, clientId } = discoveryEndpoints;
50
-
51
- const codeResponse = await requestDeviceCode(deviceAuthorizationEndpoint, clientId, http);
52
-
53
- stderr.write(
54
- `\nOpen this URL to authorize:\n ${codeResponse.verification_uri}\n\nEnter code: ${codeResponse.user_code}\n\n`
55
- );
56
-
57
- if (codeResponse.verification_uri_complete) {
58
- try {
59
- await browserOpener.open(codeResponse.verification_uri_complete);
60
- } catch {
61
- // Browser open is best-effort
62
- }
63
- }
64
-
65
- const tokenResponse = await pollForToken(
66
- tokenEndpoint,
67
- clientId,
68
- codeResponse.device_code,
69
- codeResponse.interval,
70
- codeResponse.expires_in,
71
- http,
72
- clock
73
- );
74
-
75
- if (!tokenResponse.organization) {
76
- throw new UnboundSessionError(codeResponse.verification_uri);
77
- }
33
+ deps: TargetSessionDeps,
34
+ ];
78
35
 
79
- const serviceKey = targetServiceKey(target.id);
80
- const release = await acquireLock();
81
- try {
82
- await credentialStore.set(serviceKey, TARGET_ACCOUNT, tokenResponse.refresh_token);
83
- } finally {
84
- await release();
85
- }
86
-
87
- updateTargetOrganization(targetName, tokenResponse.organization, settings);
88
-
89
- return { organization: tokenResponse.organization };
90
- }
91
-
92
- export async function refreshAccessToken(
36
+ type RefreshAccessArgs = readonly [
93
37
  targetName: string,
94
38
  target: TargetRecord,
95
39
  credentialStore: TargetCredentialStore,
96
40
  acquireLock: () => Promise<() => Promise<void>>,
97
- deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>
98
- ): Promise<{ accessToken: string; expiresIn: number }> {
99
- const { http, discoveryEndpoints } = deps;
100
- const { tokenEndpoint, revocationEndpoint, clientId } = discoveryEndpoints;
101
- const serviceKey = targetServiceKey(target.id);
41
+ deps: LegacySessionDeps,
42
+ clock?: Clock,
43
+ ];
102
44
 
103
- const release = await acquireLock();
104
- try {
105
- const currentRefreshToken = await credentialStore.get(serviceKey, TARGET_ACCOUNT);
106
- if (!currentRefreshToken) {
107
- throw new LoginRequiredError(targetName);
108
- }
109
-
110
- const body = new URLSearchParams({
111
- grant_type: 'refresh_token',
112
- refresh_token: currentRefreshToken,
113
- client_id: clientId,
114
- });
115
-
116
- let tokenResponse: TokenResponse;
117
- const response = await http.fetch(tokenEndpoint, {
118
- method: 'POST',
119
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
120
- body: body.toString(),
121
- redirect: 'error',
122
- });
123
-
124
- if (!response.ok) {
125
- const errorBody = (await response.json().catch(() => ({ error: 'unknown' }))) as {
126
- error: string;
127
- };
128
- if (errorBody.error === 'invalid_grant') {
129
- await credentialStore.delete(serviceKey, TARGET_ACCOUNT);
130
- throw new LoginRequiredError(targetName);
131
- }
132
- throw new Error(`Token refresh failed (${response.status}): ${errorBody.error}`);
133
- }
134
-
135
- tokenResponse = (await response.json()) as TokenResponse;
136
-
137
- try {
138
- await credentialStore.set(serviceKey, TARGET_ACCOUNT, tokenResponse.refresh_token);
139
- } catch {
140
- await bestEffortRevoke(tokenResponse.refresh_token, revocationEndpoint, clientId, http);
141
- await credentialStore.delete(serviceKey, TARGET_ACCOUNT);
142
- throw new LoginRequiredError(targetName);
143
- }
45
+ type RevokeArgs = readonly [
46
+ target: TargetRecord,
47
+ credentialStore: TargetCredentialStore,
48
+ acquireLock: () => Promise<() => Promise<void>>,
49
+ deps: LegacySessionDeps,
50
+ force: boolean,
51
+ ];
144
52
 
145
- return { accessToken: tokenResponse.access_token, expiresIn: tokenResponse.expires_in };
146
- } finally {
147
- await release();
148
- }
53
+ type LegacyManagerInit = {
54
+ readonly targetName: string;
55
+ readonly target: TargetRecord;
56
+ readonly credentialStore: TargetCredentialStore;
57
+ readonly acquireLock: () => Promise<() => Promise<void>>;
58
+ readonly deps: LegacySessionDeps;
59
+ readonly clock?: Clock;
60
+ };
61
+
62
+ function transientSettings(targetName: string, target: TargetRecord): SettingsPort {
63
+ const state: { _targets: Record<string, TargetRecord> } = {
64
+ _targets: { [targetName]: target },
65
+ };
66
+ return {
67
+ load: () => state,
68
+ mutate: (mutator) => mutator(state),
69
+ };
149
70
  }
150
71
 
151
- async function bestEffortRevoke(
152
- token: string,
153
- revocationEndpoint: string | undefined,
154
- clientId: string,
155
- http: HttpTransport
156
- ): Promise<void> {
157
- if (!revocationEndpoint) return;
158
- try {
159
- const body = new URLSearchParams({
160
- token,
161
- client_id: clientId,
162
- token_type_hint: 'refresh_token',
163
- });
164
- await http.fetch(revocationEndpoint, {
165
- method: 'POST',
166
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
167
- body: body.toString(),
168
- redirect: 'error',
169
- });
170
- } catch {
171
- // Best-effort
172
- }
72
+ function legacyManager(init: LegacyManagerInit): TargetSessionManager {
73
+ const clock = init.clock ?? init.deps.clock ?? { now: () => Date.now() };
74
+ return new TargetSessionManager({
75
+ targetName: init.targetName,
76
+ target: init.target,
77
+ credentialStore: init.credentialStore,
78
+ acquireLock: init.acquireLock,
79
+ settings: init.deps.settings ?? transientSettings(init.targetName, init.target),
80
+ deps: {
81
+ http: init.deps.http,
82
+ clock,
83
+ browserOpener: { open: () => Promise.resolve() },
84
+ stderr: { write: () => undefined },
85
+ discoveryEndpoints: init.deps.discoveryEndpoints,
86
+ },
87
+ });
173
88
  }
174
89
 
175
- export interface TargetAccessTokenProvider {
176
- getAccessToken(signal?: AbortSignal): Promise<string>;
90
+ export function targetLogin(...args: TargetLoginArgs): Promise<{ organization: { id: string } }> {
91
+ const [targetName, target, credentialStore, acquireLock, settings, deps] = args;
92
+ return new TargetSessionManager({
93
+ targetName,
94
+ target,
95
+ credentialStore,
96
+ acquireLock,
97
+ settings,
98
+ deps,
99
+ }).login();
177
100
  }
178
101
 
179
- export function getAccessTokenProvider(
180
- targetName: string,
181
- target: TargetRecord,
182
- credentialStore: TargetCredentialStore,
183
- acquireLock: () => Promise<() => Promise<void>>,
184
- deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>,
185
- clock: Clock = { now: () => Date.now() }
186
- ): TargetAccessTokenProvider {
187
- let cachedToken: string | null = null;
188
- let expiresAt = 0;
189
-
190
- return {
191
- async getAccessToken(_signal?: AbortSignal): Promise<string> {
192
- if (cachedToken && clock.now() < expiresAt - 30_000) {
193
- return cachedToken;
194
- }
195
- const result = await refreshAccessToken(
196
- targetName,
197
- target,
198
- credentialStore,
199
- acquireLock,
200
- deps
201
- );
202
- cachedToken = result.accessToken;
203
- expiresAt = clock.now() + result.expiresIn * 1000;
204
- return result.accessToken;
205
- },
206
- };
102
+ export async function refreshAccessToken(
103
+ ...args: RefreshAccessArgs
104
+ ): Promise<{ accessToken: string; expiresIn: number }> {
105
+ const [targetName, target, credentialStore, acquireLock, deps, clock] = args;
106
+ return legacyManager({
107
+ targetName,
108
+ target,
109
+ credentialStore,
110
+ acquireLock,
111
+ deps,
112
+ ...(clock === undefined ? {} : { clock }),
113
+ }).getAccessTokenWithLifetime('capsule');
207
114
  }
208
115
 
209
- export async function revokeAndCleanup(
210
- target: TargetRecord,
211
- credentialStore: TargetCredentialStore,
212
- acquireLock: () => Promise<() => Promise<void>>,
213
- deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>,
214
- force: boolean
215
- ): Promise<void> {
216
- const { http, discoveryEndpoints } = deps;
217
- const { revocationEndpoint, clientId } = discoveryEndpoints;
218
- const serviceKey = targetServiceKey(target.id);
116
+ export function getAccessTokenProvider(...args: RefreshAccessArgs): TargetAccessTokenProvider {
117
+ const [targetName, target, credentialStore, acquireLock, deps, clock] = args;
118
+ return legacyManager({
119
+ targetName,
120
+ target,
121
+ credentialStore,
122
+ acquireLock,
123
+ deps,
124
+ ...(clock === undefined ? {} : { clock }),
125
+ }).tokenProvider('capsule');
126
+ }
219
127
 
220
- const release = await acquireLock();
221
- try {
222
- const refreshToken = await credentialStore.get(serviceKey, TARGET_ACCOUNT);
223
- if (refreshToken && revocationEndpoint) {
224
- const body = new URLSearchParams({
225
- token: refreshToken,
226
- client_id: clientId,
227
- token_type_hint: 'refresh_token',
228
- });
229
- try {
230
- const response = await http.fetch(revocationEndpoint, {
231
- method: 'POST',
232
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
233
- body: body.toString(),
234
- redirect: 'error',
235
- });
236
- if (!response.ok && !force) {
237
- throw new Error(
238
- `Remote revocation failed (${response.status}). Use --force to remove anyway.`
239
- );
240
- }
241
- } catch (err) {
242
- if (!force) throw err;
243
- }
244
- }
245
- await credentialStore.delete(serviceKey, TARGET_ACCOUNT);
246
- } finally {
247
- await release();
248
- }
128
+ export function revokeAndCleanup(...args: RevokeArgs): Promise<void> {
129
+ const [target, credentialStore, acquireLock, deps, force] = args;
130
+ const targetName = deps.targetName ?? target.id;
131
+ return legacyManager({ targetName, target, credentialStore, acquireLock, deps }).revoke(force);
249
132
  }
@@ -0,0 +1,62 @@
1
+ const MAX_TOKEN_BYTES = 16 * 1024;
2
+ const TOKEN_RESPONSE_FIELDS = [
3
+ 'access_token',
4
+ 'refresh_token',
5
+ 'token_type',
6
+ 'expires_in',
7
+ 'refresh_expires_in',
8
+ 'scope',
9
+ ] as const;
10
+
11
+ export interface TokenResponse {
12
+ readonly access_token: string;
13
+ readonly refresh_token: string;
14
+ readonly token_type: 'Bearer';
15
+ readonly expires_in: number;
16
+ }
17
+
18
+ function boundedString(value: unknown, maxBytes: number): value is string {
19
+ return typeof value === 'string' && value.length > 0 &&
20
+ new TextEncoder().encode(value).byteLength <= maxBytes;
21
+ }
22
+
23
+ function tokenFieldsValid(token: Record<string, unknown>): boolean {
24
+ const fields = Object.keys(token);
25
+ return fields.length === TOKEN_RESPONSE_FIELDS.length &&
26
+ fields.every((key) => TOKEN_RESPONSE_FIELDS.includes(
27
+ key as (typeof TOKEN_RESPONSE_FIELDS)[number],
28
+ ));
29
+ }
30
+
31
+ function tokenLifetimesValid(token: Record<string, unknown>): boolean {
32
+ return Number.isSafeInteger(token.expires_in) &&
33
+ (token.expires_in as number) > 0 &&
34
+ (token.expires_in as number) <= 86_400 &&
35
+ Number.isSafeInteger(token.refresh_expires_in) &&
36
+ (token.refresh_expires_in as number) > 0 &&
37
+ (token.refresh_expires_in as number) <= 31_536_000;
38
+ }
39
+
40
+ function tokenStringsValid(token: Record<string, unknown>): boolean {
41
+ return boundedString(token.access_token, MAX_TOKEN_BYTES) &&
42
+ boundedString(token.refresh_token, MAX_TOKEN_BYTES) &&
43
+ token.token_type === 'Bearer' &&
44
+ boundedString(token.scope, 512) &&
45
+ !/[\u0000-\u001f\u007f]/.test(token.scope);
46
+ }
47
+
48
+ export function parseTokenResponse(value: unknown): TokenResponse {
49
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
50
+ throw new Error('Token response is malformed');
51
+ }
52
+ const token = value as Record<string, unknown>;
53
+ if (!tokenFieldsValid(token) || !tokenLifetimesValid(token) || !tokenStringsValid(token)) {
54
+ throw new Error('Token response is malformed');
55
+ }
56
+ return Object.freeze({
57
+ access_token: token.access_token,
58
+ refresh_token: token.refresh_token,
59
+ token_type: 'Bearer',
60
+ expires_in: token.expires_in,
61
+ }) as TokenResponse;
62
+ }