@hubbyesim/sdk 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1622 @@
1
+ // lib/default-client-config.ts
2
+ var refuseToSend = async () => {
3
+ throw new Error(
4
+ "This @hubbyesim/sdk operation has no signing client. Call the Partner API through new Hubby({ apiKey, apiSecret })."
5
+ );
6
+ };
7
+ var createClientConfig = (config) => ({
8
+ ...config,
9
+ fetch: refuseToSend,
10
+ throwOnError: true
11
+ });
12
+
13
+ // src/core/bodySerializer.gen.ts
14
+ var jsonBodySerializer = {
15
+ bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value)
16
+ };
17
+
18
+ // src/core/serverSentEvents.gen.ts
19
+ function createSseClient({
20
+ onRequest,
21
+ onSseError,
22
+ onSseEvent,
23
+ responseTransformer,
24
+ responseValidator,
25
+ sseDefaultRetryDelay,
26
+ sseMaxRetryAttempts,
27
+ sseMaxRetryDelay,
28
+ sseSleepFn,
29
+ url,
30
+ ...options
31
+ }) {
32
+ let lastEventId;
33
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
34
+ const createStream = async function* () {
35
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
36
+ let attempt = 0;
37
+ const signal = options.signal ?? new AbortController().signal;
38
+ while (true) {
39
+ if (signal.aborted) break;
40
+ attempt++;
41
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
42
+ if (lastEventId !== void 0) {
43
+ headers.set("Last-Event-ID", lastEventId);
44
+ }
45
+ try {
46
+ const requestInit = {
47
+ redirect: "follow",
48
+ ...options,
49
+ body: options.serializedBody,
50
+ headers,
51
+ signal
52
+ };
53
+ let request = new Request(url, requestInit);
54
+ if (onRequest) {
55
+ request = await onRequest(url, requestInit);
56
+ }
57
+ const _fetch = options.fetch ?? globalThis.fetch;
58
+ const response = await _fetch(request);
59
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
60
+ if (!response.body) throw new Error("No body in SSE response");
61
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
62
+ let buffer = "";
63
+ const abortHandler = () => {
64
+ try {
65
+ reader.cancel();
66
+ } catch {
67
+ }
68
+ };
69
+ signal.addEventListener("abort", abortHandler);
70
+ try {
71
+ while (true) {
72
+ const { done, value } = await reader.read();
73
+ if (done) break;
74
+ buffer += value;
75
+ buffer = buffer.replace(/\r\n?/g, "\n");
76
+ const chunks = buffer.split("\n\n");
77
+ buffer = chunks.pop() ?? "";
78
+ for (const chunk of chunks) {
79
+ const lines = chunk.split("\n");
80
+ const dataLines = [];
81
+ let eventName;
82
+ for (const line of lines) {
83
+ if (line.startsWith("data:")) {
84
+ dataLines.push(line.replace(/^data:\s*/, ""));
85
+ } else if (line.startsWith("event:")) {
86
+ eventName = line.replace(/^event:\s*/, "");
87
+ } else if (line.startsWith("id:")) {
88
+ lastEventId = line.replace(/^id:\s*/, "");
89
+ } else if (line.startsWith("retry:")) {
90
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
91
+ if (!Number.isNaN(parsed)) {
92
+ retryDelay = parsed;
93
+ }
94
+ }
95
+ }
96
+ let data;
97
+ let parsedJson = false;
98
+ if (dataLines.length) {
99
+ const rawData = dataLines.join("\n");
100
+ try {
101
+ data = JSON.parse(rawData);
102
+ parsedJson = true;
103
+ } catch {
104
+ data = rawData;
105
+ }
106
+ }
107
+ if (parsedJson) {
108
+ if (responseValidator) {
109
+ await responseValidator(data);
110
+ }
111
+ if (responseTransformer) {
112
+ data = await responseTransformer(data);
113
+ }
114
+ }
115
+ onSseEvent?.({
116
+ data,
117
+ event: eventName,
118
+ id: lastEventId,
119
+ retry: retryDelay
120
+ });
121
+ if (dataLines.length) {
122
+ yield data;
123
+ }
124
+ }
125
+ }
126
+ } finally {
127
+ signal.removeEventListener("abort", abortHandler);
128
+ reader.releaseLock();
129
+ }
130
+ break;
131
+ } catch (error) {
132
+ onSseError?.(error);
133
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
134
+ break;
135
+ }
136
+ const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
137
+ await sleep(backoff);
138
+ }
139
+ }
140
+ };
141
+ const stream = createStream();
142
+ return { stream };
143
+ }
144
+
145
+ // src/core/pathSerializer.gen.ts
146
+ var separatorArrayExplode = (style) => {
147
+ switch (style) {
148
+ case "label":
149
+ return ".";
150
+ case "matrix":
151
+ return ";";
152
+ case "simple":
153
+ return ",";
154
+ default:
155
+ return "&";
156
+ }
157
+ };
158
+ var separatorArrayNoExplode = (style) => {
159
+ switch (style) {
160
+ case "form":
161
+ return ",";
162
+ case "pipeDelimited":
163
+ return "|";
164
+ case "spaceDelimited":
165
+ return "%20";
166
+ default:
167
+ return ",";
168
+ }
169
+ };
170
+ var separatorObjectExplode = (style) => {
171
+ switch (style) {
172
+ case "label":
173
+ return ".";
174
+ case "matrix":
175
+ return ";";
176
+ case "simple":
177
+ return ",";
178
+ default:
179
+ return "&";
180
+ }
181
+ };
182
+ var serializeArrayParam = ({
183
+ allowReserved,
184
+ explode,
185
+ name,
186
+ style,
187
+ value
188
+ }) => {
189
+ if (!explode) {
190
+ const joinedValues2 = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
191
+ switch (style) {
192
+ case "label":
193
+ return `.${joinedValues2}`;
194
+ case "matrix":
195
+ return `;${name}=${joinedValues2}`;
196
+ case "simple":
197
+ return joinedValues2;
198
+ default:
199
+ return `${name}=${joinedValues2}`;
200
+ }
201
+ }
202
+ const separator = separatorArrayExplode(style);
203
+ const joinedValues = value.map((v) => {
204
+ if (style === "label" || style === "simple") {
205
+ return allowReserved ? v : encodeURIComponent(v);
206
+ }
207
+ return serializePrimitiveParam({
208
+ allowReserved,
209
+ name,
210
+ value: v
211
+ });
212
+ }).join(separator);
213
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
214
+ };
215
+ var serializePrimitiveParam = ({
216
+ allowReserved,
217
+ name,
218
+ value
219
+ }) => {
220
+ if (value === void 0 || value === null) {
221
+ return "";
222
+ }
223
+ if (typeof value === "object") {
224
+ throw new Error(
225
+ "Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
226
+ );
227
+ }
228
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
229
+ };
230
+ var serializeObjectParam = ({
231
+ allowReserved,
232
+ explode,
233
+ name,
234
+ style,
235
+ value,
236
+ valueOnly
237
+ }) => {
238
+ if (value instanceof Date) {
239
+ return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
240
+ }
241
+ if (style !== "deepObject" && !explode) {
242
+ let values = [];
243
+ Object.entries(value).forEach(([key, v]) => {
244
+ values = [...values, key, allowReserved ? v : encodeURIComponent(v)];
245
+ });
246
+ const joinedValues2 = values.join(",");
247
+ switch (style) {
248
+ case "form":
249
+ return `${name}=${joinedValues2}`;
250
+ case "label":
251
+ return `.${joinedValues2}`;
252
+ case "matrix":
253
+ return `;${name}=${joinedValues2}`;
254
+ default:
255
+ return joinedValues2;
256
+ }
257
+ }
258
+ const separator = separatorObjectExplode(style);
259
+ const joinedValues = Object.entries(value).map(
260
+ ([key, v]) => serializePrimitiveParam({
261
+ allowReserved,
262
+ name: style === "deepObject" ? `${name}[${key}]` : key,
263
+ value: v
264
+ })
265
+ ).join(separator);
266
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
267
+ };
268
+
269
+ // src/core/utils.gen.ts
270
+ var PATH_PARAM_RE = /\{[^{}]+\}/g;
271
+ var defaultPathSerializer = ({ path, url: _url }) => {
272
+ let url = _url;
273
+ const matches = _url.match(PATH_PARAM_RE);
274
+ if (matches) {
275
+ for (const match of matches) {
276
+ let explode = false;
277
+ let name = match.substring(1, match.length - 1);
278
+ let style = "simple";
279
+ if (name.endsWith("*")) {
280
+ explode = true;
281
+ name = name.substring(0, name.length - 1);
282
+ }
283
+ if (name.startsWith(".")) {
284
+ name = name.substring(1);
285
+ style = "label";
286
+ } else if (name.startsWith(";")) {
287
+ name = name.substring(1);
288
+ style = "matrix";
289
+ }
290
+ const value = path[name];
291
+ if (value === void 0 || value === null) {
292
+ continue;
293
+ }
294
+ if (Array.isArray(value)) {
295
+ url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
296
+ continue;
297
+ }
298
+ if (typeof value === "object") {
299
+ url = url.replace(
300
+ match,
301
+ serializeObjectParam({
302
+ explode,
303
+ name,
304
+ style,
305
+ value,
306
+ valueOnly: true
307
+ })
308
+ );
309
+ continue;
310
+ }
311
+ if (style === "matrix") {
312
+ url = url.replace(
313
+ match,
314
+ `;${serializePrimitiveParam({
315
+ name,
316
+ value
317
+ })}`
318
+ );
319
+ continue;
320
+ }
321
+ const replaceValue = encodeURIComponent(
322
+ style === "label" ? `.${value}` : value
323
+ );
324
+ url = url.replace(match, replaceValue);
325
+ }
326
+ }
327
+ return url;
328
+ };
329
+ var getUrl = ({
330
+ baseUrl,
331
+ path,
332
+ query,
333
+ querySerializer,
334
+ url: _url
335
+ }) => {
336
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
337
+ let url = (baseUrl ?? "") + pathUrl;
338
+ if (path) {
339
+ url = defaultPathSerializer({ path, url });
340
+ }
341
+ let search = query ? querySerializer(query) : "";
342
+ if (search.startsWith("?")) {
343
+ search = search.substring(1);
344
+ }
345
+ if (search) {
346
+ url += `?${search}`;
347
+ }
348
+ return url;
349
+ };
350
+ function getValidRequestBody(options) {
351
+ const hasBody = options.body !== void 0;
352
+ const isSerializedBody = hasBody && options.bodySerializer;
353
+ if (isSerializedBody) {
354
+ if ("serializedBody" in options) {
355
+ const hasSerializedBody = options.serializedBody !== void 0 && options.serializedBody !== "";
356
+ return hasSerializedBody ? options.serializedBody : null;
357
+ }
358
+ return options.body !== "" ? options.body : null;
359
+ }
360
+ if (hasBody) {
361
+ return options.body;
362
+ }
363
+ return void 0;
364
+ }
365
+
366
+ // src/core/auth.gen.ts
367
+ var getAuthToken = async (auth, callback) => {
368
+ const token = typeof callback === "function" ? await callback(auth) : callback;
369
+ if (!token) {
370
+ return;
371
+ }
372
+ if (auth.scheme === "bearer") {
373
+ return `Bearer ${token}`;
374
+ }
375
+ if (auth.scheme === "basic") {
376
+ return `Basic ${btoa(token)}`;
377
+ }
378
+ return token;
379
+ };
380
+
381
+ // src/client/utils.gen.ts
382
+ var createQuerySerializer = ({
383
+ parameters = {},
384
+ ...args
385
+ } = {}) => {
386
+ const querySerializer = (queryParams) => {
387
+ const search = [];
388
+ if (queryParams && typeof queryParams === "object") {
389
+ for (const name in queryParams) {
390
+ const value = queryParams[name];
391
+ if (value === void 0 || value === null) {
392
+ continue;
393
+ }
394
+ const options = parameters[name] || args;
395
+ if (Array.isArray(value)) {
396
+ const serializedArray = serializeArrayParam({
397
+ allowReserved: options.allowReserved,
398
+ explode: true,
399
+ name,
400
+ style: "form",
401
+ value,
402
+ ...options.array
403
+ });
404
+ if (serializedArray) search.push(serializedArray);
405
+ } else if (typeof value === "object") {
406
+ const serializedObject = serializeObjectParam({
407
+ allowReserved: options.allowReserved,
408
+ explode: true,
409
+ name,
410
+ style: "deepObject",
411
+ value,
412
+ ...options.object
413
+ });
414
+ if (serializedObject) search.push(serializedObject);
415
+ } else {
416
+ const serializedPrimitive = serializePrimitiveParam({
417
+ allowReserved: options.allowReserved,
418
+ name,
419
+ value
420
+ });
421
+ if (serializedPrimitive) search.push(serializedPrimitive);
422
+ }
423
+ }
424
+ }
425
+ return search.join("&");
426
+ };
427
+ return querySerializer;
428
+ };
429
+ var getParseAs = (contentType) => {
430
+ if (!contentType) {
431
+ return "stream";
432
+ }
433
+ const cleanContent = contentType.split(";")[0]?.trim();
434
+ if (!cleanContent) {
435
+ return;
436
+ }
437
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
438
+ return "json";
439
+ }
440
+ if (cleanContent === "multipart/form-data") {
441
+ return "formData";
442
+ }
443
+ if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
444
+ return "blob";
445
+ }
446
+ if (cleanContent.startsWith("text/")) {
447
+ return "text";
448
+ }
449
+ return;
450
+ };
451
+ var checkForExistence = (options, name) => {
452
+ if (!name) {
453
+ return false;
454
+ }
455
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
456
+ return true;
457
+ }
458
+ return false;
459
+ };
460
+ async function setAuthParams(options) {
461
+ for (const auth of options.security ?? []) {
462
+ if (checkForExistence(options, auth.name)) {
463
+ continue;
464
+ }
465
+ const token = await getAuthToken(auth, options.auth);
466
+ if (!token) {
467
+ continue;
468
+ }
469
+ const name = auth.name ?? "Authorization";
470
+ switch (auth.in) {
471
+ case "query":
472
+ if (!options.query) {
473
+ options.query = {};
474
+ }
475
+ options.query[name] = token;
476
+ break;
477
+ case "cookie":
478
+ options.headers.append("Cookie", `${name}=${token}`);
479
+ break;
480
+ case "header":
481
+ default:
482
+ options.headers.set(name, token);
483
+ break;
484
+ }
485
+ }
486
+ }
487
+ var buildUrl = (options) => getUrl({
488
+ baseUrl: options.baseUrl,
489
+ path: options.path,
490
+ query: options.query,
491
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
492
+ url: options.url
493
+ });
494
+ var mergeConfigs = (a, b) => {
495
+ const config = { ...a, ...b };
496
+ if (config.baseUrl?.endsWith("/")) {
497
+ config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
498
+ }
499
+ config.headers = mergeHeaders(a.headers, b.headers);
500
+ return config;
501
+ };
502
+ var headersEntries = (headers) => {
503
+ const entries = [];
504
+ headers.forEach((value, key) => {
505
+ entries.push([key, value]);
506
+ });
507
+ return entries;
508
+ };
509
+ var mergeHeaders = (...headers) => {
510
+ const mergedHeaders = new Headers();
511
+ for (const header of headers) {
512
+ if (!header) {
513
+ continue;
514
+ }
515
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
516
+ for (const [key, value] of iterator) {
517
+ if (value === null) {
518
+ mergedHeaders.delete(key);
519
+ } else if (Array.isArray(value)) {
520
+ for (const v of value) {
521
+ mergedHeaders.append(key, v);
522
+ }
523
+ } else if (value !== void 0) {
524
+ mergedHeaders.set(
525
+ key,
526
+ typeof value === "object" ? JSON.stringify(value) : value
527
+ );
528
+ }
529
+ }
530
+ }
531
+ return mergedHeaders;
532
+ };
533
+ var Interceptors = class {
534
+ fns = [];
535
+ clear() {
536
+ this.fns = [];
537
+ }
538
+ eject(id) {
539
+ const index = this.getInterceptorIndex(id);
540
+ if (this.fns[index]) {
541
+ this.fns[index] = null;
542
+ }
543
+ }
544
+ exists(id) {
545
+ const index = this.getInterceptorIndex(id);
546
+ return Boolean(this.fns[index]);
547
+ }
548
+ getInterceptorIndex(id) {
549
+ if (typeof id === "number") {
550
+ return this.fns[id] ? id : -1;
551
+ }
552
+ return this.fns.indexOf(id);
553
+ }
554
+ update(id, fn) {
555
+ const index = this.getInterceptorIndex(id);
556
+ if (this.fns[index]) {
557
+ this.fns[index] = fn;
558
+ return id;
559
+ }
560
+ return false;
561
+ }
562
+ use(fn) {
563
+ this.fns.push(fn);
564
+ return this.fns.length - 1;
565
+ }
566
+ };
567
+ var createInterceptors = () => ({
568
+ error: new Interceptors(),
569
+ request: new Interceptors(),
570
+ response: new Interceptors()
571
+ });
572
+ var defaultQuerySerializer = createQuerySerializer({
573
+ allowReserved: false,
574
+ array: {
575
+ explode: true,
576
+ style: "form"
577
+ },
578
+ object: {
579
+ explode: true,
580
+ style: "deepObject"
581
+ }
582
+ });
583
+ var defaultHeaders = {
584
+ "Content-Type": "application/json"
585
+ };
586
+ var createConfig = (override = {}) => ({
587
+ ...jsonBodySerializer,
588
+ headers: defaultHeaders,
589
+ parseAs: "auto",
590
+ querySerializer: defaultQuerySerializer,
591
+ ...override
592
+ });
593
+
594
+ // src/client/client.gen.ts
595
+ var createClient = (config = {}) => {
596
+ let _config = mergeConfigs(createConfig(), config);
597
+ const getConfig = () => ({ ..._config });
598
+ const setConfig = (config2) => {
599
+ _config = mergeConfigs(_config, config2);
600
+ return getConfig();
601
+ };
602
+ const interceptors = createInterceptors();
603
+ const beforeRequest = async (options) => {
604
+ const opts = {
605
+ ..._config,
606
+ ...options,
607
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
608
+ headers: mergeHeaders(_config.headers, options.headers),
609
+ serializedBody: void 0
610
+ };
611
+ if (opts.security) {
612
+ await setAuthParams(opts);
613
+ }
614
+ if (opts.requestValidator) {
615
+ await opts.requestValidator(opts);
616
+ }
617
+ if (opts.body !== void 0 && opts.bodySerializer) {
618
+ opts.serializedBody = opts.bodySerializer(opts.body);
619
+ }
620
+ if (opts.body === void 0 || opts.serializedBody === "") {
621
+ opts.headers.delete("Content-Type");
622
+ }
623
+ const resolvedOpts = opts;
624
+ const url = buildUrl(resolvedOpts);
625
+ return { opts: resolvedOpts, url };
626
+ };
627
+ const request = async (options) => {
628
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
629
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
630
+ let request2;
631
+ let response;
632
+ try {
633
+ const { opts, url } = await beforeRequest(options);
634
+ const requestInit = {
635
+ redirect: "follow",
636
+ ...opts,
637
+ body: getValidRequestBody(opts)
638
+ };
639
+ request2 = new Request(url, requestInit);
640
+ for (const fn of interceptors.request.fns) {
641
+ if (fn) {
642
+ request2 = await fn(request2, opts);
643
+ }
644
+ }
645
+ const _fetch = opts.fetch;
646
+ response = await _fetch(request2);
647
+ for (const fn of interceptors.response.fns) {
648
+ if (fn) {
649
+ response = await fn(response, request2, opts);
650
+ }
651
+ }
652
+ const result = {
653
+ request: request2,
654
+ response
655
+ };
656
+ if (response.ok) {
657
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
658
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
659
+ let emptyData;
660
+ switch (parseAs) {
661
+ case "arrayBuffer":
662
+ case "blob":
663
+ case "text":
664
+ emptyData = await response[parseAs]();
665
+ break;
666
+ case "formData":
667
+ emptyData = new FormData();
668
+ break;
669
+ case "stream":
670
+ emptyData = response.body;
671
+ break;
672
+ case "json":
673
+ default:
674
+ emptyData = {};
675
+ break;
676
+ }
677
+ return opts.responseStyle === "data" ? emptyData : {
678
+ data: emptyData,
679
+ ...result
680
+ };
681
+ }
682
+ let data;
683
+ switch (parseAs) {
684
+ case "arrayBuffer":
685
+ case "blob":
686
+ case "formData":
687
+ case "text":
688
+ data = await response[parseAs]();
689
+ break;
690
+ case "json": {
691
+ const text = await response.text();
692
+ data = text ? JSON.parse(text) : {};
693
+ break;
694
+ }
695
+ case "stream":
696
+ return opts.responseStyle === "data" ? response.body : {
697
+ data: response.body,
698
+ ...result
699
+ };
700
+ }
701
+ if (parseAs === "json") {
702
+ if (opts.responseValidator) {
703
+ await opts.responseValidator(data);
704
+ }
705
+ if (opts.responseTransformer) {
706
+ data = await opts.responseTransformer(data);
707
+ }
708
+ }
709
+ return opts.responseStyle === "data" ? data : {
710
+ data,
711
+ ...result
712
+ };
713
+ }
714
+ const textError = await response.text();
715
+ let jsonError;
716
+ try {
717
+ jsonError = JSON.parse(textError);
718
+ } catch {
719
+ }
720
+ throw jsonError ?? textError;
721
+ } catch (error) {
722
+ let finalError = error;
723
+ for (const fn of interceptors.error.fns) {
724
+ if (fn) {
725
+ finalError = await fn(finalError, response, request2, options);
726
+ }
727
+ }
728
+ finalError = finalError || {};
729
+ if (throwOnError) {
730
+ throw finalError;
731
+ }
732
+ return responseStyle === "data" ? void 0 : {
733
+ error: finalError,
734
+ request: request2,
735
+ response
736
+ };
737
+ }
738
+ };
739
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
740
+ const makeSseFn = (method) => async (options) => {
741
+ const { opts, url } = await beforeRequest(options);
742
+ return createSseClient({
743
+ ...opts,
744
+ body: opts.body,
745
+ method,
746
+ onRequest: async (url2, init) => {
747
+ let request2 = new Request(url2, init);
748
+ for (const fn of interceptors.request.fns) {
749
+ if (fn) {
750
+ request2 = await fn(request2, opts);
751
+ }
752
+ }
753
+ return request2;
754
+ },
755
+ serializedBody: getValidRequestBody(opts),
756
+ url
757
+ });
758
+ };
759
+ const _buildUrl = (options) => buildUrl({ ..._config, ...options });
760
+ return {
761
+ buildUrl: _buildUrl,
762
+ connect: makeMethodFn("CONNECT"),
763
+ delete: makeMethodFn("DELETE"),
764
+ get: makeMethodFn("GET"),
765
+ getConfig,
766
+ head: makeMethodFn("HEAD"),
767
+ interceptors,
768
+ options: makeMethodFn("OPTIONS"),
769
+ patch: makeMethodFn("PATCH"),
770
+ post: makeMethodFn("POST"),
771
+ put: makeMethodFn("PUT"),
772
+ request,
773
+ setConfig,
774
+ sse: {
775
+ connect: makeSseFn("CONNECT"),
776
+ delete: makeSseFn("DELETE"),
777
+ get: makeSseFn("GET"),
778
+ head: makeSseFn("HEAD"),
779
+ options: makeSseFn("OPTIONS"),
780
+ patch: makeSseFn("PATCH"),
781
+ post: makeSseFn("POST"),
782
+ put: makeSseFn("PUT"),
783
+ trace: makeSseFn("TRACE")
784
+ },
785
+ trace: makeMethodFn("TRACE")
786
+ };
787
+ };
788
+
789
+ // src/client.gen.ts
790
+ var client = createClient(createClientConfig(createConfig({ baseUrl: "http://localhost/api" })));
791
+
792
+ // src/sdk.gen.ts
793
+ var partnerApiV1AnalyticsSummary = (options) => (options?.client ?? client).get({
794
+ security: [
795
+ { name: "X-Api-Key", type: "apiKey" },
796
+ { name: "X-Signature", type: "apiKey" },
797
+ { name: "X-Timestamp", type: "apiKey" }
798
+ ],
799
+ url: "/v1/analytics/summary",
800
+ ...options
801
+ });
802
+ var partnerApiV1AnalyticsFunnel = (options) => (options?.client ?? client).get({
803
+ security: [
804
+ { name: "X-Api-Key", type: "apiKey" },
805
+ { name: "X-Signature", type: "apiKey" },
806
+ { name: "X-Timestamp", type: "apiKey" }
807
+ ],
808
+ url: "/v1/analytics/funnel",
809
+ ...options
810
+ });
811
+ var partnerApiV1AnalyticsTimeseries = (options) => (options?.client ?? client).get({
812
+ security: [
813
+ { name: "X-Api-Key", type: "apiKey" },
814
+ { name: "X-Signature", type: "apiKey" },
815
+ { name: "X-Timestamp", type: "apiKey" }
816
+ ],
817
+ url: "/v1/analytics/timeseries",
818
+ ...options
819
+ });
820
+ var partnerApiV1AnalyticsBreakdowns = (options) => (options?.client ?? client).get({
821
+ security: [
822
+ { name: "X-Api-Key", type: "apiKey" },
823
+ { name: "X-Signature", type: "apiKey" },
824
+ { name: "X-Timestamp", type: "apiKey" }
825
+ ],
826
+ url: "/v1/analytics/breakdowns",
827
+ ...options
828
+ });
829
+ var partnerApiV1BookingsIndex = (options) => (options?.client ?? client).get({
830
+ security: [
831
+ { name: "X-Api-Key", type: "apiKey" },
832
+ { name: "X-Signature", type: "apiKey" },
833
+ { name: "X-Timestamp", type: "apiKey" }
834
+ ],
835
+ url: "/v1/bookings",
836
+ ...options
837
+ });
838
+ var partnerApiV1BookingsStore = (options) => (options.client ?? client).post({
839
+ security: [
840
+ { name: "X-Api-Key", type: "apiKey" },
841
+ { name: "X-Signature", type: "apiKey" },
842
+ { name: "X-Timestamp", type: "apiKey" }
843
+ ],
844
+ url: "/v1/bookings",
845
+ ...options,
846
+ headers: {
847
+ "Content-Type": "application/json",
848
+ ...options.headers
849
+ }
850
+ });
851
+ var partnerApiV1BookingsImportsPresignUpload = (options) => (options.client ?? client).post({
852
+ security: [
853
+ { name: "X-Api-Key", type: "apiKey" },
854
+ { name: "X-Signature", type: "apiKey" },
855
+ { name: "X-Timestamp", type: "apiKey" }
856
+ ],
857
+ url: "/v1/bookings/imports/presign-upload",
858
+ ...options,
859
+ headers: {
860
+ "Content-Type": "application/json",
861
+ ...options.headers
862
+ }
863
+ });
864
+ var partnerApiV1BookingsUpdatePackageSpecifications = (options) => (options.client ?? client).patch({
865
+ security: [
866
+ { name: "X-Api-Key", type: "apiKey" },
867
+ { name: "X-Signature", type: "apiKey" },
868
+ { name: "X-Timestamp", type: "apiKey" }
869
+ ],
870
+ url: "/v1/bookings/{bookingId}/package-specifications/{uuid}",
871
+ ...options,
872
+ headers: {
873
+ "Content-Type": "application/json",
874
+ ...options.headers
875
+ }
876
+ });
877
+ var partnerApiV1BookingsDestroy = (options) => (options.client ?? client).delete({
878
+ security: [
879
+ { name: "X-Api-Key", type: "apiKey" },
880
+ { name: "X-Signature", type: "apiKey" },
881
+ { name: "X-Timestamp", type: "apiKey" }
882
+ ],
883
+ url: "/v1/bookings/{id}",
884
+ ...options
885
+ });
886
+ var partnerApiV1BookingsShow = (options) => (options.client ?? client).get({
887
+ security: [
888
+ { name: "X-Api-Key", type: "apiKey" },
889
+ { name: "X-Signature", type: "apiKey" },
890
+ { name: "X-Timestamp", type: "apiKey" }
891
+ ],
892
+ url: "/v1/bookings/{id}",
893
+ ...options
894
+ });
895
+ var partnerApiV1BookingsUpdate = (options) => (options.client ?? client).patch({
896
+ security: [
897
+ { name: "X-Api-Key", type: "apiKey" },
898
+ { name: "X-Signature", type: "apiKey" },
899
+ { name: "X-Timestamp", type: "apiKey" }
900
+ ],
901
+ url: "/v1/bookings/{id}",
902
+ ...options,
903
+ headers: {
904
+ "Content-Type": "application/json",
905
+ ...options.headers
906
+ }
907
+ });
908
+ var partnerApiV1BookingsItemsPackageDestroy = (options) => (options.client ?? client).delete({
909
+ security: [
910
+ { name: "X-Api-Key", type: "apiKey" },
911
+ { name: "X-Signature", type: "apiKey" },
912
+ { name: "X-Timestamp", type: "apiKey" }
913
+ ],
914
+ url: "/v1/bookings/{booking}/items/{item}/package",
915
+ ...options
916
+ });
917
+ var partnerApiV1BookingsItemsPackageShow = (options) => (options.client ?? client).get({
918
+ security: [
919
+ { name: "X-Api-Key", type: "apiKey" },
920
+ { name: "X-Signature", type: "apiKey" },
921
+ { name: "X-Timestamp", type: "apiKey" }
922
+ ],
923
+ url: "/v1/bookings/{booking}/items/{item}/package",
924
+ ...options
925
+ });
926
+ var partnerApiV1BookingsItemsPackageStore = (options) => (options.client ?? client).post({
927
+ security: [
928
+ { name: "X-Api-Key", type: "apiKey" },
929
+ { name: "X-Signature", type: "apiKey" },
930
+ { name: "X-Timestamp", type: "apiKey" }
931
+ ],
932
+ url: "/v1/bookings/{booking}/items/{item}/package",
933
+ ...options,
934
+ headers: {
935
+ "Content-Type": "application/json",
936
+ ...options.headers
937
+ }
938
+ });
939
+ var partnerApiV1DestinationsBundlesIndex = (options) => (options.client ?? client).get({
940
+ security: [
941
+ { name: "X-Api-Key", type: "apiKey" },
942
+ { name: "X-Signature", type: "apiKey" },
943
+ { name: "X-Timestamp", type: "apiKey" }
944
+ ],
945
+ url: "/v1/destinations/{destination}/bundles",
946
+ ...options
947
+ });
948
+ var partnerApiV1BundlesIndex = (options) => (options?.client ?? client).get({
949
+ security: [
950
+ { name: "X-Api-Key", type: "apiKey" },
951
+ { name: "X-Signature", type: "apiKey" },
952
+ { name: "X-Timestamp", type: "apiKey" }
953
+ ],
954
+ url: "/v1/bundles",
955
+ ...options
956
+ });
957
+ var partnerApiV1Me = (options) => (options?.client ?? client).get({
958
+ security: [
959
+ { name: "X-Api-Key", type: "apiKey" },
960
+ { name: "X-Signature", type: "apiKey" },
961
+ { name: "X-Timestamp", type: "apiKey" }
962
+ ],
963
+ url: "/v1/me",
964
+ ...options
965
+ });
966
+ var partnerApiV1Destinations = (options) => (options?.client ?? client).get({
967
+ security: [
968
+ { name: "X-Api-Key", type: "apiKey" },
969
+ { name: "X-Signature", type: "apiKey" },
970
+ { name: "X-Timestamp", type: "apiKey" }
971
+ ],
972
+ url: "/v1/destinations",
973
+ ...options
974
+ });
975
+ var partnerApiV1DestinationsShow = (options) => (options.client ?? client).get({
976
+ security: [
977
+ { name: "X-Api-Key", type: "apiKey" },
978
+ { name: "X-Signature", type: "apiKey" },
979
+ { name: "X-Timestamp", type: "apiKey" }
980
+ ],
981
+ url: "/v1/destinations/{destination}",
982
+ ...options
983
+ });
984
+ var partnerApiV1DeviceCompatibilityIndex = (options) => (options?.client ?? client).get({
985
+ security: [
986
+ { name: "X-Api-Key", type: "apiKey" },
987
+ { name: "X-Signature", type: "apiKey" },
988
+ { name: "X-Timestamp", type: "apiKey" }
989
+ ],
990
+ url: "/v1/device-compatibility",
991
+ ...options
992
+ });
993
+ var partnerApiV1DeviceCompatibilityCheck = (options) => (options.client ?? client).post({
994
+ security: [
995
+ { name: "X-Api-Key", type: "apiKey" },
996
+ { name: "X-Signature", type: "apiKey" },
997
+ { name: "X-Timestamp", type: "apiKey" }
998
+ ],
999
+ url: "/v1/device-compatibility",
1000
+ ...options,
1001
+ headers: {
1002
+ "Content-Type": "application/json",
1003
+ ...options.headers
1004
+ }
1005
+ });
1006
+ var partnerApiV1EsimsShow = (options) => (options.client ?? client).get({
1007
+ security: [
1008
+ { name: "X-Api-Key", type: "apiKey" },
1009
+ { name: "X-Signature", type: "apiKey" },
1010
+ { name: "X-Timestamp", type: "apiKey" }
1011
+ ],
1012
+ url: "/v1/esims/{iccid}",
1013
+ ...options
1014
+ });
1015
+ var partnerApiV1EsimsInstructions = (options) => (options.client ?? client).get({
1016
+ security: [
1017
+ { name: "X-Api-Key", type: "apiKey" },
1018
+ { name: "X-Signature", type: "apiKey" },
1019
+ { name: "X-Timestamp", type: "apiKey" }
1020
+ ],
1021
+ url: "/v1/esims/{iccid}/instructions",
1022
+ ...options
1023
+ });
1024
+ var partnerApiV1RedirectTokensCreate = (options) => (options.client ?? client).post({
1025
+ security: [
1026
+ { name: "X-Api-Key", type: "apiKey" },
1027
+ { name: "X-Signature", type: "apiKey" },
1028
+ { name: "X-Timestamp", type: "apiKey" }
1029
+ ],
1030
+ url: "/v1/redirect-tokens",
1031
+ ...options,
1032
+ headers: {
1033
+ "Content-Type": "application/json",
1034
+ ...options.headers
1035
+ }
1036
+ });
1037
+
1038
+ // lib/api-error.ts
1039
+ var HubbyApiError = class extends Error {
1040
+ status;
1041
+ /** The refusal code, when the body has one, for example `idempotency_key_in_use`. */
1042
+ code;
1043
+ /** The field errors of a `422`, keyed by field; absent when the body's `errors` has another shape. */
1044
+ errors;
1045
+ /** The parsed error body: JSON when it parses, else the text. */
1046
+ body;
1047
+ response;
1048
+ constructor(response, body) {
1049
+ const answer = isRecord(body) ? body : {};
1050
+ super(
1051
+ typeof answer.message === "string" && answer.message !== "" ? answer.message : `Partner API answered ${response.status}`
1052
+ );
1053
+ Object.setPrototypeOf(this, new.target.prototype);
1054
+ this.name = "HubbyApiError";
1055
+ this.status = response.status;
1056
+ this.code = typeof answer.code === "string" ? answer.code : void 0;
1057
+ this.errors = isFieldErrors(answer.errors) ? answer.errors : void 0;
1058
+ this.body = body;
1059
+ this.response = response;
1060
+ }
1061
+ };
1062
+ function isRecord(value) {
1063
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1064
+ }
1065
+ function isFieldErrors(value) {
1066
+ return isRecord(value) && Object.values(value).every(
1067
+ (messages) => Array.isArray(messages) && messages.every((message) => typeof message === "string")
1068
+ );
1069
+ }
1070
+
1071
+ // lib/crypto.ts
1072
+ var encoder = new TextEncoder();
1073
+ function utf8(text) {
1074
+ return encoder.encode(text);
1075
+ }
1076
+ function toHex(bytes) {
1077
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
1078
+ let hex = "";
1079
+ for (const byte of view) {
1080
+ hex += byte.toString(16).padStart(2, "0");
1081
+ }
1082
+ return hex;
1083
+ }
1084
+ async function sha256Hex(bytes) {
1085
+ return toHex(await crypto.subtle.digest("SHA-256", bytes));
1086
+ }
1087
+ async function hmacSha256(secret, message) {
1088
+ const key = await crypto.subtle.importKey(
1089
+ "raw",
1090
+ utf8(secret),
1091
+ { name: "HMAC", hash: "SHA-256" },
1092
+ false,
1093
+ ["sign"]
1094
+ );
1095
+ return new Uint8Array(await crypto.subtle.sign("HMAC", key, message));
1096
+ }
1097
+ function constantTimeEqual(left, right) {
1098
+ if (left.length !== right.length) {
1099
+ return false;
1100
+ }
1101
+ let difference = 0;
1102
+ for (let index = 0; index < left.length; index++) {
1103
+ difference |= left[index] ^ right[index];
1104
+ }
1105
+ return difference === 0;
1106
+ }
1107
+
1108
+ // lib/signing.ts
1109
+ var SIGNATURE_VERSION = "HUBBY-HMAC-SHA256-v1";
1110
+ async function signRequest(secret, request) {
1111
+ const url = new URL(request.url);
1112
+ const canonical = [
1113
+ SIGNATURE_VERSION,
1114
+ request.method.toUpperCase(),
1115
+ url.host,
1116
+ url.pathname + url.search,
1117
+ String(request.timestamp),
1118
+ await sha256Hex(request.body)
1119
+ ].join("\n");
1120
+ return toHex(await hmacSha256(secret, utf8(canonical)));
1121
+ }
1122
+
1123
+ // lib/version.ts
1124
+ var SUPPORTED_API_VERSION = "5.13.0";
1125
+ var SDK_VERSION = "2.1.0";
1126
+
1127
+ // lib/transport.ts
1128
+ var DEFAULT_RETRY = {
1129
+ maxAttempts: 3,
1130
+ maxDelayMs: 3e4,
1131
+ baseDelayMs: 500
1132
+ };
1133
+ var IDEMPOTENCY_HEADER = "Idempotency-Key";
1134
+ var USER_AGENT = `hubbyesim-sdk/${SDK_VERSION}`;
1135
+ var REPEATABLE_AFTER_NETWORK_ERROR = /* @__PURE__ */ new Set(["GET", "HEAD", "POST"]);
1136
+ function createPartnerFetch(options) {
1137
+ return async (request) => {
1138
+ const body = new Uint8Array(await request.arrayBuffer());
1139
+ const headers = new Headers(request.headers);
1140
+ if (request.method === "POST" && !headers.has(IDEMPOTENCY_HEADER)) {
1141
+ headers.set(IDEMPOTENCY_HEADER, options.idempotencyKey());
1142
+ }
1143
+ if (!headers.has("User-Agent")) {
1144
+ headers.set("User-Agent", USER_AGENT);
1145
+ }
1146
+ const send = async () => {
1147
+ const timestamp = Math.floor(options.now() / 1e3);
1148
+ const signed = new Headers(headers);
1149
+ signed.set("X-Api-Key", options.apiKey);
1150
+ signed.set("X-Timestamp", String(timestamp));
1151
+ signed.set(
1152
+ "X-Signature",
1153
+ await signRequest(options.apiSecret, {
1154
+ method: request.method,
1155
+ url: request.url,
1156
+ timestamp,
1157
+ body
1158
+ })
1159
+ );
1160
+ return options.fetch(
1161
+ new Request(request.url, {
1162
+ method: request.method,
1163
+ headers: signed,
1164
+ body: body.length > 0 ? body : void 0,
1165
+ signal: request.signal,
1166
+ redirect: request.redirect
1167
+ })
1168
+ );
1169
+ };
1170
+ for (let attempt = 1; ; attempt++) {
1171
+ const isLastAttempt = attempt >= options.retry.maxAttempts;
1172
+ const backoff = options.retry.baseDelayMs * 2 ** (attempt - 1);
1173
+ let response;
1174
+ try {
1175
+ response = await send();
1176
+ } catch (error) {
1177
+ if (isLastAttempt || request.signal.aborted || !REPEATABLE_AFTER_NETWORK_ERROR.has(request.method)) {
1178
+ throw error;
1179
+ }
1180
+ await waitUnlessAborted(
1181
+ options.sleep,
1182
+ Math.min(backoff, options.retry.maxDelayMs),
1183
+ request.signal
1184
+ );
1185
+ continue;
1186
+ }
1187
+ if (isLastAttempt || request.signal.aborted || !await isRetryable(response)) {
1188
+ return response;
1189
+ }
1190
+ const delay = retryAfterMilliseconds(response.headers.get("Retry-After"), options.now()) ?? backoff;
1191
+ if (delay > options.retry.maxDelayMs) {
1192
+ return response;
1193
+ }
1194
+ await response.body?.cancel();
1195
+ await waitUnlessAborted(options.sleep, delay, request.signal);
1196
+ }
1197
+ };
1198
+ }
1199
+ function waitUnlessAborted(sleep, milliseconds, signal) {
1200
+ if (signal.aborted) {
1201
+ return Promise.reject(abortReason(signal));
1202
+ }
1203
+ return new Promise((resolve, reject) => {
1204
+ const onAbort = () => reject(abortReason(signal));
1205
+ signal.addEventListener("abort", onAbort, { once: true });
1206
+ sleep(milliseconds).then(
1207
+ () => {
1208
+ signal.removeEventListener("abort", onAbort);
1209
+ resolve();
1210
+ },
1211
+ (error) => {
1212
+ signal.removeEventListener("abort", onAbort);
1213
+ reject(error);
1214
+ }
1215
+ );
1216
+ });
1217
+ }
1218
+ function abortReason(signal) {
1219
+ return signal.reason ?? new DOMException("This operation was aborted", "AbortError");
1220
+ }
1221
+ async function isRetryable(response) {
1222
+ if (response.status === 429) {
1223
+ return true;
1224
+ }
1225
+ if (response.status !== 409) {
1226
+ return false;
1227
+ }
1228
+ try {
1229
+ const answer = await response.clone().json();
1230
+ return answer.code === "idempotency_key_in_use";
1231
+ } catch {
1232
+ return false;
1233
+ }
1234
+ }
1235
+ function retryAfterMilliseconds(header, now) {
1236
+ if (header === null || header.trim() === "") {
1237
+ return null;
1238
+ }
1239
+ if (/^\d+$/.test(header.trim())) {
1240
+ return Number(header.trim()) * 1e3;
1241
+ }
1242
+ const date = Date.parse(header);
1243
+ return Number.isNaN(date) ? null : Math.max(0, date - now);
1244
+ }
1245
+
1246
+ // lib/client.ts
1247
+ var defaultSleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
1248
+ function createSigningClient(options) {
1249
+ assertValidOptions(options);
1250
+ const partnerFetch = createPartnerFetch({
1251
+ apiKey: options.apiKey,
1252
+ apiSecret: options.apiSecret,
1253
+ fetch: options.fetch ?? ((request) => globalThis.fetch(request)),
1254
+ now: options.now ?? Date.now,
1255
+ sleep: options.sleep ?? defaultSleep,
1256
+ idempotencyKey: () => crypto.randomUUID(),
1257
+ retry: options.retry === false ? { ...DEFAULT_RETRY, maxAttempts: 1 } : { ...DEFAULT_RETRY, ...options.retry }
1258
+ });
1259
+ const client2 = createClient(
1260
+ createConfig({
1261
+ baseUrl: options.baseUrl.replace(/\/+$/, ""),
1262
+ // The option is typed as the global `fetch`, but the generated client always calls it with one built `Request`.
1263
+ fetch: (input) => partnerFetch(input)
1264
+ })
1265
+ );
1266
+ client2.interceptors.error.use(rejectWhenNothingWasAnswered);
1267
+ return client2;
1268
+ }
1269
+ function assertValidOptions(options) {
1270
+ if (typeof options.apiKey !== "string" || options.apiKey === "") {
1271
+ throw new Error(`Hubby: \`apiKey\` must be a non-empty string.`);
1272
+ }
1273
+ if (typeof options.apiSecret !== "string" || options.apiSecret === "") {
1274
+ throw new Error(`Hubby: \`apiSecret\` must be a non-empty string.`);
1275
+ }
1276
+ if (typeof options.baseUrl !== "string" || !URL.canParse(options.baseUrl)) {
1277
+ throw new Error(
1278
+ `Hubby: \`baseUrl\` must be an absolute URL, for example \`https://core.hubbyesim.com/api\`.`
1279
+ );
1280
+ }
1281
+ }
1282
+ function rejectWhenNothingWasAnswered(error, response) {
1283
+ if (response === void 0) {
1284
+ throw error;
1285
+ }
1286
+ return error;
1287
+ }
1288
+
1289
+ // lib/hubby.ts
1290
+ var PRODUCTION_BASE_URL = "https://core.hubbyesim.com/api";
1291
+ var Hubby = class {
1292
+ #client;
1293
+ constructor(options) {
1294
+ this.#client = createSigningClient({
1295
+ ...options,
1296
+ baseUrl: options.baseUrl ?? PRODUCTION_BASE_URL
1297
+ });
1298
+ }
1299
+ /** `GET /v1/me`: the Partner the API key belongs to, and its settings. */
1300
+ async getPartner(options = {}) {
1301
+ return unwrap(await partnerApiV1Me({ client: this.#client, ...options }));
1302
+ }
1303
+ /** `GET /v1/bundles`: the Bundles you sell. */
1304
+ async getBundles(options = {}) {
1305
+ return unwrap(await partnerApiV1BundlesIndex({ client: this.#client, ...options }));
1306
+ }
1307
+ /**
1308
+ * `POST /v1/bookings`: creates a booking. The SDK gives the call an
1309
+ * `Idempotency-Key` and keeps it across its retries.
1310
+ */
1311
+ async createBooking(input, options = {}) {
1312
+ return unwrap(
1313
+ await partnerApiV1BookingsStore({ client: this.#client, ...options, body: input })
1314
+ );
1315
+ }
1316
+ /**
1317
+ * `POST /v1/bookings/{booking}/items/{item}/package`: issues the item's
1318
+ * package. Create `idempotencyKey` once per issue attempt and store it with
1319
+ * your own purchase record before the call, so a retry after a crash
1320
+ * replays rather than issues again. After a terminate, the next issue of
1321
+ * the item needs a new key.
1322
+ */
1323
+ async issuePackage(booking, item, input, options = {}) {
1324
+ const { idempotencyKey, ...body } = input;
1325
+ return unwrap(
1326
+ await partnerApiV1BookingsItemsPackageStore({
1327
+ client: this.#client,
1328
+ ...options,
1329
+ path: { booking, item },
1330
+ headers: { ...options.headers, "Idempotency-Key": idempotencyKey },
1331
+ body
1332
+ })
1333
+ );
1334
+ }
1335
+ /** `GET /v1/bookings/{booking}/items/{item}/package`: the package issued for the item. */
1336
+ async getPackage(booking, item, options = {}) {
1337
+ return unwrap(
1338
+ await partnerApiV1BookingsItemsPackageShow({
1339
+ client: this.#client,
1340
+ ...options,
1341
+ path: { booking, item }
1342
+ })
1343
+ );
1344
+ }
1345
+ /**
1346
+ * `DELETE /v1/bookings/{booking}/items/{item}/package`: terminates the
1347
+ * item's package. Resolves once the Partner API answers `204`. Not retried
1348
+ * after a network error: read the package to learn whether it ended.
1349
+ */
1350
+ async terminatePackage(booking, item, input = {}, options = {}) {
1351
+ unwrap(
1352
+ await partnerApiV1BookingsItemsPackageDestroy({
1353
+ client: this.#client,
1354
+ ...options,
1355
+ path: { booking, item },
1356
+ query: input.reason === void 0 ? void 0 : { reason: input.reason }
1357
+ })
1358
+ );
1359
+ }
1360
+ /**
1361
+ * `GET /v1/esims/{iccid}`: the eSIM and its packages. `refresh: true` reads
1362
+ * it from the network now instead of answering the last readout; it is
1363
+ * rate-limited per Partner.
1364
+ */
1365
+ async getEsim(iccid, input = {}, options = {}) {
1366
+ return unwrap(
1367
+ await partnerApiV1EsimsShow({
1368
+ client: this.#client,
1369
+ ...options,
1370
+ path: { iccid },
1371
+ query: input.refresh === void 0 ? void 0 : { refresh: `${input.refresh}` }
1372
+ })
1373
+ );
1374
+ }
1375
+ /** `GET /v1/destinations`: the destinations, filtered by `iso3` when given. */
1376
+ async getDestinations(filters = {}, options = {}) {
1377
+ return unwrap(
1378
+ await partnerApiV1Destinations({ client: this.#client, ...options, query: filters })
1379
+ );
1380
+ }
1381
+ /**
1382
+ * `GET /v1/destinations/{destination}`: one destination.
1383
+ *
1384
+ * @param destination a ULID, an ISO alpha-2 or alpha-3 code, or a name
1385
+ * (English, exact, any letter case).
1386
+ */
1387
+ async getDestination(destination, options = {}) {
1388
+ return unwrap(
1389
+ await partnerApiV1DestinationsShow({
1390
+ client: this.#client,
1391
+ ...options,
1392
+ path: { destination }
1393
+ })
1394
+ );
1395
+ }
1396
+ /**
1397
+ * `GET /v1/destinations/{destination}/bundles`: the Bundles you sell for one destination.
1398
+ *
1399
+ * @param destination a ULID, an ISO alpha-2 or alpha-3 code, or a name
1400
+ * (English, exact, any letter case).
1401
+ */
1402
+ async getDestinationBundles(destination, options = {}) {
1403
+ return unwrap(
1404
+ await partnerApiV1DestinationsBundlesIndex({
1405
+ client: this.#client,
1406
+ ...options,
1407
+ path: { destination }
1408
+ })
1409
+ );
1410
+ }
1411
+ /** `GET /v1/bookings`: a page of your bookings, filtered and sorted. Pass `pagination.nextCursor` back as `cursor` for the next page. */
1412
+ async getBookings(filters = {}, options = {}) {
1413
+ return unwrap(
1414
+ await partnerApiV1BookingsIndex({ client: this.#client, ...options, query: filters })
1415
+ );
1416
+ }
1417
+ /** `GET /v1/bookings/{id}`: one booking, by any identifier the guide lists. */
1418
+ async getBooking(id, options = {}) {
1419
+ return unwrap(
1420
+ await partnerApiV1BookingsShow({ client: this.#client, ...options, path: { id } })
1421
+ );
1422
+ }
1423
+ /** `PATCH /v1/bookings/{id}`: changes a booking. Not retried after a network error. */
1424
+ async updateBooking(id, input, options = {}) {
1425
+ return unwrap(
1426
+ await partnerApiV1BookingsUpdate({
1427
+ client: this.#client,
1428
+ ...options,
1429
+ path: { id },
1430
+ body: input
1431
+ })
1432
+ );
1433
+ }
1434
+ /**
1435
+ * `DELETE /v1/bookings/{id}`: cancels the booking (a Booking cancellation).
1436
+ * Resolves once the Partner API accepts it. Not retried after a network
1437
+ * error: read the booking to learn whether it was cancelled.
1438
+ */
1439
+ async cancelBooking(id, options = {}) {
1440
+ unwrap(
1441
+ await partnerApiV1BookingsDestroy({ client: this.#client, ...options, path: { id } })
1442
+ );
1443
+ }
1444
+ /**
1445
+ * `PATCH /v1/bookings/{booking_id}/package-specifications/{uuid}`: changes
1446
+ * the package specification of one item, by its package queue `uuid`.
1447
+ */
1448
+ async updatePackageSpecification(bookingId, uuid, input, options = {}) {
1449
+ return unwrap(
1450
+ await partnerApiV1BookingsUpdatePackageSpecifications({
1451
+ client: this.#client,
1452
+ ...options,
1453
+ path: { bookingId, uuid },
1454
+ body: input
1455
+ })
1456
+ );
1457
+ }
1458
+ /**
1459
+ * `PATCH /v1/bookings/{booking_id}/package-specifications`: changes the
1460
+ * package specification of every item of the booking not yet issued.
1461
+ */
1462
+ async updatePackageSpecifications(bookingId, input, options = {}) {
1463
+ const withoutItem = {
1464
+ client: this.#client,
1465
+ ...options,
1466
+ url: "/v1/bookings/{bookingId}/package-specifications",
1467
+ path: { bookingId, uuid: null },
1468
+ body: input
1469
+ };
1470
+ return unwrap(await partnerApiV1BookingsUpdatePackageSpecifications(withoutItem));
1471
+ }
1472
+ /** `POST /v1/bookings/imports/presign-upload`: a URL to upload a booking import file to. */
1473
+ async presignBookingImport(input, options = {}) {
1474
+ return unwrap(
1475
+ await partnerApiV1BookingsImportsPresignUpload({
1476
+ client: this.#client,
1477
+ ...options,
1478
+ body: input
1479
+ })
1480
+ );
1481
+ }
1482
+ /**
1483
+ * `GET /v1/esims/{iccid}/instructions`: how to install the eSIM, in `locale`
1484
+ * when given, else in the locale of the booking that issued it.
1485
+ */
1486
+ async getEsimInstructions(iccid, input = {}, options = {}) {
1487
+ return unwrap(
1488
+ await partnerApiV1EsimsInstructions({
1489
+ client: this.#client,
1490
+ ...options,
1491
+ path: { iccid },
1492
+ query: input.locale === void 0 ? void 0 : { locale: input.locale }
1493
+ })
1494
+ );
1495
+ }
1496
+ /** `GET /v1/device-compatibility`: the devices that take an eSIM. */
1497
+ async getCompatibleDevices(options = {}) {
1498
+ return unwrap(
1499
+ await partnerApiV1DeviceCompatibilityIndex({ client: this.#client, ...options })
1500
+ );
1501
+ }
1502
+ /** `POST /v1/device-compatibility`: whether one device takes an eSIM. */
1503
+ async checkDeviceCompatibility(input, options = {}) {
1504
+ return unwrap(
1505
+ await partnerApiV1DeviceCompatibilityCheck({
1506
+ client: this.#client,
1507
+ ...options,
1508
+ body: input
1509
+ })
1510
+ );
1511
+ }
1512
+ /** `POST /v1/redirect-tokens`: a redirect token that signs one of your travellers in, creating their Customer on first use. */
1513
+ async createRedirectToken(input, options = {}) {
1514
+ return unwrap(
1515
+ await partnerApiV1RedirectTokensCreate({
1516
+ client: this.#client,
1517
+ ...options,
1518
+ body: input
1519
+ })
1520
+ );
1521
+ }
1522
+ /** `GET /v1/analytics/summary`: the totals for a date range, with the preceding period when `compare` is set. */
1523
+ async getAnalyticsSummary(query = {}, options = {}) {
1524
+ return unwrap(
1525
+ await partnerApiV1AnalyticsSummary({ client: this.#client, ...options, query })
1526
+ );
1527
+ }
1528
+ /** `GET /v1/analytics/funnel`: booking codes issued through to first data used, split by `dimension` when given. */
1529
+ async getAnalyticsFunnel(query = {}, options = {}) {
1530
+ return unwrap(
1531
+ await partnerApiV1AnalyticsFunnel({ client: this.#client, ...options, query })
1532
+ );
1533
+ }
1534
+ /** `GET /v1/analytics/timeseries`: the totals per `bucket` (day, week or month). */
1535
+ async getAnalyticsTimeseries(query = {}, options = {}) {
1536
+ return unwrap(
1537
+ await partnerApiV1AnalyticsTimeseries({ client: this.#client, ...options, query })
1538
+ );
1539
+ }
1540
+ /** `GET /v1/analytics/breakdowns`: the totals split by each dimension. */
1541
+ async getAnalyticsBreakdowns(query = {}, options = {}) {
1542
+ return unwrap(
1543
+ await partnerApiV1AnalyticsBreakdowns({ client: this.#client, ...options, query })
1544
+ );
1545
+ }
1546
+ };
1547
+ function unwrap(result) {
1548
+ if (result.error !== void 0 || result.response?.ok !== true) {
1549
+ throw new HubbyApiError(result.response, result.error);
1550
+ }
1551
+ return result.data;
1552
+ }
1553
+
1554
+ // lib/webhooks.ts
1555
+ var WebhookVerificationError = class extends Error {
1556
+ constructor(reason, message) {
1557
+ super(message);
1558
+ this.reason = reason;
1559
+ this.name = "WebhookVerificationError";
1560
+ }
1561
+ reason;
1562
+ };
1563
+ var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
1564
+ async function verifyWebhook(options) {
1565
+ const signatureHeader = readHeader(options.headers, "x-hubby-signature");
1566
+ const timestampHeader = readHeader(options.headers, "x-hubby-timestamp");
1567
+ if (!signatureHeader) {
1568
+ throw new WebhookVerificationError(
1569
+ "missing_signature",
1570
+ "The delivery carries no x-hubby-signature header."
1571
+ );
1572
+ }
1573
+ if (!timestampHeader || !/^\d+$/.test(timestampHeader)) {
1574
+ throw new WebhookVerificationError(
1575
+ "missing_timestamp",
1576
+ "The delivery carries no x-hubby-timestamp header in unix seconds."
1577
+ );
1578
+ }
1579
+ const tolerance = options.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
1580
+ const nowSeconds = Math.floor((options.now ?? Date.now)() / 1e3);
1581
+ if (Math.abs(nowSeconds - Number(timestampHeader)) > tolerance) {
1582
+ throw new WebhookVerificationError(
1583
+ "stale_timestamp",
1584
+ `x-hubby-timestamp is more than ${tolerance} seconds from now.`
1585
+ );
1586
+ }
1587
+ const body = typeof options.body === "string" ? utf8(options.body) : options.body;
1588
+ const signedPayload = new Uint8Array([...utf8(`${timestampHeader}.`), ...body]);
1589
+ const expected = utf8(`sha256=${toHex(await hmacSha256(options.secret, signedPayload))}`);
1590
+ let matched = false;
1591
+ for (const candidate of signatureHeader.split(",")) {
1592
+ matched = constantTimeEqual(expected, utf8(candidate.trim())) || matched;
1593
+ }
1594
+ if (!matched) {
1595
+ throw new WebhookVerificationError(
1596
+ "invalid_signature",
1597
+ "No x-hubby-signature entry matches the body."
1598
+ );
1599
+ }
1600
+ try {
1601
+ return JSON.parse(new TextDecoder().decode(body));
1602
+ } catch {
1603
+ throw new WebhookVerificationError("invalid_body", "The signed body is not JSON.");
1604
+ }
1605
+ }
1606
+ function readHeader(headers, name) {
1607
+ if (headers instanceof Headers) {
1608
+ return headers.get(name);
1609
+ }
1610
+ const match = Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];
1611
+ return Array.isArray(match) ? match[0] ?? null : match ?? null;
1612
+ }
1613
+ export {
1614
+ DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
1615
+ Hubby,
1616
+ HubbyApiError,
1617
+ SIGNATURE_VERSION,
1618
+ SUPPORTED_API_VERSION,
1619
+ WebhookVerificationError,
1620
+ signRequest,
1621
+ verifyWebhook
1622
+ };