@bisibility/sdk 0.3.1 → 0.4.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/CHANGELOG.md +9 -0
- package/README.md +19 -3
- package/dist/client.d.ts +93 -93
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -3
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/public-id-contract.d.ts +12 -0
- package/dist/public-id-contract.d.ts.map +1 -0
- package/dist/public-id-contract.js +693 -0
- package/dist/public-id-contract.js.map +1 -0
- package/dist/public-id.d.ts +40 -0
- package/dist/public-id.d.ts.map +1 -0
- package/dist/public-id.js +47 -0
- package/dist/public-id.js.map +1 -0
- package/dist/types.d.ts +143 -147
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { isPublicIdOfType, publicIdExpectation } from "./public-id.js";
|
|
2
|
+
function object(value) {
|
|
3
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
function segments(path) {
|
|
8
|
+
return (path.split("?", 1)[0] ?? "")
|
|
9
|
+
.split("/")
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.map((segment) => decodeURIComponent(segment));
|
|
12
|
+
}
|
|
13
|
+
function assertId(value, prefix, label) {
|
|
14
|
+
if (!isPublicIdOfType(value, prefix)) {
|
|
15
|
+
throw new TypeError(`${label} must match ${publicIdExpectation(prefix)}.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertOptionalId(value, prefix, label) {
|
|
19
|
+
if (value !== undefined && value !== null) {
|
|
20
|
+
assertId(value, prefix, label);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function assertIdArray(value, prefix, label) {
|
|
24
|
+
if (!Array.isArray(value)) {
|
|
25
|
+
throw new TypeError(`${label} must be an array of ${publicIdExpectation(prefix)} values.`);
|
|
26
|
+
}
|
|
27
|
+
for (const [index, item] of value.entries()) {
|
|
28
|
+
assertId(item, prefix, `${label}[${index}]`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertAlertRuleInput(input) {
|
|
32
|
+
const body = object(input);
|
|
33
|
+
if (!body)
|
|
34
|
+
return;
|
|
35
|
+
if (body.recipient_ids !== undefined) {
|
|
36
|
+
assertIdArray(body.recipient_ids, "usr", "recipient_ids");
|
|
37
|
+
}
|
|
38
|
+
if (body.target_ids === undefined)
|
|
39
|
+
return;
|
|
40
|
+
const targetType = body.target_type;
|
|
41
|
+
if (targetType === "keyword") {
|
|
42
|
+
assertIdArray(body.target_ids, "kw", "target_ids");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (targetType === "tag") {
|
|
46
|
+
assertIdArray(body.target_ids, "tag", "target_ids");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (targetType === "all") {
|
|
50
|
+
assertIdArray(body.target_ids, "kw", "target_ids");
|
|
51
|
+
if (Array.isArray(body.target_ids) && body.target_ids.length > 0) {
|
|
52
|
+
throw new TypeError("target_ids must be empty when target_type is all.");
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
throw new TypeError("target_ids require target_type 'all', 'keyword', or 'tag'.");
|
|
57
|
+
}
|
|
58
|
+
function assertAlertRuleResponse(value, label) {
|
|
59
|
+
const rule = object(value);
|
|
60
|
+
if (!rule)
|
|
61
|
+
throw new TypeError(`${label} must be an object.`);
|
|
62
|
+
assertId(rule.id, "rule", `${label}.id`);
|
|
63
|
+
assertIdArray(rule.recipient_ids, "usr", `${label}.recipient_ids`);
|
|
64
|
+
if (rule.target_type === "keyword") {
|
|
65
|
+
assertIdArray(rule.target_ids, "kw", `${label}.target_ids`);
|
|
66
|
+
}
|
|
67
|
+
else if (rule.target_type === "tag") {
|
|
68
|
+
assertIdArray(rule.target_ids, "tag", `${label}.target_ids`);
|
|
69
|
+
}
|
|
70
|
+
else if (rule.target_type === "all") {
|
|
71
|
+
assertIdArray(rule.target_ids, "kw", `${label}.target_ids`);
|
|
72
|
+
if (rule.target_ids.length > 0) {
|
|
73
|
+
throw new TypeError(`${label}.target_ids must be empty when target_type is all.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new TypeError(`${label}.target_type must be all, keyword, or tag.`);
|
|
78
|
+
}
|
|
79
|
+
for (const staleField of ["created_by_id", "project_id", "targets"]) {
|
|
80
|
+
if (staleField in rule) {
|
|
81
|
+
throw new TypeError(`${label}.${staleField} is not part of the public alert rule response.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const cloudImportLocations = [
|
|
86
|
+
"United States",
|
|
87
|
+
"United Kingdom",
|
|
88
|
+
"Canada",
|
|
89
|
+
"Australia",
|
|
90
|
+
"Germany",
|
|
91
|
+
"France",
|
|
92
|
+
"Spain",
|
|
93
|
+
"Italy",
|
|
94
|
+
"Netherlands",
|
|
95
|
+
"Sweden",
|
|
96
|
+
"Poland",
|
|
97
|
+
"Ireland",
|
|
98
|
+
"Portugal",
|
|
99
|
+
"Belgium",
|
|
100
|
+
"Switzerland",
|
|
101
|
+
"Austria",
|
|
102
|
+
"Denmark",
|
|
103
|
+
"Norway",
|
|
104
|
+
"Finland",
|
|
105
|
+
"Brazil",
|
|
106
|
+
"Mexico",
|
|
107
|
+
"India",
|
|
108
|
+
"Japan",
|
|
109
|
+
"Singapore",
|
|
110
|
+
"New Zealand",
|
|
111
|
+
"South Africa",
|
|
112
|
+
"United Arab Emirates",
|
|
113
|
+
];
|
|
114
|
+
function assertSchemaObject(value, label, properties) {
|
|
115
|
+
const input = object(value);
|
|
116
|
+
if (!input)
|
|
117
|
+
throw new TypeError(`${label} must be an object.`);
|
|
118
|
+
for (const key of Object.keys(input)) {
|
|
119
|
+
if (!properties.includes(key)) {
|
|
120
|
+
throw new TypeError(`${label}.${key} is not part of the v4 cloud import schema.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return input;
|
|
124
|
+
}
|
|
125
|
+
function required(input, key, label) {
|
|
126
|
+
if (!Object.hasOwn(input, key))
|
|
127
|
+
throw new TypeError(`${label}.${key} is required.`);
|
|
128
|
+
return input[key];
|
|
129
|
+
}
|
|
130
|
+
function assertString(value, label, minimum = 0, maximum) {
|
|
131
|
+
if (typeof value !== "string" ||
|
|
132
|
+
value.length < minimum ||
|
|
133
|
+
(maximum !== undefined && value.length > maximum)) {
|
|
134
|
+
throw new TypeError(`${label} must be a string${minimum ? ` with at least ${minimum} characters` : ""}.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function assertNullableString(value, label, minimum = 0, maximum) {
|
|
138
|
+
if (value !== null)
|
|
139
|
+
assertString(value, label, minimum, maximum);
|
|
140
|
+
}
|
|
141
|
+
function assertInteger(value, label, minimum, maximum) {
|
|
142
|
+
if (typeof value !== "number" ||
|
|
143
|
+
!Number.isInteger(value) ||
|
|
144
|
+
(minimum !== undefined && value < minimum) ||
|
|
145
|
+
(maximum !== undefined && value > maximum)) {
|
|
146
|
+
throw new TypeError(`${label} must be an integer.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function assertNullablePositiveInteger(value, label) {
|
|
150
|
+
if (value !== null)
|
|
151
|
+
assertInteger(value, label, 1);
|
|
152
|
+
}
|
|
153
|
+
function assertNumber(value, label) {
|
|
154
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
155
|
+
throw new TypeError(`${label} must be a number.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function assertNullableNumber(value, label) {
|
|
159
|
+
if (value !== null)
|
|
160
|
+
assertNumber(value, label);
|
|
161
|
+
}
|
|
162
|
+
function assertBoolean(value, label) {
|
|
163
|
+
if (typeof value !== "boolean")
|
|
164
|
+
throw new TypeError(`${label} must be a boolean.`);
|
|
165
|
+
}
|
|
166
|
+
function assertEnum(value, values, label) {
|
|
167
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
168
|
+
throw new TypeError(`${label} must be one of ${values.join(", ")}.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function assertArray(value, label, maximum) {
|
|
172
|
+
if (!Array.isArray(value) || (maximum !== undefined && value.length > maximum)) {
|
|
173
|
+
throw new TypeError(`${label} must be an array.`);
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
function has(input, key) {
|
|
178
|
+
return Object.hasOwn(input, key);
|
|
179
|
+
}
|
|
180
|
+
function assertCloudImportDevice(value, label) {
|
|
181
|
+
assertEnum(value, ["desktop", "mobile"], label);
|
|
182
|
+
}
|
|
183
|
+
function assertCloudImportLocation(value, label) {
|
|
184
|
+
assertEnum(value, cloudImportLocations, label);
|
|
185
|
+
}
|
|
186
|
+
function assertCloudImportDateTime(value, label) {
|
|
187
|
+
assertString(value, label);
|
|
188
|
+
if (Number.isNaN(Date.parse(value)))
|
|
189
|
+
throw new TypeError(`${label} must be a date-time string.`);
|
|
190
|
+
}
|
|
191
|
+
function assertCloudImportRankingHistory(value, label) {
|
|
192
|
+
const history = assertSchemaObject(value, label, [
|
|
193
|
+
"checkedAt",
|
|
194
|
+
"position",
|
|
195
|
+
"previousPosition",
|
|
196
|
+
"rankingUrl",
|
|
197
|
+
]);
|
|
198
|
+
assertCloudImportDateTime(required(history, "checkedAt", label), `${label}.checkedAt`);
|
|
199
|
+
if (has(history, "position"))
|
|
200
|
+
assertNullablePositiveInteger(history.position, `${label}.position`);
|
|
201
|
+
if (has(history, "previousPosition")) {
|
|
202
|
+
assertNullablePositiveInteger(history.previousPosition, `${label}.previousPosition`);
|
|
203
|
+
}
|
|
204
|
+
if (has(history, "rankingUrl"))
|
|
205
|
+
assertNullableString(history.rankingUrl, `${label}.rankingUrl`, 0, 500);
|
|
206
|
+
}
|
|
207
|
+
function assertCloudImportKeyword(value, label) {
|
|
208
|
+
const keyword = assertSchemaObject(value, label, [
|
|
209
|
+
"device",
|
|
210
|
+
"id",
|
|
211
|
+
"keyword",
|
|
212
|
+
"location",
|
|
213
|
+
"rankingHistory",
|
|
214
|
+
"tags",
|
|
215
|
+
"target_url",
|
|
216
|
+
]);
|
|
217
|
+
assertId(required(keyword, "id", label), "kw", `${label}.id`);
|
|
218
|
+
assertString(required(keyword, "keyword", label), `${label}.keyword`, 1, 180);
|
|
219
|
+
assertCloudImportDevice(required(keyword, "device", label), `${label}.device`);
|
|
220
|
+
assertCloudImportLocation(required(keyword, "location", label), `${label}.location`);
|
|
221
|
+
if (has(keyword, "rankingHistory")) {
|
|
222
|
+
for (const [index, entry] of assertArray(keyword.rankingHistory, `${label}.rankingHistory`, 5000).entries()) {
|
|
223
|
+
assertCloudImportRankingHistory(entry, `${label}.rankingHistory[${index}]`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (has(keyword, "tags")) {
|
|
227
|
+
for (const [index, tag] of assertArray(keyword.tags, `${label}.tags`, 12).entries()) {
|
|
228
|
+
assertString(tag, `${label}.tags[${index}]`, 1, 48);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (has(keyword, "target_url")) {
|
|
232
|
+
assertNullableString(keyword.target_url, `${label}.target_url`, 0, 500);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function assertCloudImportCompetitor(value, label) {
|
|
236
|
+
const competitor = assertSchemaObject(value, label, ["domain", "id", "label"]);
|
|
237
|
+
assertId(required(competitor, "id", label), "comp", `${label}.id`);
|
|
238
|
+
assertString(required(competitor, "domain", label), `${label}.domain`, 1, 253);
|
|
239
|
+
if (has(competitor, "label"))
|
|
240
|
+
assertNullableString(competitor.label, `${label}.label`, 0, 80);
|
|
241
|
+
}
|
|
242
|
+
function assertCloudImportSavedView(value, label) {
|
|
243
|
+
const view = assertSchemaObject(value, label, ["config", "id", "name", "surface"]);
|
|
244
|
+
assertId(required(view, "id", label), "view", `${label}.id`);
|
|
245
|
+
assertString(required(view, "name", label), `${label}.name`, 1, 120);
|
|
246
|
+
if (has(view, "surface"))
|
|
247
|
+
assertEnum(view.surface, ["keywords", "competitors"], `${label}.surface`);
|
|
248
|
+
}
|
|
249
|
+
function assertCloudImportAlertRuleTarget(value, label) {
|
|
250
|
+
const target = object(value);
|
|
251
|
+
if (!target)
|
|
252
|
+
throw new TypeError(`${label} must be an object.`);
|
|
253
|
+
const type = required(target, "type", label);
|
|
254
|
+
if (type === "keyword") {
|
|
255
|
+
const keywordTarget = assertSchemaObject(value, label, [
|
|
256
|
+
"device",
|
|
257
|
+
"keyword",
|
|
258
|
+
"keyword_id",
|
|
259
|
+
"location",
|
|
260
|
+
"type",
|
|
261
|
+
]);
|
|
262
|
+
assertId(required(keywordTarget, "keyword_id", label), "kw", `${label}.keyword_id`);
|
|
263
|
+
if (has(keywordTarget, "device"))
|
|
264
|
+
assertCloudImportDevice(keywordTarget.device, `${label}.device`);
|
|
265
|
+
if (has(keywordTarget, "keyword"))
|
|
266
|
+
assertString(keywordTarget.keyword, `${label}.keyword`, 1, 180);
|
|
267
|
+
if (has(keywordTarget, "location"))
|
|
268
|
+
assertCloudImportLocation(keywordTarget.location, `${label}.location`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (type === "tag") {
|
|
272
|
+
const tagTarget = assertSchemaObject(value, label, ["tag", "type"]);
|
|
273
|
+
assertString(required(tagTarget, "tag", label), `${label}.tag`, 1, 80);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
throw new TypeError(`${label}.type must be keyword or tag.`);
|
|
277
|
+
}
|
|
278
|
+
function assertCloudImportAlertRule(value, label) {
|
|
279
|
+
const rule = assertSchemaObject(value, label, [
|
|
280
|
+
"change_pct",
|
|
281
|
+
"channels",
|
|
282
|
+
"competitor_domain",
|
|
283
|
+
"condition_type",
|
|
284
|
+
"drop_positions",
|
|
285
|
+
"enabled",
|
|
286
|
+
"id",
|
|
287
|
+
"name",
|
|
288
|
+
"serp_feature",
|
|
289
|
+
"target_type",
|
|
290
|
+
"targets",
|
|
291
|
+
"threshold_position",
|
|
292
|
+
"top_n",
|
|
293
|
+
]);
|
|
294
|
+
assertId(required(rule, "id", label), "rule", `${label}.id`);
|
|
295
|
+
assertString(required(rule, "name", label), `${label}.name`, 1, 120);
|
|
296
|
+
if (has(rule, "change_pct"))
|
|
297
|
+
assertNullableNumber(rule.change_pct, `${label}.change_pct`);
|
|
298
|
+
if (has(rule, "channels")) {
|
|
299
|
+
for (const [index, channel] of assertArray(rule.channels, `${label}.channels`).entries()) {
|
|
300
|
+
assertEnum(channel, ["email", "slack", "webhook"], `${label}.channels[${index}]`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (has(rule, "competitor_domain")) {
|
|
304
|
+
assertNullableString(rule.competitor_domain, `${label}.competitor_domain`);
|
|
305
|
+
}
|
|
306
|
+
if (has(rule, "condition_type")) {
|
|
307
|
+
assertEnum(rule.condition_type, [
|
|
308
|
+
"change_pct",
|
|
309
|
+
"competitor_overtake",
|
|
310
|
+
"ctr_drop",
|
|
311
|
+
"downtrend",
|
|
312
|
+
"enters_top_n",
|
|
313
|
+
"exits_top_n",
|
|
314
|
+
"position_drop",
|
|
315
|
+
"serp_feature",
|
|
316
|
+
"threshold",
|
|
317
|
+
"url_mismatch",
|
|
318
|
+
], `${label}.condition_type`);
|
|
319
|
+
}
|
|
320
|
+
if (has(rule, "drop_positions")) {
|
|
321
|
+
assertNullablePositiveInteger(rule.drop_positions, `${label}.drop_positions`);
|
|
322
|
+
}
|
|
323
|
+
if (has(rule, "enabled"))
|
|
324
|
+
assertBoolean(rule.enabled, `${label}.enabled`);
|
|
325
|
+
if (has(rule, "serp_feature"))
|
|
326
|
+
assertNullableString(rule.serp_feature, `${label}.serp_feature`);
|
|
327
|
+
if (has(rule, "target_type")) {
|
|
328
|
+
assertEnum(rule.target_type, ["all", "keyword", "tag"], `${label}.target_type`);
|
|
329
|
+
}
|
|
330
|
+
if (has(rule, "targets")) {
|
|
331
|
+
for (const [index, target] of assertArray(rule.targets, `${label}.targets`, 1000).entries()) {
|
|
332
|
+
assertCloudImportAlertRuleTarget(target, `${label}.targets[${index}]`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (has(rule, "threshold_position")) {
|
|
336
|
+
assertNullablePositiveInteger(rule.threshold_position, `${label}.threshold_position`);
|
|
337
|
+
}
|
|
338
|
+
if (has(rule, "top_n"))
|
|
339
|
+
assertNullablePositiveInteger(rule.top_n, `${label}.top_n`);
|
|
340
|
+
}
|
|
341
|
+
function assertCloudImportNotificationPreference(value, label) {
|
|
342
|
+
const preference = assertSchemaObject(value, label, [
|
|
343
|
+
"alert_email",
|
|
344
|
+
"alert_in_app",
|
|
345
|
+
"check_email",
|
|
346
|
+
"check_in_app",
|
|
347
|
+
"import_email",
|
|
348
|
+
"import_in_app",
|
|
349
|
+
"invite_email",
|
|
350
|
+
"invite_in_app",
|
|
351
|
+
"report_email",
|
|
352
|
+
]);
|
|
353
|
+
for (const key of Object.keys(preference))
|
|
354
|
+
assertBoolean(preference[key], `${label}.${key}`);
|
|
355
|
+
}
|
|
356
|
+
function assertCloudImportPackage(input) {
|
|
357
|
+
const payload = assertSchemaObject(input, "Cloud import payload", [
|
|
358
|
+
"alert_rules",
|
|
359
|
+
"competitors",
|
|
360
|
+
"exported_at",
|
|
361
|
+
"keywords",
|
|
362
|
+
"notification_preferences",
|
|
363
|
+
"project_id",
|
|
364
|
+
"saved_views",
|
|
365
|
+
"scope",
|
|
366
|
+
"version",
|
|
367
|
+
]);
|
|
368
|
+
if (required(payload, "version", "Cloud import payload") !== 4) {
|
|
369
|
+
throw new TypeError("Cloud import payload version must be 4.");
|
|
370
|
+
}
|
|
371
|
+
assertId(required(payload, "project_id", "Cloud import payload"), "prj", "project_id");
|
|
372
|
+
for (const [index, keyword] of assertArray(required(payload, "keywords", "Cloud import payload"), "keywords", 500).entries()) {
|
|
373
|
+
assertCloudImportKeyword(keyword, `keywords[${index}]`);
|
|
374
|
+
}
|
|
375
|
+
for (const [index, rule] of assertArray(required(payload, "alert_rules", "Cloud import payload"), "alert_rules", 500).entries()) {
|
|
376
|
+
assertCloudImportAlertRule(rule, `alert_rules[${index}]`);
|
|
377
|
+
}
|
|
378
|
+
for (const [index, competitor] of assertArray(required(payload, "competitors", "Cloud import payload"), "competitors", 500).entries()) {
|
|
379
|
+
assertCloudImportCompetitor(competitor, `competitors[${index}]`);
|
|
380
|
+
}
|
|
381
|
+
for (const [index, preference] of assertArray(required(payload, "notification_preferences", "Cloud import payload"), "notification_preferences", 50).entries()) {
|
|
382
|
+
assertCloudImportNotificationPreference(preference, `notification_preferences[${index}]`);
|
|
383
|
+
}
|
|
384
|
+
for (const [index, view] of assertArray(required(payload, "saved_views", "Cloud import payload"), "saved_views", 500).entries()) {
|
|
385
|
+
assertCloudImportSavedView(view, `saved_views[${index}]`);
|
|
386
|
+
}
|
|
387
|
+
if (has(payload, "exported_at"))
|
|
388
|
+
assertCloudImportDateTime(payload.exported_at, "exported_at");
|
|
389
|
+
if (has(payload, "scope"))
|
|
390
|
+
assertEnum(payload.scope, ["current", "history"], "scope");
|
|
391
|
+
}
|
|
392
|
+
function assertCloudImportSessionCreate(input) {
|
|
393
|
+
const session = assertSchemaObject(input, "Cloud import session", [
|
|
394
|
+
"chunk_count",
|
|
395
|
+
"source_project_id",
|
|
396
|
+
"totals",
|
|
397
|
+
"version",
|
|
398
|
+
]);
|
|
399
|
+
if (required(session, "version", "Cloud import session") !== 4) {
|
|
400
|
+
throw new TypeError("Cloud import session version must be 4.");
|
|
401
|
+
}
|
|
402
|
+
assertInteger(required(session, "chunk_count", "Cloud import session"), "chunk_count", 1, 500);
|
|
403
|
+
assertId(required(session, "source_project_id", "Cloud import session"), "prj", "source_project_id");
|
|
404
|
+
if (has(session, "totals")) {
|
|
405
|
+
const totals = assertSchemaObject(session.totals, "totals", ["keywords", "rank_checks"]);
|
|
406
|
+
if (has(totals, "keywords"))
|
|
407
|
+
assertInteger(totals.keywords, "totals.keywords", 0);
|
|
408
|
+
if (has(totals, "rank_checks"))
|
|
409
|
+
assertInteger(totals.rank_checks, "totals.rank_checks", 0);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function assertCloudImportSourceKeyword(value, label) {
|
|
413
|
+
const keyword = assertSchemaObject(value, label, ["device", "location", "text"]);
|
|
414
|
+
assertCloudImportDevice(required(keyword, "device", label), `${label}.device`);
|
|
415
|
+
assertCloudImportLocation(required(keyword, "location", label), `${label}.location`);
|
|
416
|
+
assertString(required(keyword, "text", label), `${label}.text`);
|
|
417
|
+
}
|
|
418
|
+
function assertCloudImportSessionSections(value, label) {
|
|
419
|
+
const sections = assertSchemaObject(value, label, [
|
|
420
|
+
"alert_rules",
|
|
421
|
+
"competitors",
|
|
422
|
+
"notification_preferences",
|
|
423
|
+
"saved_views",
|
|
424
|
+
"source_keyword_ids",
|
|
425
|
+
]);
|
|
426
|
+
if (has(sections, "alert_rules")) {
|
|
427
|
+
for (const [index, rule] of assertArray(sections.alert_rules, `${label}.alert_rules`, 500).entries()) {
|
|
428
|
+
assertCloudImportAlertRule(rule, `${label}.alert_rules[${index}]`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (has(sections, "competitors")) {
|
|
432
|
+
for (const [index, competitor] of assertArray(sections.competitors, `${label}.competitors`, 500).entries()) {
|
|
433
|
+
assertCloudImportCompetitor(competitor, `${label}.competitors[${index}]`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (has(sections, "notification_preferences")) {
|
|
437
|
+
for (const [index, preference] of assertArray(sections.notification_preferences, `${label}.notification_preferences`, 50).entries()) {
|
|
438
|
+
assertCloudImportNotificationPreference(preference, `${label}.notification_preferences[${index}]`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (has(sections, "saved_views")) {
|
|
442
|
+
for (const [index, view] of assertArray(sections.saved_views, `${label}.saved_views`, 500).entries()) {
|
|
443
|
+
assertCloudImportSavedView(view, `${label}.saved_views[${index}]`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (has(sections, "source_keyword_ids")) {
|
|
447
|
+
const sourceKeywords = object(sections.source_keyword_ids);
|
|
448
|
+
if (!sourceKeywords)
|
|
449
|
+
throw new TypeError(`${label}.source_keyword_ids must be an object.`);
|
|
450
|
+
for (const [key, keyword] of Object.entries(sourceKeywords)) {
|
|
451
|
+
assertId(key, "kw", `${label}.source_keyword_ids.${key}`);
|
|
452
|
+
assertCloudImportSourceKeyword(keyword, `${label}.source_keyword_ids.${key}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function assertCloudImportChunk(input) {
|
|
457
|
+
const chunk = object(input);
|
|
458
|
+
if (!chunk)
|
|
459
|
+
throw new TypeError("Cloud import chunk must be an object.");
|
|
460
|
+
const kind = required(chunk, "kind", "Cloud import chunk");
|
|
461
|
+
if (kind === "keywords") {
|
|
462
|
+
const keywordChunk = assertSchemaObject(input, "Cloud import chunk", [
|
|
463
|
+
"checksum",
|
|
464
|
+
"kind",
|
|
465
|
+
"keywords",
|
|
466
|
+
]);
|
|
467
|
+
assertString(required(keywordChunk, "checksum", "Cloud import chunk"), "checksum");
|
|
468
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(keywordChunk.checksum)) {
|
|
469
|
+
throw new TypeError("checksum must match sha256:[0-9a-f]{64}.");
|
|
470
|
+
}
|
|
471
|
+
for (const [index, keyword] of assertArray(required(keywordChunk, "keywords", "Cloud import chunk"), "keywords", 500).entries()) {
|
|
472
|
+
assertCloudImportKeyword(keyword, `keywords[${index}]`);
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (kind === "sections") {
|
|
477
|
+
const sectionChunk = assertSchemaObject(input, "Cloud import chunk", [
|
|
478
|
+
"checksum",
|
|
479
|
+
"kind",
|
|
480
|
+
"sections",
|
|
481
|
+
]);
|
|
482
|
+
assertString(required(sectionChunk, "checksum", "Cloud import chunk"), "checksum");
|
|
483
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(sectionChunk.checksum)) {
|
|
484
|
+
throw new TypeError("checksum must match sha256:[0-9a-f]{64}.");
|
|
485
|
+
}
|
|
486
|
+
assertCloudImportSessionSections(required(sectionChunk, "sections", "Cloud import chunk"), "sections");
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
throw new TypeError("Cloud import chunk kind must be keywords or sections.");
|
|
490
|
+
}
|
|
491
|
+
function assertCloudImportCompatibilityResponse(value) {
|
|
492
|
+
const response = object(value);
|
|
493
|
+
if (!response)
|
|
494
|
+
throw new TypeError("Cloud import compatibility response must be an object.");
|
|
495
|
+
const versions = assertArray(required(response, "schema_versions_supported", "Cloud import compatibility response"), "schema_versions_supported");
|
|
496
|
+
for (const [index, version] of versions.entries()) {
|
|
497
|
+
if (version !== 4)
|
|
498
|
+
throw new TypeError(`schema_versions_supported[${index}] must be 4.`);
|
|
499
|
+
}
|
|
500
|
+
assertString(required(response, "app_version", "Cloud import compatibility response"), "app_version");
|
|
501
|
+
assertNullableString(required(response, "latest_migration", "Cloud import compatibility response"), "latest_migration");
|
|
502
|
+
}
|
|
503
|
+
function assertCloudImportCounts(value, label) {
|
|
504
|
+
const counts = object(value);
|
|
505
|
+
if (!counts)
|
|
506
|
+
throw new TypeError(`${label} must be an object.`);
|
|
507
|
+
for (const [key, count] of Object.entries(counts))
|
|
508
|
+
assertInteger(count, `${label}.${key}`);
|
|
509
|
+
}
|
|
510
|
+
function assertCloudImportFinalizeResponse(value) {
|
|
511
|
+
const response = object(value);
|
|
512
|
+
if (!response)
|
|
513
|
+
throw new TypeError("Cloud import finalize response must be an object.");
|
|
514
|
+
assertCloudImportCounts(required(response, "counts", "Cloud import finalize response"), "counts");
|
|
515
|
+
assertId(required(response, "job_id", "Cloud import finalize response"), "job", "job_id");
|
|
516
|
+
if (required(response, "state", "Cloud import finalize response") !== "done") {
|
|
517
|
+
throw new TypeError("state must be done.");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function assertCloudImportSessionCreateResponse(value) {
|
|
521
|
+
const response = object(value);
|
|
522
|
+
if (!response)
|
|
523
|
+
throw new TypeError("Cloud import session response must be an object.");
|
|
524
|
+
assertId(required(response, "session_id", "Cloud import session response"), "job", "session_id");
|
|
525
|
+
if (required(response, "state", "Cloud import session response") !== "receiving") {
|
|
526
|
+
throw new TypeError("state must be receiving.");
|
|
527
|
+
}
|
|
528
|
+
const limits = object(required(response, "chunk_limits", "Cloud import session response"));
|
|
529
|
+
if (!limits)
|
|
530
|
+
throw new TypeError("chunk_limits must be an object.");
|
|
531
|
+
for (const key of ["max_body_bytes", "max_history_rows", "max_keywords"]) {
|
|
532
|
+
assertInteger(required(limits, key, "chunk_limits"), `chunk_limits.${key}`, 1);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function assertCloudImportChunkResponse(value) {
|
|
536
|
+
const response = object(value);
|
|
537
|
+
if (!response)
|
|
538
|
+
throw new TypeError("Cloud import chunk response must be an object.");
|
|
539
|
+
if (required(response, "state", "Cloud import chunk response") !== "receiving") {
|
|
540
|
+
throw new TypeError("state must be receiving.");
|
|
541
|
+
}
|
|
542
|
+
assertInteger(required(response, "chunks_received", "Cloud import chunk response"), "chunks_received", 0);
|
|
543
|
+
assertInteger(required(response, "chunk_count", "Cloud import chunk response"), "chunk_count", 1);
|
|
544
|
+
}
|
|
545
|
+
function queryValues(query, key) {
|
|
546
|
+
const value = query?.[key];
|
|
547
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
548
|
+
}
|
|
549
|
+
function assertQueryIds(query, key, prefix) {
|
|
550
|
+
for (const [index, value] of queryValues(query, key).entries()) {
|
|
551
|
+
assertId(value, prefix, `${key}[${index}]`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/** Validates every public SDK request before a fetch is attempted. */
|
|
555
|
+
export function validatePublicIdRequest(path, contract = {}) {
|
|
556
|
+
const route = segments(path);
|
|
557
|
+
const [first, second, third, fourth, fifth] = route;
|
|
558
|
+
if (first === "projects" && second) {
|
|
559
|
+
assertId(second, "prj", "projectId");
|
|
560
|
+
}
|
|
561
|
+
if (first === "keywords" && second && second !== "bulk") {
|
|
562
|
+
assertId(second, "kw", "keywordId");
|
|
563
|
+
}
|
|
564
|
+
if (first === "rank-checks" && second)
|
|
565
|
+
assertId(second, "check", "checkId");
|
|
566
|
+
if (first === "alert-rules" && second)
|
|
567
|
+
assertId(second, "rule", "ruleId");
|
|
568
|
+
if (first === "api-keys" && second)
|
|
569
|
+
assertId(second, "key", "keyId");
|
|
570
|
+
if (first === "saved-views" && second)
|
|
571
|
+
assertId(second, "view", "viewId");
|
|
572
|
+
if (first === "competitors" && second)
|
|
573
|
+
assertId(second, "comp", "competitorId");
|
|
574
|
+
if (first === "migration-tokens" && second)
|
|
575
|
+
assertId(second, "mtok", "tokenId");
|
|
576
|
+
if (first === "team" && second === "invites" && third)
|
|
577
|
+
assertId(third, "invite", "inviteId");
|
|
578
|
+
if (first === "me" && second === "tokens" && third && third !== "current") {
|
|
579
|
+
assertId(third, "pat", "tokenId");
|
|
580
|
+
}
|
|
581
|
+
if (first === "cloud" && second === "import" && third === "sessions" && fourth) {
|
|
582
|
+
assertId(fourth, "job", "sessionId");
|
|
583
|
+
}
|
|
584
|
+
if (first === "projects" && second && third === "webhooks" && fourth) {
|
|
585
|
+
assertId(fourth, "webhook", "webhookId");
|
|
586
|
+
}
|
|
587
|
+
if (first === "projects" && second && third === "sitemap-monitors" && fourth) {
|
|
588
|
+
assertId(fourth, "prj", "monitorId");
|
|
589
|
+
}
|
|
590
|
+
if (first === "projects" &&
|
|
591
|
+
second &&
|
|
592
|
+
third === "triggered-alerts" &&
|
|
593
|
+
fourth &&
|
|
594
|
+
fourth !== "mark-read") {
|
|
595
|
+
assertId(fourth, "alert", "alertId");
|
|
596
|
+
}
|
|
597
|
+
if (first === "projects" && second && third === "team" && fourth === "invites" && fifth) {
|
|
598
|
+
assertId(fifth, "invite", "inviteId");
|
|
599
|
+
}
|
|
600
|
+
if (first === "projects" && second && third === "team" && fourth === "members" && fifth) {
|
|
601
|
+
assertId(fifth, "member", "memberId");
|
|
602
|
+
}
|
|
603
|
+
if (first === "projects" && second && third === "migration-tokens" && fourth) {
|
|
604
|
+
assertId(fourth, "mtok", "tokenId");
|
|
605
|
+
}
|
|
606
|
+
if (first === "projects" && second && third === "competitors" && fourth) {
|
|
607
|
+
assertId(fourth, "comp", "competitorId");
|
|
608
|
+
}
|
|
609
|
+
if (first === "projects" && second && third === "saved-views" && fourth) {
|
|
610
|
+
assertId(fourth, "view", "viewId");
|
|
611
|
+
}
|
|
612
|
+
if (first === "keywords" && second === "bulk") {
|
|
613
|
+
assertIdArray(object(contract.body)?.keyword_ids, "kw", "keyword_ids");
|
|
614
|
+
}
|
|
615
|
+
if (first === "signals") {
|
|
616
|
+
assertOptionalId(object(contract.body)?.keyword_id, "kw", "keyword_id");
|
|
617
|
+
}
|
|
618
|
+
if (first === "projects" && third === "alert-rules") {
|
|
619
|
+
assertAlertRuleInput(contract.body);
|
|
620
|
+
}
|
|
621
|
+
if (first === "alert-rules")
|
|
622
|
+
assertAlertRuleInput(contract.body);
|
|
623
|
+
if (first === "projects" && third === "exports" && fourth === "rank-history") {
|
|
624
|
+
assertQueryIds(contract.query, "keyword_id", "kw");
|
|
625
|
+
}
|
|
626
|
+
if (first === "projects" &&
|
|
627
|
+
third === "analytics" &&
|
|
628
|
+
(fourth === "query-stats" || fourth === "traffic-snapshots")) {
|
|
629
|
+
assertQueryIds(contract.query, "connection_id", "conn");
|
|
630
|
+
}
|
|
631
|
+
if (first === "projects" &&
|
|
632
|
+
(third === "ranked-keyword-suggestions" || third === "keyword-research")) {
|
|
633
|
+
assertQueryIds(contract.query, "connection_id", "conn");
|
|
634
|
+
}
|
|
635
|
+
if (first === "projects" && third === "keyword-metrics") {
|
|
636
|
+
assertOptionalId(object(contract.body)?.connection_id, "conn", "connection_id");
|
|
637
|
+
}
|
|
638
|
+
if (first === "cloud" && second === "import" && third === "sessions" && !fourth) {
|
|
639
|
+
assertCloudImportSessionCreate(contract.body);
|
|
640
|
+
}
|
|
641
|
+
if (first === "cloud" && second === "import" && !third)
|
|
642
|
+
assertCloudImportPackage(contract.body);
|
|
643
|
+
if (first === "cloud" &&
|
|
644
|
+
second === "import" &&
|
|
645
|
+
third === "sessions" &&
|
|
646
|
+
fourth &&
|
|
647
|
+
fifth === "chunks") {
|
|
648
|
+
assertCloudImportChunk(contract.body);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** Validates ID-bearing public response fields for resources with contextual IDs. */
|
|
652
|
+
export function validatePublicIdResponse(path, response, method) {
|
|
653
|
+
const route = segments(path);
|
|
654
|
+
const [first, second, third, fourth, fifth] = route;
|
|
655
|
+
const requestMethod = method?.toUpperCase();
|
|
656
|
+
if (first === "cloud" && second === "import") {
|
|
657
|
+
if (third === "compatibility" && requestMethod === "GET") {
|
|
658
|
+
assertCloudImportCompatibilityResponse(response);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (!third && requestMethod === "POST") {
|
|
662
|
+
assertCloudImportFinalizeResponse(response);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (third === "sessions" && !fourth && requestMethod === "POST") {
|
|
666
|
+
assertCloudImportSessionCreateResponse(response);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (third === "sessions" && fourth && fifth === "chunks" && requestMethod === "PUT") {
|
|
670
|
+
assertCloudImportChunkResponse(response);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (third === "sessions" && fourth && fifth === "finalize" && requestMethod === "POST") {
|
|
674
|
+
assertCloudImportFinalizeResponse(response);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const isProjectAlertRuleRoute = first === "projects" && third === "alert-rules";
|
|
679
|
+
const isTopLevelAlertRuleRoute = first === "alert-rules" && second;
|
|
680
|
+
if (!isProjectAlertRuleRoute && !isTopLevelAlertRuleRoute)
|
|
681
|
+
return;
|
|
682
|
+
if (method?.toUpperCase() === "DELETE")
|
|
683
|
+
return;
|
|
684
|
+
const body = object(response);
|
|
685
|
+
if (Array.isArray(body?.data)) {
|
|
686
|
+
for (const [index, rule] of body.data.entries()) {
|
|
687
|
+
assertAlertRuleResponse(rule, `data[${index}]`);
|
|
688
|
+
}
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
assertAlertRuleResponse(response, "alert rule");
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=public-id-contract.js.map
|