@pymthouse/builder-sdk 0.3.0 → 0.4.1-rc.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 (103) hide show
  1. package/README.md +54 -28
  2. package/dist/{client-BHfjDvIe.d.ts → client-CauCfGa7.d.ts} +1 -1
  3. package/dist/{client-CvhJEhjV.d.cts → client-D1Xz-xlx.d.cts} +1 -1
  4. package/dist/config.cjs +0 -21
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.cts +1 -5
  7. package/dist/config.d.ts +1 -5
  8. package/dist/config.js +1 -20
  9. package/dist/config.js.map +1 -1
  10. package/dist/device-initiate.cjs.map +1 -1
  11. package/dist/device-initiate.js.map +1 -1
  12. package/dist/device.cjs.map +1 -1
  13. package/dist/device.d.cts +1 -1
  14. package/dist/device.d.ts +1 -1
  15. package/dist/device.js.map +1 -1
  16. package/dist/env.cjs +13 -4
  17. package/dist/env.cjs.map +1 -1
  18. package/dist/env.d.cts +2 -2
  19. package/dist/env.d.ts +2 -2
  20. package/dist/env.js +13 -4
  21. package/dist/env.js.map +1 -1
  22. package/dist/index-BTDKEorK.d.ts +64 -0
  23. package/dist/index-BixH4VIG.d.cts +64 -0
  24. package/dist/index.cjs +13 -4
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +29 -5
  27. package/dist/index.d.ts +29 -5
  28. package/dist/index.js +13 -4
  29. package/dist/index.js.map +1 -1
  30. package/dist/{ingest-DoKJTWU9.d.ts → proxy-JrT6raU_.d.cts} +5 -42
  31. package/dist/{ingest-B3Yi8Tb1.d.cts → proxy-U32DFNuj.d.ts} +5 -42
  32. package/dist/signer/server.cjs +799 -895
  33. package/dist/signer/server.cjs.map +1 -1
  34. package/dist/signer/server.d.cts +9 -13
  35. package/dist/signer/server.d.ts +9 -13
  36. package/dist/signer/server.js +799 -893
  37. package/dist/signer/server.js.map +1 -1
  38. package/dist/signer/webhook/adapters/api-key.cjs +78 -0
  39. package/dist/signer/webhook/adapters/api-key.cjs.map +1 -0
  40. package/dist/signer/webhook/adapters/api-key.d.cts +18 -0
  41. package/dist/signer/webhook/adapters/api-key.d.ts +18 -0
  42. package/dist/signer/webhook/adapters/api-key.js +76 -0
  43. package/dist/signer/webhook/adapters/api-key.js.map +1 -0
  44. package/dist/signer/webhook/adapters/composite.cjs +60 -0
  45. package/dist/signer/webhook/adapters/composite.cjs.map +1 -0
  46. package/dist/signer/webhook/adapters/composite.d.cts +5 -0
  47. package/dist/signer/webhook/adapters/composite.d.ts +5 -0
  48. package/dist/signer/webhook/adapters/composite.js +58 -0
  49. package/dist/signer/webhook/adapters/composite.js.map +1 -0
  50. package/dist/signer/webhook/adapters/oauth1.cjs +18 -0
  51. package/dist/signer/webhook/adapters/oauth1.cjs.map +1 -0
  52. package/dist/signer/webhook/adapters/oauth1.d.cts +19 -0
  53. package/dist/signer/webhook/adapters/oauth1.d.ts +19 -0
  54. package/dist/signer/webhook/adapters/oauth1.js +16 -0
  55. package/dist/signer/webhook/adapters/oauth1.js.map +1 -0
  56. package/dist/signer/webhook/adapters/oidc.cjs +522 -0
  57. package/dist/signer/webhook/adapters/oidc.cjs.map +1 -0
  58. package/dist/signer/webhook/adapters/oidc.d.cts +4 -0
  59. package/dist/signer/webhook/adapters/oidc.d.ts +4 -0
  60. package/dist/signer/webhook/adapters/oidc.js +515 -0
  61. package/dist/signer/webhook/adapters/oidc.js.map +1 -0
  62. package/dist/signer/webhook/adapters/trusted-headers.cjs +103 -0
  63. package/dist/signer/webhook/adapters/trusted-headers.cjs.map +1 -0
  64. package/dist/signer/webhook/adapters/trusted-headers.d.cts +18 -0
  65. package/dist/signer/webhook/adapters/trusted-headers.d.ts +18 -0
  66. package/dist/signer/webhook/adapters/trusted-headers.js +99 -0
  67. package/dist/signer/webhook/adapters/trusted-headers.js.map +1 -0
  68. package/dist/signer/webhook.cjs +747 -0
  69. package/dist/signer/webhook.cjs.map +1 -0
  70. package/dist/signer/webhook.d.cts +26 -0
  71. package/dist/signer/webhook.d.ts +26 -0
  72. package/dist/signer/webhook.js +721 -0
  73. package/dist/signer/webhook.js.map +1 -0
  74. package/dist/tokens.d.cts +1 -1
  75. package/dist/tokens.d.ts +1 -1
  76. package/dist/{types-_R1AwEZp.d.cts → types-BORaHW_x.d.cts} +5 -5
  77. package/dist/{types-_R1AwEZp.d.ts → types-BORaHW_x.d.ts} +5 -5
  78. package/dist/verifier-B-WFDMz6.d.cts +48 -0
  79. package/dist/verifier-B-WFDMz6.d.ts +48 -0
  80. package/dist/verify.cjs.map +1 -1
  81. package/dist/verify.d.cts +1 -1
  82. package/dist/verify.d.ts +1 -1
  83. package/dist/verify.js.map +1 -1
  84. package/package.json +30 -30
  85. package/dist/gateway/client/index.cjs +0 -492
  86. package/dist/gateway/client/index.cjs.map +0 -1
  87. package/dist/gateway/client/index.d.cts +0 -63
  88. package/dist/gateway/client/index.d.ts +0 -63
  89. package/dist/gateway/client/index.js +0 -489
  90. package/dist/gateway/client/index.js.map +0 -1
  91. package/dist/gateway/index.cjs +0 -16
  92. package/dist/gateway/index.cjs.map +0 -1
  93. package/dist/gateway/index.d.cts +0 -52
  94. package/dist/gateway/index.d.ts +0 -52
  95. package/dist/gateway/index.js +0 -10
  96. package/dist/gateway/index.js.map +0 -1
  97. package/dist/gateway/server/index.cjs +0 -1248
  98. package/dist/gateway/server/index.cjs.map +0 -1
  99. package/dist/gateway/server/index.d.cts +0 -31
  100. package/dist/gateway/server/index.d.ts +0 -31
  101. package/dist/gateway/server/index.js +0 -1233
  102. package/dist/gateway/server/index.js.map +0 -1
  103. package/gateway/proto/lp_rpc.proto +0 -542
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pymthouse/builder-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.1-rc.1",
4
4
  "description": "PymtHouse Builder API and OIDC client (OpenID-certified oauth4webapi)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -57,40 +57,42 @@
57
57
  "import": "./dist/signer/server.js",
58
58
  "require": "./dist/signer/server.cjs"
59
59
  },
60
- "./gateway": {
61
- "types": "./dist/gateway/index.d.ts",
62
- "import": "./dist/gateway/index.js",
63
- "require": "./dist/gateway/index.cjs"
60
+ "./signer/webhook": {
61
+ "types": "./dist/signer/webhook.d.ts",
62
+ "import": "./dist/signer/webhook.js",
63
+ "require": "./dist/signer/webhook.cjs"
64
64
  },
65
- "./gateway/client": {
66
- "types": "./dist/gateway/client/index.d.ts",
67
- "import": "./dist/gateway/client/index.js",
68
- "require": "./dist/gateway/client/index.cjs"
65
+ "./signer/webhook/adapters/api-key": {
66
+ "types": "./dist/signer/webhook/adapters/api-key.d.ts",
67
+ "import": "./dist/signer/webhook/adapters/api-key.js",
68
+ "require": "./dist/signer/webhook/adapters/api-key.cjs"
69
69
  },
70
- "./gateway/server": {
71
- "types": "./dist/gateway/server/index.d.ts",
72
- "import": "./dist/gateway/server/index.js",
73
- "require": "./dist/gateway/server/index.cjs"
70
+ "./signer/webhook/adapters/composite": {
71
+ "types": "./dist/signer/webhook/adapters/composite.d.ts",
72
+ "import": "./dist/signer/webhook/adapters/composite.js",
73
+ "require": "./dist/signer/webhook/adapters/composite.cjs"
74
+ },
75
+ "./signer/webhook/adapters/oidc": {
76
+ "types": "./dist/signer/webhook/adapters/oidc.d.ts",
77
+ "import": "./dist/signer/webhook/adapters/oidc.js",
78
+ "require": "./dist/signer/webhook/adapters/oidc.cjs"
79
+ },
80
+ "./signer/webhook/adapters/oauth1": {
81
+ "types": "./dist/signer/webhook/adapters/oauth1.d.ts",
82
+ "import": "./dist/signer/webhook/adapters/oauth1.js",
83
+ "require": "./dist/signer/webhook/adapters/oauth1.cjs"
84
+ },
85
+ "./signer/webhook/adapters/trusted-headers": {
86
+ "types": "./dist/signer/webhook/adapters/trusted-headers.d.ts",
87
+ "import": "./dist/signer/webhook/adapters/trusted-headers.js",
88
+ "require": "./dist/signer/webhook/adapters/trusted-headers.cjs"
74
89
  }
75
90
  },
76
91
  "files": [
77
92
  "dist",
78
- "gateway/proto",
79
93
  "README.md",
80
94
  "LICENSE"
81
95
  ],
82
- "peerDependencies": {
83
- "@grpc/grpc-js": "^1.12.0",
84
- "@grpc/proto-loader": "^0.7.0"
85
- },
86
- "peerDependenciesMeta": {
87
- "@grpc/grpc-js": {
88
- "optional": true
89
- },
90
- "@grpc/proto-loader": {
91
- "optional": true
92
- }
93
- },
94
96
  "sideEffects": false,
95
97
  "engines": {
96
98
  "node": ">=20"
@@ -103,7 +105,7 @@
103
105
  "typecheck": "tsc --noEmit",
104
106
  "lint": "eslint .",
105
107
  "test": "vitest run",
106
- "check:gateway-peers": "node scripts/check-gateway-peers.mjs",
108
+ "webhook:serve": "node examples/remote-signer-webhook.mjs",
107
109
  "format": "prettier . --write",
108
110
  "format:check": "prettier . --check",
109
111
  "prepack": "pnpm run build"
@@ -112,8 +114,6 @@
112
114
  "oauth4webapi": "^3.8.5"
113
115
  },
114
116
  "devDependencies": {
115
- "@grpc/grpc-js": "^1.14.3",
116
- "@grpc/proto-loader": "^0.8.0",
117
117
  "@eslint/js": "^9.18.0",
118
118
  "@types/node": "^22.10.0",
119
119
  "eslint": "^9.18.0",
@@ -133,7 +133,7 @@
133
133
  ],
134
134
  "repository": {
135
135
  "type": "git",
136
- "url": "https://github.com/pymthouse/builder-sdk.git"
136
+ "url": "git+https://github.com/pymthouse/builder-sdk.git"
137
137
  },
138
138
  "bugs": {
139
139
  "url": "https://github.com/pymthouse/builder-sdk/issues"
@@ -1,492 +0,0 @@
1
- 'use strict';
2
-
3
- // src/gateway/types.ts
4
- var TRICKLE_SEQ_LATEST = -1;
5
- var TRICKLE_SEQ_CURRENT = -2;
6
-
7
- // src/string-utils.ts
8
- function stripTrailingSlashes(value) {
9
- let end = value.length;
10
- while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
11
- end--;
12
- }
13
- return value.slice(0, end);
14
- }
15
-
16
- // src/errors.ts
17
- var PmtHouseError = class extends Error {
18
- status;
19
- code;
20
- details;
21
- constructor(message, {
22
- status = 500,
23
- code = "pymthouse_error",
24
- details
25
- } = {}) {
26
- super(message);
27
- this.name = "PmtHouseError";
28
- this.status = status;
29
- this.code = code;
30
- this.details = details;
31
- }
32
- };
33
-
34
- // src/signer/fetch-json.ts
35
- function oauthFailureDescription(parsed, failureLabel, status) {
36
- if (typeof parsed.error_description === "string") {
37
- return parsed.error_description;
38
- }
39
- if (typeof parsed.error === "string") {
40
- return parsed.error;
41
- }
42
- return `${failureLabel} (${status})`;
43
- }
44
- async function readJsonObjectFromResponse(response, options) {
45
- const text = await response.text();
46
- let parsed;
47
- try {
48
- parsed = text ? JSON.parse(text) : {};
49
- } catch {
50
- throw new PmtHouseError(options.invalidJsonMessage, {
51
- status: 502,
52
- code: options.invalidJsonCode,
53
- details: { status: response.status }
54
- });
55
- }
56
- if (!response.ok) {
57
- const description = oauthFailureDescription(parsed, options.failureLabel, response.status);
58
- throw new PmtHouseError(description, {
59
- status: response.status,
60
- code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
61
- details: parsed
62
- });
63
- }
64
- return parsed;
65
- }
66
-
67
- // src/signer/device-exchange.ts
68
- function extractSignerAccessTokenFromExchangeBody(body) {
69
- const tokenObj = body.token;
70
- if (tokenObj !== null && typeof tokenObj === "object" && !Array.isArray(tokenObj)) {
71
- const nested = tokenObj;
72
- for (const key of ["accessToken", "access_token"]) {
73
- const value = nested[key];
74
- if (typeof value === "string" && value.trim()) {
75
- return value.trim();
76
- }
77
- }
78
- }
79
- for (const key of ["accessToken", "access_token"]) {
80
- const value = body[key];
81
- if (typeof value === "string" && value.trim()) {
82
- return value.trim();
83
- }
84
- }
85
- throw new PmtHouseError("Device exchange response missing signer access token", {
86
- status: 502,
87
- code: "invalid_exchange_response"
88
- });
89
- }
90
- function normalizeDeviceExchangeResponse(minted, options) {
91
- const scope = minted.scope.trim() || "sign:job";
92
- const body = {
93
- access_token: minted.access_token,
94
- token_type: "Bearer",
95
- expires_in: minted.expires_in,
96
- scope,
97
- balanceUsdMicros: minted.balanceUsdMicros,
98
- lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros,
99
- token: {
100
- accessToken: minted.access_token,
101
- access_token: minted.access_token,
102
- expiresIn: minted.expires_in,
103
- expires_in: minted.expires_in,
104
- scope,
105
- balanceUsdMicros: minted.balanceUsdMicros,
106
- lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros
107
- }
108
- };
109
- const signerUrl = options?.signerUrl?.trim();
110
- if (signerUrl) {
111
- body.signerUrl = signerUrl;
112
- }
113
- return body;
114
- }
115
-
116
- // src/signer/api-key-exchange.ts
117
- var EXCHANGE_RESPONSE_ERROR = "invalid_exchange_response";
118
- async function exchangeApiKeyForSigner(options) {
119
- const fetchImpl = options.fetch ?? fetch;
120
- const url = `${stripTrailingSlashes(options.facadeUrl)}/api/pymthouse/keys/exchange`;
121
- const body = { apiKey: options.apiKey };
122
- if (options.scope?.trim()) {
123
- body.scope = options.scope.trim();
124
- }
125
- if (options.clientId?.trim()) {
126
- body.clientId = options.clientId.trim();
127
- }
128
- const response = await fetchImpl(url, {
129
- method: "POST",
130
- headers: {
131
- "Content-Type": "application/json",
132
- Accept: "application/json"
133
- },
134
- body: JSON.stringify(body),
135
- cache: "no-store"
136
- });
137
- const parsed = await readJsonObjectFromResponse(response, {
138
- invalidJsonMessage: "API key exchange returned invalid JSON",
139
- invalidJsonCode: EXCHANGE_RESPONSE_ERROR,
140
- failureLabel: "API key exchange failed",
141
- defaultErrorCode: "api_key_exchange_failed"
142
- });
143
- const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
144
- const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
145
- const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
146
- return normalizeDeviceExchangeResponse(
147
- {
148
- access_token: accessToken,
149
- expires_in: typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 3600,
150
- scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
151
- balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
152
- lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
153
- },
154
- { signerUrl }
155
- );
156
- }
157
-
158
- // src/gateway/client/resolve-signer.ts
159
- var boundFetch = (input, init) => globalThis.fetch(input, init);
160
- async function resolveSignerToken(credentials, fetchImpl = boundFetch) {
161
- if (credentials.type === "bearer") {
162
- const token2 = credentials.accessToken.trim();
163
- if (!token2) {
164
- throw new Error("Signer bearer token is empty");
165
- }
166
- return token2;
167
- }
168
- const exchanged = await exchangeApiKeyForSigner({
169
- facadeUrl: credentials.facadeUrl,
170
- apiKey: credentials.apiKey,
171
- scope: credentials.scope ?? "sign:job",
172
- clientId: credentials.clientId,
173
- fetch: fetchImpl
174
- });
175
- const token = exchanged.access_token?.trim() || exchanged.token?.accessToken?.trim() || exchanged.token?.access_token?.trim();
176
- if (!token) {
177
- throw new Error("API key exchange did not return a signer access token");
178
- }
179
- return token;
180
- }
181
-
182
- // src/gateway/client/browser-client.ts
183
- function jsonErrorMessage(body, status, fallback) {
184
- if (typeof body.message === "string") {
185
- return body.message;
186
- }
187
- if (typeof body.error === "string") {
188
- return body.error;
189
- }
190
- return `${fallback} (${status})`;
191
- }
192
- function resolveSubscribeSegmentSeq(requestedSeq, headerSeq) {
193
- if (Number.isFinite(headerSeq)) {
194
- return headerSeq;
195
- }
196
- if (requestedSeq >= 0) {
197
- return requestedSeq;
198
- }
199
- return 0;
200
- }
201
- function isTrickleLeadingIndex(seq) {
202
- return seq === TRICKLE_SEQ_LATEST || seq === TRICKLE_SEQ_CURRENT;
203
- }
204
- function parseHeaderInt(headers, name) {
205
- const raw = headers.get(name);
206
- if (!raw) {
207
- return Number.NaN;
208
- }
209
- const parsed = Number.parseInt(raw, 10);
210
- return Number.isFinite(parsed) ? parsed : Number.NaN;
211
- }
212
- var BrowserGatewayClient = class {
213
- constructor(options) {
214
- this.options = options;
215
- this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
216
- }
217
- options;
218
- signerToken = null;
219
- sessionId = null;
220
- publishSeq = -1;
221
- /** Next trickle GET index for orchestrator output (see TrickleSubscriber). */
222
- subscribeSeq = TRICKLE_SEQ_CURRENT;
223
- subscribeEmptyPolls = 0;
224
- fetchImpl;
225
- get baseUrl() {
226
- return stripTrailingSlashes(this.options.baseUrl);
227
- }
228
- get signerAccessToken() {
229
- return this.signerToken;
230
- }
231
- get activeSessionId() {
232
- return this.sessionId;
233
- }
234
- get nextSubscribeSeq() {
235
- return this.subscribeSeq;
236
- }
237
- async connect(credentials) {
238
- this.signerToken = await resolveSignerToken(credentials, this.fetchImpl);
239
- }
240
- async startSession(request) {
241
- if (!this.signerToken) {
242
- throw new Error("Call connect() with signer credentials before startSession()");
243
- }
244
- const response = await this.fetchImpl(`${this.baseUrl}/api/gateway/sessions`, {
245
- method: "POST",
246
- headers: {
247
- Authorization: `Bearer ${this.signerToken}`,
248
- "Content-Type": "application/json",
249
- Accept: "application/json"
250
- },
251
- body: JSON.stringify(request)
252
- });
253
- const body = await response.json().catch(() => ({}));
254
- if (!response.ok) {
255
- throw new Error(jsonErrorMessage(body, response.status, "startSession failed"));
256
- }
257
- const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
258
- const manifestId = typeof body.manifestId === "string" ? body.manifestId : "";
259
- if (!sessionId) {
260
- throw new Error("startSession response missing sessionId");
261
- }
262
- this.sessionId = sessionId;
263
- const initialPublishSeq = typeof body.publishSeq === "number" && Number.isFinite(body.publishSeq) ? body.publishSeq : 0;
264
- this.publishSeq = initialPublishSeq - 1;
265
- const initialSubscribeSeq = typeof body.subscribeSeq === "number" && Number.isFinite(body.subscribeSeq) ? body.subscribeSeq : TRICKLE_SEQ_CURRENT;
266
- this.subscribeSeq = initialSubscribeSeq;
267
- this.subscribeEmptyPolls = 0;
268
- const mimeType = typeof body.mimeType === "string" ? body.mimeType : void 0;
269
- return {
270
- sessionId,
271
- manifestId,
272
- mimeType,
273
- publishSeq: initialPublishSeq,
274
- subscribeSeq: initialSubscribeSeq
275
- };
276
- }
277
- setSignerToken(accessToken) {
278
- const token = accessToken.trim();
279
- if (!token) {
280
- throw new Error("Signer bearer token is empty");
281
- }
282
- this.signerToken = token;
283
- }
284
- async publishSegment(bytes, options) {
285
- if (!this.sessionId || !this.signerToken) {
286
- throw new Error("No active gateway session");
287
- }
288
- const seq = options?.seq ?? this.publishSeq + 1;
289
- const part = bytes instanceof Uint8Array ? Uint8Array.from(bytes) : new Uint8Array(bytes);
290
- const body = new Blob([part]);
291
- const response = await this.fetchImpl(
292
- `${this.baseUrl}/api/gateway/sessions/${encodeURIComponent(this.sessionId)}/publish/${seq}`,
293
- {
294
- method: "PUT",
295
- headers: {
296
- Authorization: `Bearer ${this.signerToken}`,
297
- "Content-Type": options?.contentType ?? "application/octet-stream"
298
- },
299
- body
300
- }
301
- );
302
- if (!response.ok) {
303
- const text = await response.text();
304
- throw new Error(`publishSegment failed (${response.status}): ${text.slice(0, 300)}`);
305
- }
306
- this.publishSeq = seq;
307
- return { seq, ok: true };
308
- }
309
- /**
310
- * Fetch the next orchestrator output segment (sequential walk from subscribeSeq).
311
- * Matches livepeer-python-gateway TrickleSubscriber / MediaOutput segment iteration.
312
- */
313
- async subscribeOutputSegment() {
314
- const chunks = [];
315
- const segment = await this.subscribeOutputSegmentStream((chunk) => {
316
- chunks.push(chunk);
317
- });
318
- if (!segment) {
319
- return null;
320
- }
321
- const total = chunks.reduce((sum, part) => sum + part.byteLength, 0);
322
- const joined = new Uint8Array(total);
323
- let offset = 0;
324
- for (const part of chunks) {
325
- joined.set(part, offset);
326
- offset += part.byteLength;
327
- }
328
- return { ...segment, data: joined.buffer, byteCount: total };
329
- }
330
- /**
331
- * Stream the next orchestrator output segment incrementally.
332
- * Mirrors Python SegmentReader behavior (consume bytes before segment closes).
333
- */
334
- async subscribeOutputSegmentStream(onChunk) {
335
- if (!this.sessionId || !this.signerToken) {
336
- throw new Error("No active gateway session");
337
- }
338
- const requestedSeq = this.subscribeSeq;
339
- const url = new URL(
340
- `${this.baseUrl}/api/gateway/sessions/${encodeURIComponent(this.sessionId)}/subscribe`
341
- );
342
- url.searchParams.set("seq", String(requestedSeq));
343
- const response = await this.fetchImpl(url.toString(), {
344
- method: "GET",
345
- headers: {
346
- Authorization: `Bearer ${this.signerToken}`
347
- }
348
- });
349
- if (response.status === 204) {
350
- this.handleSubscribeEmpty(requestedSeq, response.headers);
351
- return null;
352
- }
353
- if (!response.ok) {
354
- const text = await response.text();
355
- throw new Error(`subscribeOutputSegment failed (${response.status}): ${text.slice(0, 300)}`);
356
- }
357
- const segmentSeq = parseHeaderInt(response.headers, "X-Gateway-Segment-Seq");
358
- const latestSeq = parseHeaderInt(response.headers, "X-Gateway-Latest-Seq");
359
- const resolvedSegmentSeq = resolveSubscribeSegmentSeq(requestedSeq, segmentSeq);
360
- const resolvedLatest = Number.isFinite(latestSeq) ? latestSeq : resolvedSegmentSeq;
361
- const byteCount = await this.readSubscribeBody(response, onChunk);
362
- if (byteCount === 0) {
363
- this.handleSubscribeEmpty(requestedSeq, response.headers);
364
- return null;
365
- }
366
- this.advanceSubscribeSeq(requestedSeq, response.headers, resolvedLatest);
367
- return {
368
- segmentSeq: resolvedSegmentSeq,
369
- latestSeq: resolvedLatest,
370
- nextSeq: this.subscribeSeq,
371
- byteCount
372
- };
373
- }
374
- async readSubscribeBody(response, onChunk) {
375
- if (!response.body) {
376
- const data = await response.arrayBuffer();
377
- if (data.byteLength === 0) {
378
- return 0;
379
- }
380
- onChunk(new Uint8Array(data));
381
- return data.byteLength;
382
- }
383
- const reader = response.body.getReader();
384
- let bytesRead = 0;
385
- try {
386
- while (true) {
387
- const { done, value } = await reader.read();
388
- if (done) {
389
- break;
390
- }
391
- if (value && value.byteLength > 0) {
392
- bytesRead += value.byteLength;
393
- onChunk(value);
394
- }
395
- }
396
- } finally {
397
- reader.releaseLock();
398
- }
399
- return bytesRead;
400
- }
401
- /** @deprecated Use subscribeOutputSegment() — live-edge-only polling does not advance output seq. */
402
- async subscribeLiveSegment() {
403
- return this.subscribeOutputSegment();
404
- }
405
- advanceSubscribeSeq(requestedSeq, headers, latestSeq) {
406
- const nextHeader = parseHeaderInt(headers, "X-Gateway-Next-Seq");
407
- if (Number.isFinite(nextHeader)) {
408
- this.subscribeSeq = nextHeader;
409
- } else {
410
- const segmentSeq = parseHeaderInt(headers, "X-Gateway-Segment-Seq");
411
- if (Number.isFinite(segmentSeq) && segmentSeq >= 0) {
412
- this.subscribeSeq = segmentSeq + 1;
413
- } else if (requestedSeq >= 0) {
414
- this.subscribeSeq = requestedSeq + 1;
415
- } else if (Number.isFinite(latestSeq) && latestSeq >= 0) {
416
- this.subscribeSeq = latestSeq + 1;
417
- } else {
418
- this.subscribeSeq = TRICKLE_SEQ_LATEST;
419
- }
420
- }
421
- if (Number.isFinite(latestSeq) && latestSeq >= 0 && this.subscribeSeq >= 0 && latestSeq - this.subscribeSeq > 2) {
422
- this.subscribeSeq = latestSeq;
423
- }
424
- }
425
- handleSubscribeEmpty(requestedSeq, headers) {
426
- this.subscribeEmptyPolls += 1;
427
- const latest = parseHeaderInt(headers, "X-Gateway-Latest-Seq");
428
- if (Number.isFinite(latest) && this.applyLatestSeqOnEmptyPoll(requestedSeq, latest, headers)) {
429
- return;
430
- }
431
- if (requestedSeq >= 0) {
432
- this.subscribeSeq = requestedSeq;
433
- return;
434
- }
435
- if (this.subscribeEmptyPolls >= 12) {
436
- this.subscribeSeq = TRICKLE_SEQ_LATEST;
437
- this.subscribeEmptyPolls = 0;
438
- }
439
- }
440
- applyLatestSeqOnEmptyPoll(requestedSeq, latest, headers) {
441
- const wait = headers.get("X-Gateway-Wait") === "1";
442
- if (wait && requestedSeq >= 0 && latest < requestedSeq) {
443
- this.subscribeSeq = requestedSeq;
444
- return true;
445
- }
446
- if (isTrickleLeadingIndex(requestedSeq)) {
447
- this.subscribeSeq = latest >= 0 ? latest : TRICKLE_SEQ_LATEST;
448
- this.subscribeEmptyPolls = 0;
449
- return true;
450
- }
451
- if (requestedSeq >= 0 && latest === requestedSeq) {
452
- this.subscribeSeq = requestedSeq;
453
- return true;
454
- }
455
- if (requestedSeq >= 0 && latest > requestedSeq) {
456
- this.subscribeSeq = latest;
457
- this.subscribeEmptyPolls = 0;
458
- return true;
459
- }
460
- return false;
461
- }
462
- async subscribeSegment(seq) {
463
- if (seq !== void 0) {
464
- this.subscribeSeq = seq;
465
- }
466
- const segment = await this.subscribeOutputSegment();
467
- return segment?.data ?? null;
468
- }
469
- async stop() {
470
- if (!this.sessionId || !this.signerToken) {
471
- return;
472
- }
473
- await this.fetchImpl(
474
- `${this.baseUrl}/api/gateway/sessions/${encodeURIComponent(this.sessionId)}`,
475
- {
476
- method: "DELETE",
477
- headers: {
478
- Authorization: `Bearer ${this.signerToken}`
479
- }
480
- }
481
- ).catch(() => void 0);
482
- this.sessionId = null;
483
- this.publishSeq = -1;
484
- this.subscribeSeq = TRICKLE_SEQ_CURRENT;
485
- this.subscribeEmptyPolls = 0;
486
- }
487
- };
488
-
489
- exports.BrowserGatewayClient = BrowserGatewayClient;
490
- exports.resolveSignerToken = resolveSignerToken;
491
- //# sourceMappingURL=index.cjs.map
492
- //# sourceMappingURL=index.cjs.map