@bisibility/sdk 0.1.0 → 0.1.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.
- package/CHANGELOG.md +2 -17
- package/README.md +91 -10
- package/dist/client.d.ts +47 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +325 -41
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +9 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -4
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pagination.d.ts +5 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +14 -0
- package/dist/pagination.js.map +1 -0
- package/dist/types.d.ts +460 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -10
package/dist/client.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { BisibilityApiError, BisibilityConfigurationError, BisibilityNetworkError, BisibilityResponseError, } from "./errors.js";
|
|
2
|
+
import { iterateCursorPagination } from "./pagination.js";
|
|
3
|
+
import { SDK_VERSION } from "./version.js";
|
|
2
4
|
const DEFAULT_BASE_URL = "https://bisibility.com/api/v1";
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
7
|
+
const CLIENT_ID = `bisibility-sdk-ts/${SDK_VERSION}`;
|
|
3
8
|
const RELATIVE_BASE_ORIGIN = "https://bisibility.local";
|
|
9
|
+
const IDEMPOTENT_METHODS = new Set(["DELETE", "GET", "HEAD", "PUT"]);
|
|
4
10
|
function isAbsoluteUrl(value) {
|
|
5
11
|
return /^[a-z][a-z\d+\-.]*:/i.test(value);
|
|
6
12
|
}
|
|
@@ -69,7 +75,7 @@ function problemFromJson(value) {
|
|
|
69
75
|
return problem;
|
|
70
76
|
}
|
|
71
77
|
function composedSignal(signal, timeout) {
|
|
72
|
-
if (timeout === undefined) {
|
|
78
|
+
if (timeout === null || timeout === undefined) {
|
|
73
79
|
return signal;
|
|
74
80
|
}
|
|
75
81
|
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
@@ -92,6 +98,53 @@ function composedSignal(signal, timeout) {
|
|
|
92
98
|
}
|
|
93
99
|
return controller.signal;
|
|
94
100
|
}
|
|
101
|
+
function validatedMaxRetries(value) {
|
|
102
|
+
const maxRetries = value ?? DEFAULT_MAX_RETRIES;
|
|
103
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0) {
|
|
104
|
+
throw new BisibilityConfigurationError("maxRetries must be a non-negative integer.");
|
|
105
|
+
}
|
|
106
|
+
return maxRetries;
|
|
107
|
+
}
|
|
108
|
+
function validatedTimeout(value) {
|
|
109
|
+
if (value === null || value === undefined) {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
113
|
+
throw new BisibilityConfigurationError("timeout must be a positive finite number or null.");
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function retryBackoffMs(attempt) {
|
|
118
|
+
return Math.min(500 * 2 ** attempt, 8_000);
|
|
119
|
+
}
|
|
120
|
+
function sleep(milliseconds, signal) {
|
|
121
|
+
if (signal?.aborted) {
|
|
122
|
+
return Promise.reject(signal.reason);
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const timer = setTimeout(() => {
|
|
126
|
+
signal?.removeEventListener("abort", onAbort);
|
|
127
|
+
resolve();
|
|
128
|
+
}, milliseconds);
|
|
129
|
+
const onAbort = () => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(signal?.reason);
|
|
132
|
+
};
|
|
133
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function sleepForRetry(milliseconds, signal, method, url) {
|
|
137
|
+
try {
|
|
138
|
+
await sleep(milliseconds, signal);
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
throw new BisibilityNetworkError("Network error while calling the Bisibility API.", {
|
|
142
|
+
cause,
|
|
143
|
+
method,
|
|
144
|
+
url,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
95
148
|
function mergeHeaders(first, second) {
|
|
96
149
|
const headers = new Headers(first);
|
|
97
150
|
if (second) {
|
|
@@ -105,14 +158,18 @@ export class BisibilityClient {
|
|
|
105
158
|
#apiKey;
|
|
106
159
|
#defaultHeaders;
|
|
107
160
|
#fetchImpl;
|
|
161
|
+
#maxRetries;
|
|
108
162
|
#projectId;
|
|
163
|
+
#timeout;
|
|
109
164
|
baseUrl;
|
|
110
165
|
constructor(config = {}) {
|
|
111
166
|
this.#apiKey = config.apiKey;
|
|
112
167
|
this.baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
113
168
|
this.#defaultHeaders = config.headers;
|
|
114
169
|
this.#fetchImpl = config.fetch;
|
|
170
|
+
this.#maxRetries = validatedMaxRetries(config.maxRetries);
|
|
115
171
|
this.#projectId = config.projectId;
|
|
172
|
+
this.#timeout = validatedTimeout(config.timeout);
|
|
116
173
|
}
|
|
117
174
|
getHealth(options) {
|
|
118
175
|
return this.request("GET", "/health", { ...options, auth: false });
|
|
@@ -154,6 +211,16 @@ export class BisibilityClient {
|
|
|
154
211
|
},
|
|
155
212
|
});
|
|
156
213
|
}
|
|
214
|
+
searchLocations(input, options) {
|
|
215
|
+
return this.request("GET", "/locations/search", {
|
|
216
|
+
...options,
|
|
217
|
+
query: {
|
|
218
|
+
country: input.country,
|
|
219
|
+
limit: input.limit,
|
|
220
|
+
q: input.q,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
157
224
|
getMe(options) {
|
|
158
225
|
return this.request("GET", "/me", options);
|
|
159
226
|
}
|
|
@@ -166,11 +233,7 @@ export class BisibilityClient {
|
|
|
166
233
|
createMyToken(input, options) {
|
|
167
234
|
return this.request("POST", "/me/tokens", {
|
|
168
235
|
...options,
|
|
169
|
-
body:
|
|
170
|
-
expires_in_days: input.expiresInDays,
|
|
171
|
-
name: input.name,
|
|
172
|
-
scope: input.scope,
|
|
173
|
-
},
|
|
236
|
+
body: input,
|
|
174
237
|
});
|
|
175
238
|
}
|
|
176
239
|
revokeMyToken(tokenId, options) {
|
|
@@ -182,12 +245,7 @@ export class BisibilityClient {
|
|
|
182
245
|
createProject(input, options) {
|
|
183
246
|
return this.request("POST", "/projects", {
|
|
184
247
|
...options,
|
|
185
|
-
body:
|
|
186
|
-
defaults: input.defaults,
|
|
187
|
-
domain: input.domain,
|
|
188
|
-
name: input.name,
|
|
189
|
-
tracking_scope: input.trackingScope,
|
|
190
|
-
},
|
|
248
|
+
body: input,
|
|
191
249
|
});
|
|
192
250
|
}
|
|
193
251
|
getProject(projectId, options) {
|
|
@@ -215,6 +273,9 @@ export class BisibilityClient {
|
|
|
215
273
|
},
|
|
216
274
|
});
|
|
217
275
|
}
|
|
276
|
+
iterateApiKeys(options = {}, requestOptions) {
|
|
277
|
+
return iterateCursorPagination((pageOptions) => this.listApiKeys(pageOptions, requestOptions), options);
|
|
278
|
+
}
|
|
218
279
|
createApiKey(input, options) {
|
|
219
280
|
return this.request("POST", "/api-keys", { ...options, body: input });
|
|
220
281
|
}
|
|
@@ -231,6 +292,9 @@ export class BisibilityClient {
|
|
|
231
292
|
},
|
|
232
293
|
});
|
|
233
294
|
}
|
|
295
|
+
iterateProjectApiKeys(projectId, options = {}, requestOptions) {
|
|
296
|
+
return iterateCursorPagination((pageOptions) => this.listProjectApiKeys(projectId, pageOptions, requestOptions), options);
|
|
297
|
+
}
|
|
234
298
|
createProjectApiKey(projectId, input, options) {
|
|
235
299
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/api-keys`, { ...options, body: input });
|
|
236
300
|
}
|
|
@@ -244,6 +308,9 @@ export class BisibilityClient {
|
|
|
244
308
|
},
|
|
245
309
|
});
|
|
246
310
|
}
|
|
311
|
+
iterateWebhooks(projectId, options = {}, requestOptions) {
|
|
312
|
+
return iterateCursorPagination((pageOptions) => this.listWebhooks(projectId, pageOptions, requestOptions), options);
|
|
313
|
+
}
|
|
247
314
|
createWebhook(projectId, input, options) {
|
|
248
315
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/webhooks`, {
|
|
249
316
|
...options,
|
|
@@ -275,12 +342,12 @@ export class BisibilityClient {
|
|
|
275
342
|
},
|
|
276
343
|
});
|
|
277
344
|
}
|
|
345
|
+
iterateKeywords(projectId, options = {}, requestOptions) {
|
|
346
|
+
return iterateCursorPagination((pageOptions) => this.listKeywords(projectId, pageOptions, requestOptions), options);
|
|
347
|
+
}
|
|
278
348
|
addKeywords(projectId, input, options) {
|
|
279
349
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/keywords`, { ...options, body: input });
|
|
280
350
|
}
|
|
281
|
-
createKeywords(projectId, input, options) {
|
|
282
|
-
return this.addKeywords(projectId, input, options);
|
|
283
|
-
}
|
|
284
351
|
getKeyword(keywordId, options) {
|
|
285
352
|
return this.request("GET", `/keywords/${encodedPathSegment(keywordId)}`, options);
|
|
286
353
|
}
|
|
@@ -294,11 +361,72 @@ export class BisibilityClient {
|
|
|
294
361
|
return this.updateKeyword(keywordId, { target_url: targetUrl }, options);
|
|
295
362
|
}
|
|
296
363
|
deleteKeyword(keywordId, options) {
|
|
297
|
-
return this.
|
|
364
|
+
return this.requestOrUndefined("DELETE", `/keywords/${encodedPathSegment(keywordId)}`, options);
|
|
298
365
|
}
|
|
299
366
|
bulkUpdateKeywords(input, options) {
|
|
300
367
|
return this.request("POST", "/keywords/bulk", { ...options, body: input });
|
|
301
368
|
}
|
|
369
|
+
listRankedKeywordSuggestions(projectId, options, requestOptions) {
|
|
370
|
+
const filters = options ?? {};
|
|
371
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/ranked-keyword-suggestions`, {
|
|
372
|
+
...requestOptions,
|
|
373
|
+
query: {
|
|
374
|
+
connection_id: filters.connectionId,
|
|
375
|
+
fresh: filters.fresh,
|
|
376
|
+
limit: filters.limit,
|
|
377
|
+
offset: filters.offset,
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Research keywords from one seed. This operation requires API write scope because a cache
|
|
383
|
+
* miss can spend the project's provider budget. Use `estimateOnly` for a free dry run.
|
|
384
|
+
*/
|
|
385
|
+
researchKeywords(projectId, options, requestOptions) {
|
|
386
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/keyword-research`, {
|
|
387
|
+
...requestOptions,
|
|
388
|
+
query: {
|
|
389
|
+
connection_id: options.connectionId,
|
|
390
|
+
estimate_only: options.estimateOnly,
|
|
391
|
+
fresh: options.fresh,
|
|
392
|
+
include_clickstream: options.includeClickstream,
|
|
393
|
+
max_cost_cents: options.maxCostCents,
|
|
394
|
+
mode: options.mode,
|
|
395
|
+
result_limit: options.resultLimit,
|
|
396
|
+
seed: options.seed,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Hydrate keyword metrics. This operation requires API write scope because cache misses can
|
|
402
|
+
* spend the project's provider budget. Use `estimate_only` for a free dry run.
|
|
403
|
+
*/
|
|
404
|
+
getKeywordMetrics(projectId, input, options) {
|
|
405
|
+
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/keyword-metrics`, { ...options, body: input });
|
|
406
|
+
}
|
|
407
|
+
exportRankHistory(projectId, options = {}, requestOptions) {
|
|
408
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/exports/rank-history`, {
|
|
409
|
+
...requestOptions,
|
|
410
|
+
parseAs: options.format === "csv" ? "text" : "json",
|
|
411
|
+
query: {
|
|
412
|
+
cursor: "cursor" in options ? options.cursor : undefined,
|
|
413
|
+
format: options.format,
|
|
414
|
+
granularity: options.granularity,
|
|
415
|
+
keyword_id: options.keywordIds,
|
|
416
|
+
limit: "limit" in options ? options.limit : undefined,
|
|
417
|
+
range: options.range,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
iterateRankHistoryExport(projectId, options = {}, requestOptions) {
|
|
422
|
+
return iterateCursorPagination((pageOptions) => this.exportRankHistory(projectId, { ...options, ...pageOptions, format: "json" }, requestOptions), options);
|
|
423
|
+
}
|
|
424
|
+
listSitemapMonitors(projectId, options) {
|
|
425
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/sitemap-monitors`, options);
|
|
426
|
+
}
|
|
427
|
+
updateSitemapMonitor(projectId, monitorId, input, options) {
|
|
428
|
+
return this.request("PATCH", `/projects/${encodedPathSegment(projectId)}/sitemap-monitors/${encodedPathSegment(monitorId)}`, { ...options, body: input });
|
|
429
|
+
}
|
|
302
430
|
listRankChecks(keywordId, options, requestOptions) {
|
|
303
431
|
const filters = options ?? {};
|
|
304
432
|
return this.request("GET", `/keywords/${encodedPathSegment(keywordId)}/rank-checks`, {
|
|
@@ -312,6 +440,9 @@ export class BisibilityClient {
|
|
|
312
440
|
},
|
|
313
441
|
});
|
|
314
442
|
}
|
|
443
|
+
iterateRankChecks(keywordId, options = {}, requestOptions) {
|
|
444
|
+
return iterateCursorPagination((pageOptions) => this.listRankChecks(keywordId, pageOptions, requestOptions), options);
|
|
445
|
+
}
|
|
315
446
|
runRankCheck(keywordId, input, options) {
|
|
316
447
|
const { async: runAsync, ...requestOptions } = options ?? {};
|
|
317
448
|
const body = input && Object.keys(input).length ? input : undefined;
|
|
@@ -341,6 +472,36 @@ export class BisibilityClient {
|
|
|
341
472
|
},
|
|
342
473
|
});
|
|
343
474
|
}
|
|
475
|
+
iterateSignals(projectId, options = {}, requestOptions) {
|
|
476
|
+
return iterateCursorPagination((pageOptions) => this.listSignals(projectId, pageOptions, requestOptions), options);
|
|
477
|
+
}
|
|
478
|
+
listTrafficSnapshots(projectId, options, requestOptions) {
|
|
479
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/analytics/traffic-snapshots`, {
|
|
480
|
+
...requestOptions,
|
|
481
|
+
query: {
|
|
482
|
+
end_date: options.endDate,
|
|
483
|
+
limit: options.limit,
|
|
484
|
+
offset: options.offset,
|
|
485
|
+
path: options.paths,
|
|
486
|
+
start_date: options.startDate,
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
listSearchPerformanceQueryStats(projectId, options, requestOptions) {
|
|
491
|
+
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/analytics/query-stats`, {
|
|
492
|
+
...requestOptions,
|
|
493
|
+
query: {
|
|
494
|
+
connection_id: options.connectionId,
|
|
495
|
+
end_date: options.endDate,
|
|
496
|
+
limit: options.limit,
|
|
497
|
+
query: options.query,
|
|
498
|
+
start_date: options.startDate,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
syncProjectTraffic(projectId, options) {
|
|
503
|
+
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/analytics/sync`, options);
|
|
504
|
+
}
|
|
344
505
|
listAlertRules(projectId, options, requestOptions) {
|
|
345
506
|
const pagination = options ?? {};
|
|
346
507
|
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/alert-rules`, {
|
|
@@ -351,6 +512,9 @@ export class BisibilityClient {
|
|
|
351
512
|
},
|
|
352
513
|
});
|
|
353
514
|
}
|
|
515
|
+
iterateAlertRules(projectId, options = {}, requestOptions) {
|
|
516
|
+
return iterateCursorPagination((pageOptions) => this.listAlertRules(projectId, pageOptions, requestOptions), options);
|
|
517
|
+
}
|
|
354
518
|
createAlertRule(projectId, input, options) {
|
|
355
519
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/alert-rules`, { ...options, body: input });
|
|
356
520
|
}
|
|
@@ -373,6 +537,15 @@ export class BisibilityClient {
|
|
|
373
537
|
},
|
|
374
538
|
});
|
|
375
539
|
}
|
|
540
|
+
iterateTriggeredAlerts(projectId, options = {}, requestOptions) {
|
|
541
|
+
return iterateCursorPagination((pageOptions) => this.listTriggeredAlerts(projectId, pageOptions, requestOptions), options);
|
|
542
|
+
}
|
|
543
|
+
muteTriggeredAlert(projectId, alertId, options) {
|
|
544
|
+
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/triggered-alerts/${encodedPathSegment(alertId)}/mute`, options);
|
|
545
|
+
}
|
|
546
|
+
markProjectAlertsRead(projectId, options) {
|
|
547
|
+
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/triggered-alerts/mark-read`, options);
|
|
548
|
+
}
|
|
376
549
|
listTeamMembers(projectId, options, requestOptions) {
|
|
377
550
|
const pagination = options ?? {};
|
|
378
551
|
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/team/members`, {
|
|
@@ -383,6 +556,9 @@ export class BisibilityClient {
|
|
|
383
556
|
},
|
|
384
557
|
});
|
|
385
558
|
}
|
|
559
|
+
iterateTeamMembers(projectId, options = {}, requestOptions) {
|
|
560
|
+
return iterateCursorPagination((pageOptions) => this.listTeamMembers(projectId, pageOptions, requestOptions), options);
|
|
561
|
+
}
|
|
386
562
|
listTeamInvites(projectId, options, requestOptions) {
|
|
387
563
|
const pagination = options ?? {};
|
|
388
564
|
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/team/invites`, {
|
|
@@ -393,6 +569,9 @@ export class BisibilityClient {
|
|
|
393
569
|
},
|
|
394
570
|
});
|
|
395
571
|
}
|
|
572
|
+
iterateTeamInvites(projectId, options = {}, requestOptions) {
|
|
573
|
+
return iterateCursorPagination((pageOptions) => this.listTeamInvites(projectId, pageOptions, requestOptions), options);
|
|
574
|
+
}
|
|
396
575
|
createTeamInvite(projectId, input, options) {
|
|
397
576
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/team/invites`, { ...options, body: input });
|
|
398
577
|
}
|
|
@@ -402,6 +581,15 @@ export class BisibilityClient {
|
|
|
402
581
|
revokeTeamInviteById(inviteId, options) {
|
|
403
582
|
return this.request("DELETE", `/team/invites/${encodedPathSegment(inviteId)}`, options);
|
|
404
583
|
}
|
|
584
|
+
resendTeamInvite(projectId, inviteId, options) {
|
|
585
|
+
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/team/invites/${encodedPathSegment(inviteId)}/resend`, options);
|
|
586
|
+
}
|
|
587
|
+
updateTeamMemberRole(projectId, memberId, input, options) {
|
|
588
|
+
return this.request("PATCH", `/projects/${encodedPathSegment(projectId)}/team/members/${encodedPathSegment(memberId)}`, { ...options, body: input });
|
|
589
|
+
}
|
|
590
|
+
removeTeamMember(projectId, memberId, options) {
|
|
591
|
+
return this.request("DELETE", `/projects/${encodedPathSegment(projectId)}/team/members/${encodedPathSegment(memberId)}`, options);
|
|
592
|
+
}
|
|
405
593
|
listProviders(projectId, options, requestOptions) {
|
|
406
594
|
const pagination = options ?? {};
|
|
407
595
|
return this.request("GET", `/projects/${encodedPathSegment(projectId)}/providers`, {
|
|
@@ -412,6 +600,9 @@ export class BisibilityClient {
|
|
|
412
600
|
},
|
|
413
601
|
});
|
|
414
602
|
}
|
|
603
|
+
iterateProviders(projectId, options = {}, requestOptions) {
|
|
604
|
+
return iterateCursorPagination((pageOptions) => this.listProviders(projectId, pageOptions, requestOptions), options);
|
|
605
|
+
}
|
|
415
606
|
connectProvider(projectId, providerId, input = {}, options) {
|
|
416
607
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/providers/${encodedPathSegment(providerId)}/connect`, { ...options, body: bodyOrUndefined(input) });
|
|
417
608
|
}
|
|
@@ -449,6 +640,9 @@ export class BisibilityClient {
|
|
|
449
640
|
},
|
|
450
641
|
});
|
|
451
642
|
}
|
|
643
|
+
iterateSavedViews(projectId, options = {}, requestOptions) {
|
|
644
|
+
return iterateCursorPagination((pageOptions) => this.listSavedViews(projectId, pageOptions, requestOptions), options);
|
|
645
|
+
}
|
|
452
646
|
createSavedView(projectId, input, options) {
|
|
453
647
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/saved-views`, { ...options, body: input });
|
|
454
648
|
}
|
|
@@ -468,6 +662,9 @@ export class BisibilityClient {
|
|
|
468
662
|
},
|
|
469
663
|
});
|
|
470
664
|
}
|
|
665
|
+
iterateCompetitors(projectId, options = {}, requestOptions) {
|
|
666
|
+
return iterateCursorPagination((pageOptions) => this.listCompetitors(projectId, pageOptions, requestOptions), options);
|
|
667
|
+
}
|
|
471
668
|
addCompetitor(projectId, input, options) {
|
|
472
669
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/competitors`, { ...options, body: input });
|
|
473
670
|
}
|
|
@@ -493,6 +690,9 @@ export class BisibilityClient {
|
|
|
493
690
|
},
|
|
494
691
|
});
|
|
495
692
|
}
|
|
693
|
+
iterateMigrationTokens(projectId, options = {}, requestOptions) {
|
|
694
|
+
return iterateCursorPagination((pageOptions) => this.listMigrationTokens(projectId, pageOptions, requestOptions), options);
|
|
695
|
+
}
|
|
496
696
|
mintMigrationToken(projectId, input = {}, options) {
|
|
497
697
|
return this.request("POST", `/projects/${encodedPathSegment(projectId)}/migration-tokens`, { ...options, body: bodyOrUndefined(input) });
|
|
498
698
|
}
|
|
@@ -502,21 +702,69 @@ export class BisibilityClient {
|
|
|
502
702
|
revokeMigrationTokenById(tokenId, options) {
|
|
503
703
|
return this.request("DELETE", `/migration-tokens/${encodedPathSegment(tokenId)}`, options);
|
|
504
704
|
}
|
|
705
|
+
getCloudImportCompatibility(options) {
|
|
706
|
+
return this.request("GET", "/cloud/import/compatibility", {
|
|
707
|
+
...options,
|
|
708
|
+
auth: false,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
importCloudExport(input, options) {
|
|
712
|
+
return this.request("POST", "/cloud/import", {
|
|
713
|
+
...options,
|
|
714
|
+
body: input,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
createCloudImportSession(input, options) {
|
|
718
|
+
return this.request("POST", "/cloud/import/sessions", {
|
|
719
|
+
...options,
|
|
720
|
+
body: input,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
uploadCloudImportChunk(sessionId, index, input, options) {
|
|
724
|
+
const { contentEncoding, headers, ...requestOptions } = options ?? {};
|
|
725
|
+
const mergedHeaders = new Headers(headers);
|
|
726
|
+
if (contentEncoding !== undefined) {
|
|
727
|
+
mergedHeaders.set("Content-Encoding", contentEncoding);
|
|
728
|
+
}
|
|
729
|
+
return this.request("PUT", `/cloud/import/sessions/${encodedPathSegment(sessionId)}/chunks/${encodedPathSegment(String(index))}`, { ...requestOptions, body: input, headers: mergedHeaders });
|
|
730
|
+
}
|
|
731
|
+
finalizeCloudImportSession(sessionId, options) {
|
|
732
|
+
return this.request("POST", `/cloud/import/sessions/${encodedPathSegment(sessionId)}/finalize`, options);
|
|
733
|
+
}
|
|
505
734
|
buildUrl(path, query) {
|
|
506
735
|
const absolute = isAbsoluteUrl(this.baseUrl);
|
|
507
736
|
const base = `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
508
737
|
const url = new URL(base, absolute ? undefined : RELATIVE_BASE_ORIGIN);
|
|
509
738
|
if (query) {
|
|
510
739
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
740
|
+
const rawValues = Array.isArray(rawValue)
|
|
741
|
+
? rawValue
|
|
742
|
+
: [rawValue];
|
|
743
|
+
for (const rawItem of rawValues) {
|
|
744
|
+
const value = queryValue(rawItem);
|
|
745
|
+
if (value !== undefined) {
|
|
746
|
+
url.searchParams.append(key, value);
|
|
747
|
+
}
|
|
514
748
|
}
|
|
515
749
|
}
|
|
516
750
|
}
|
|
517
751
|
return absolute ? url.toString() : `${url.pathname}${url.search}`;
|
|
518
752
|
}
|
|
519
753
|
async request(method, path, options = {}) {
|
|
754
|
+
const statusRef = { status: 0 };
|
|
755
|
+
const result = await this.requestOrUndefined(method, path, options, statusRef);
|
|
756
|
+
if (result === undefined) {
|
|
757
|
+
throw new BisibilityResponseError("Bisibility API returned an empty response body.", {
|
|
758
|
+
body: "",
|
|
759
|
+
cause: undefined,
|
|
760
|
+
method,
|
|
761
|
+
status: statusRef.status,
|
|
762
|
+
url: this.buildUrl(path, options.query),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
async requestOrUndefined(method, path, options = {}, statusRef) {
|
|
520
768
|
const fetchImpl = this.#fetchImpl ?? globalThis.fetch;
|
|
521
769
|
const url = this.buildUrl(path, options.query);
|
|
522
770
|
if (!fetchImpl) {
|
|
@@ -535,35 +783,71 @@ export class BisibilityClient {
|
|
|
535
783
|
if (options.idempotencyKey) {
|
|
536
784
|
headers.set("Idempotency-Key", options.idempotencyKey);
|
|
537
785
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
786
|
+
headers.set("X-Bisibility-Client", CLIENT_ID);
|
|
787
|
+
if (!headers.has("User-Agent")) {
|
|
788
|
+
try {
|
|
789
|
+
headers.set("User-Agent", CLIENT_ID);
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
// Browsers may forbid setting User-Agent. X-Bisibility-Client remains authoritative.
|
|
793
|
+
}
|
|
542
794
|
}
|
|
795
|
+
const baseInit = { headers, method, redirect: "error" };
|
|
543
796
|
if (options.body !== undefined) {
|
|
544
797
|
if (!headers.has("Content-Type")) {
|
|
545
798
|
headers.set("Content-Type", "application/json");
|
|
546
799
|
}
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
let response;
|
|
550
|
-
try {
|
|
551
|
-
response = await fetchImpl(url, init);
|
|
552
|
-
}
|
|
553
|
-
catch (error) {
|
|
554
|
-
throw new BisibilityNetworkError("Network error while calling the Bisibility API.", {
|
|
555
|
-
cause: error,
|
|
556
|
-
method,
|
|
557
|
-
url,
|
|
558
|
-
});
|
|
800
|
+
baseInit.body = JSON.stringify(options.body);
|
|
559
801
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
802
|
+
const timeout = options.timeout !== undefined
|
|
803
|
+
? validatedTimeout(options.timeout)
|
|
804
|
+
: this.#timeout !== undefined
|
|
805
|
+
? this.#timeout
|
|
806
|
+
: options.signal
|
|
807
|
+
? null
|
|
808
|
+
: DEFAULT_TIMEOUT;
|
|
809
|
+
const retryable = IDEMPOTENT_METHODS.has(method.toUpperCase()) || headers.has("Idempotency-Key");
|
|
810
|
+
for (let attempt = 0;; attempt += 1) {
|
|
811
|
+
const init = { ...baseInit };
|
|
812
|
+
const signal = composedSignal(options.signal, timeout);
|
|
813
|
+
if (signal) {
|
|
814
|
+
init.signal = signal;
|
|
815
|
+
}
|
|
816
|
+
let response;
|
|
817
|
+
try {
|
|
818
|
+
response = await fetchImpl(url, init);
|
|
819
|
+
}
|
|
820
|
+
catch (cause) {
|
|
821
|
+
if (!retryable || attempt >= this.#maxRetries || options.signal?.aborted) {
|
|
822
|
+
throw new BisibilityNetworkError("Network error while calling the Bisibility API.", {
|
|
823
|
+
cause,
|
|
824
|
+
method,
|
|
825
|
+
url,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
await sleepForRetry(retryBackoffMs(attempt), options.signal, method, url);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (!response.ok) {
|
|
832
|
+
const error = await this.errorFromResponse(response, method, url);
|
|
833
|
+
if (retryable &&
|
|
834
|
+
attempt < this.#maxRetries &&
|
|
835
|
+
(error.status === 429 || error.status === 503)) {
|
|
836
|
+
await sleepForRetry(error.retryAfterSeconds === null
|
|
837
|
+
? retryBackoffMs(attempt)
|
|
838
|
+
: error.retryAfterSeconds * 1_000, options.signal, method, url);
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
if (statusRef) {
|
|
844
|
+
statusRef.status = response.status;
|
|
845
|
+
}
|
|
846
|
+
if (options.parseAs === "text") {
|
|
847
|
+
return response.text();
|
|
848
|
+
}
|
|
849
|
+
return this.jsonFromResponse(response, method, url);
|
|
565
850
|
}
|
|
566
|
-
return this.jsonFromResponse(response, method, url);
|
|
567
851
|
}
|
|
568
852
|
async jsonFromResponse(response, method, url) {
|
|
569
853
|
const body = await response.text();
|