@widecast/sdk 0.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/CHANGELOG.md +21 -0
- package/LICENSE +190 -0
- package/README.md +94 -0
- package/dist/index.cjs +849 -0
- package/dist/index.d.cts +448 -0
- package/dist/index.d.ts +448 -0
- package/dist/index.js +788 -0
- package/package.json +55 -0
- package/src/index.ts +1109 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
var VERSION = "0.1.0";
|
|
12
|
+
var DEFAULT_BASE_URL = typeof process !== "undefined" && process.env?.WIDECAST_BASE_URL || "https://widecast.ai/app/dashboard2";
|
|
13
|
+
var TERMINAL_STATUSES = ["completed", "failed"];
|
|
14
|
+
var SCRIPT_MIN_WORDS = 80;
|
|
15
|
+
var SCRIPT_MAX_WORDS = 500;
|
|
16
|
+
var IDEA_MIN_WORDS = 5;
|
|
17
|
+
var IDEA_MAX_WORDS = 1e3;
|
|
18
|
+
var BLOG_MIN_WORDS = 30;
|
|
19
|
+
var BLOG_MAX_WORDS = 3e3;
|
|
20
|
+
var OUTPUT_TYPES = ["text", "scene", "video"];
|
|
21
|
+
var SOURCES = [
|
|
22
|
+
"text",
|
|
23
|
+
"idea",
|
|
24
|
+
"blog",
|
|
25
|
+
"video_url",
|
|
26
|
+
"video_file",
|
|
27
|
+
"audio_url",
|
|
28
|
+
"audio_file"
|
|
29
|
+
];
|
|
30
|
+
var FACELESS_SOURCES = ["text", "idea", "blog"];
|
|
31
|
+
var CONTENT_TYPES = ["blog", "facebook", "x", "linkedin"];
|
|
32
|
+
var INTERVENTION_LEVELS = [0, 1, 2];
|
|
33
|
+
var PUBLISH_PLATFORMS = [
|
|
34
|
+
"youtube",
|
|
35
|
+
"tiktok",
|
|
36
|
+
"instagram",
|
|
37
|
+
"facebook",
|
|
38
|
+
"linkedin",
|
|
39
|
+
"x",
|
|
40
|
+
"threads",
|
|
41
|
+
"pinterest",
|
|
42
|
+
"reddit",
|
|
43
|
+
"bluesky",
|
|
44
|
+
"google_business"
|
|
45
|
+
];
|
|
46
|
+
var MEDIA_SOURCES = {
|
|
47
|
+
video_url: { media_type: "video", input_kind: "url", field: "video_url" },
|
|
48
|
+
video_file: { media_type: "video", input_kind: "file", field: "video_file" },
|
|
49
|
+
audio_url: { media_type: "audio", input_kind: "url", field: "audio_url" },
|
|
50
|
+
audio_file: { media_type: "audio", input_kind: "file", field: "audio_file" }
|
|
51
|
+
};
|
|
52
|
+
var MEDIA_MAX_DURATION_SECONDS = 120;
|
|
53
|
+
var MEDIA_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
54
|
+
var VIDEO_LENGTHS = ["short", "normal"];
|
|
55
|
+
var LANGUAGES = ["English", "Vietnamese"];
|
|
56
|
+
var WidecastError = class extends Error {
|
|
57
|
+
constructor(message, opts = {}) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = "WidecastError";
|
|
60
|
+
this.code = opts.code ?? "";
|
|
61
|
+
this.requestId = opts.requestId ?? "";
|
|
62
|
+
this.status = opts.status ?? 0;
|
|
63
|
+
this.docUrl = opts.docUrl ?? "";
|
|
64
|
+
this.param = opts.param;
|
|
65
|
+
this.responseJson = opts.responseJson;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var InvalidRequestError = class extends WidecastError {
|
|
69
|
+
constructor(m, o = {}) {
|
|
70
|
+
super(m, o);
|
|
71
|
+
this.name = "InvalidRequestError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var NotFoundError = class extends WidecastError {
|
|
75
|
+
constructor(m, o = {}) {
|
|
76
|
+
super(m, o);
|
|
77
|
+
this.name = "NotFoundError";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var PreconditionFailedError = class extends WidecastError {
|
|
81
|
+
constructor(m, o = {}) {
|
|
82
|
+
super(m, o);
|
|
83
|
+
this.name = "PreconditionFailedError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var RateLimitError = class extends WidecastError {
|
|
87
|
+
constructor(m, o = {}) {
|
|
88
|
+
super(m, o);
|
|
89
|
+
this.name = "RateLimitError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var APIError = class extends WidecastError {
|
|
93
|
+
constructor(m, o = {}) {
|
|
94
|
+
super(m, o);
|
|
95
|
+
this.name = "APIError";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var ERROR_CLASSES = {
|
|
99
|
+
invalid_request_error: InvalidRequestError,
|
|
100
|
+
not_found_error: NotFoundError,
|
|
101
|
+
precondition_failed: PreconditionFailedError,
|
|
102
|
+
rate_limit_error: RateLimitError,
|
|
103
|
+
api_error: APIError,
|
|
104
|
+
authentication_error: WidecastError,
|
|
105
|
+
permission_error: WidecastError
|
|
106
|
+
};
|
|
107
|
+
var _client;
|
|
108
|
+
var Video = class {
|
|
109
|
+
constructor(data, client) {
|
|
110
|
+
__privateAdd(this, _client);
|
|
111
|
+
Object.assign(this, data);
|
|
112
|
+
__privateSet(this, _client, client);
|
|
113
|
+
}
|
|
114
|
+
get isTerminal() {
|
|
115
|
+
return TERMINAL_STATUSES.includes(this.status);
|
|
116
|
+
}
|
|
117
|
+
// ── Ergonomic unwrapper for `result` ─────────────────────────────────────
|
|
118
|
+
/** URL where the user reviews the rendered scenes + audio. Present from the
|
|
119
|
+
* first response (pending / processing / completed) — the review page
|
|
120
|
+
* handles early arrival itself (spinner + in-page polling), so this is
|
|
121
|
+
* safe to share with the user before status='completed'. */
|
|
122
|
+
get review_url() {
|
|
123
|
+
return this.result?.review_url ?? null;
|
|
124
|
+
}
|
|
125
|
+
/** Direct MP4 URL — present only when status='completed' AND the video was
|
|
126
|
+
* created with output_type='video' (or exported via client.export_video). */
|
|
127
|
+
get video_url() {
|
|
128
|
+
return this.result?.video_url ?? null;
|
|
129
|
+
}
|
|
130
|
+
/** Poll /v1/status until terminal or timeout.
|
|
131
|
+
* Default: fixed 5-second polling (no backoff). Override kwargs for
|
|
132
|
+
* long-running polls where backoff is preferred. */
|
|
133
|
+
async wait(opts = {}) {
|
|
134
|
+
const timeoutMs = opts.timeoutMs ?? 6e5;
|
|
135
|
+
const maxIntervalMs = opts.maxIntervalMs ?? 5e3;
|
|
136
|
+
const backoff = opts.backoffMultiplier ?? 1;
|
|
137
|
+
let interval = opts.initialIntervalMs ?? 5e3;
|
|
138
|
+
const deadline = Date.now() + timeoutMs;
|
|
139
|
+
let latest = this;
|
|
140
|
+
while (!latest.isTerminal && Date.now() < deadline) {
|
|
141
|
+
const remaining = deadline - Date.now();
|
|
142
|
+
await sleep(Math.min(interval, Math.max(100, remaining)));
|
|
143
|
+
latest = await __privateGet(this, _client).get_status(this.id);
|
|
144
|
+
interval = Math.min(interval * backoff, maxIntervalMs);
|
|
145
|
+
}
|
|
146
|
+
return latest;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
_client = new WeakMap();
|
|
150
|
+
var _fetch, _Widecast_instances, get_fn, request_fn, requestMultipart_fn;
|
|
151
|
+
var Widecast = class {
|
|
152
|
+
constructor(cfg = {}) {
|
|
153
|
+
__privateAdd(this, _Widecast_instances);
|
|
154
|
+
__privateAdd(this, _fetch);
|
|
155
|
+
this.apiKey = cfg.apiKey ?? (typeof process !== "undefined" ? process.env?.WIDECAST_API_KEY : void 0);
|
|
156
|
+
this.baseUrl = (cfg.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
157
|
+
this.timeoutMs = cfg.timeoutMs ?? 6e4;
|
|
158
|
+
this.maxRetries = cfg.maxRetries ?? 3;
|
|
159
|
+
this.userAgent = cfg.userAgent ?? `widecast-js/${VERSION}`;
|
|
160
|
+
__privateSet(this, _fetch, cfg.fetchImpl ?? globalThis.fetch);
|
|
161
|
+
if (!__privateGet(this, _fetch)) {
|
|
162
|
+
throw new Error("No fetch implementation found. Pass `fetchImpl` for environments without global fetch.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ── Public methods ──────────────────────────────────────────────────────
|
|
166
|
+
async create_video(opts) {
|
|
167
|
+
if (!opts) {
|
|
168
|
+
throw new InvalidRequestError(
|
|
169
|
+
"create_video requires options.",
|
|
170
|
+
{ code: "missing_field", param: "options" }
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const source = opts.source ?? "text";
|
|
174
|
+
if (!SOURCES.includes(source)) {
|
|
175
|
+
throw new InvalidRequestError(
|
|
176
|
+
`source must be one of ${JSON.stringify(SOURCES)} (got ${JSON.stringify(opts.source)}).`,
|
|
177
|
+
{ code: "invalid_source", param: "source" }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const outputType = opts.output_type ?? "scene";
|
|
181
|
+
if (!OUTPUT_TYPES.includes(outputType)) {
|
|
182
|
+
throw new InvalidRequestError(
|
|
183
|
+
`output_type must be one of ${JSON.stringify(OUTPUT_TYPES)} (got ${JSON.stringify(opts.output_type)}).`,
|
|
184
|
+
{ code: "invalid_output_type", param: "output_type" }
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (outputType === "text" && source === "text") {
|
|
188
|
+
throw new InvalidRequestError(
|
|
189
|
+
"output_type='text' requires a generative source (e.g. source='idea'). With source='text' you already supplied the script \u2014 use output_type 'scene' or 'video'.",
|
|
190
|
+
{ code: "invalid_output_type", param: "output_type" }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const faceless = opts.faceless === true;
|
|
194
|
+
if (opts.faceless !== void 0 && typeof opts.faceless !== "boolean") {
|
|
195
|
+
throw new InvalidRequestError(
|
|
196
|
+
`faceless must be a boolean (got ${JSON.stringify(opts.faceless)}).`,
|
|
197
|
+
{ code: "invalid_faceless", param: "faceless" }
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (faceless) {
|
|
201
|
+
if (outputType === "text") {
|
|
202
|
+
throw new InvalidRequestError(
|
|
203
|
+
"faceless controls A/B-roll for generated scenes; it has no effect with output_type='text'. Use output_type 'scene' or 'video'.",
|
|
204
|
+
{ code: "invalid_faceless", param: "faceless" }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (!FACELESS_SOURCES.includes(source)) {
|
|
208
|
+
throw new InvalidRequestError(
|
|
209
|
+
`faceless is only supported for source in ${JSON.stringify(FACELESS_SOURCES)} (got source=${JSON.stringify(source)}).`,
|
|
210
|
+
{ code: "invalid_faceless", param: "faceless" }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const body = { source, output_type: outputType };
|
|
215
|
+
if (faceless) body.faceless = true;
|
|
216
|
+
if (Array.isArray(opts.media_pool) && opts.media_pool.length) {
|
|
217
|
+
body.media_pool = opts.media_pool.filter((u) => typeof u === "string" && u.trim());
|
|
218
|
+
}
|
|
219
|
+
let uploadField;
|
|
220
|
+
let uploadFile;
|
|
221
|
+
if (source === "text") {
|
|
222
|
+
if (typeof opts.script_text !== "string" || !opts.script_text.trim()) {
|
|
223
|
+
throw new InvalidRequestError(
|
|
224
|
+
"script_text (non-empty string) is required when source='text'.",
|
|
225
|
+
{ code: "missing_field", param: "script_text" }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const wordCount = opts.script_text.trim().split(/\s+/).length;
|
|
229
|
+
if (wordCount < SCRIPT_MIN_WORDS) {
|
|
230
|
+
throw new InvalidRequestError(
|
|
231
|
+
`script_text has ${wordCount} words; minimum is ${SCRIPT_MIN_WORDS} (~20s of narration).`,
|
|
232
|
+
{ code: "script_too_short", param: "script_text" }
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (wordCount > SCRIPT_MAX_WORDS) {
|
|
236
|
+
throw new InvalidRequestError(
|
|
237
|
+
`script_text has ${wordCount} words; maximum is ${SCRIPT_MAX_WORDS} (~120s / 2 min of narration).`,
|
|
238
|
+
{ code: "script_too_long", param: "script_text" }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
body.script_text = opts.script_text;
|
|
242
|
+
} else if (source === "idea" || source === "blog") {
|
|
243
|
+
const genSpec = source === "idea" ? {
|
|
244
|
+
field: "idea_text",
|
|
245
|
+
value: opts.idea_text,
|
|
246
|
+
min: IDEA_MIN_WORDS,
|
|
247
|
+
missingCode: "missing_idea_text",
|
|
248
|
+
tooShortCode: "idea_too_short"
|
|
249
|
+
} : {
|
|
250
|
+
field: "blog_text",
|
|
251
|
+
value: opts.blog_text,
|
|
252
|
+
min: BLOG_MIN_WORDS,
|
|
253
|
+
missingCode: "missing_blog_text",
|
|
254
|
+
tooShortCode: "blog_too_short"
|
|
255
|
+
};
|
|
256
|
+
if (typeof genSpec.value !== "string" || !genSpec.value.trim()) {
|
|
257
|
+
throw new InvalidRequestError(
|
|
258
|
+
`${genSpec.field} (non-empty string) is required when source='${source}'.`,
|
|
259
|
+
{ code: genSpec.missingCode, param: genSpec.field }
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const wordCount = genSpec.value.trim().split(/\s+/).length;
|
|
263
|
+
if (wordCount < genSpec.min) {
|
|
264
|
+
throw new InvalidRequestError(
|
|
265
|
+
`${genSpec.field} has ${wordCount} words; minimum is ${genSpec.min}.`,
|
|
266
|
+
{ code: genSpec.tooShortCode, param: genSpec.field }
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const language = opts.language ?? "English";
|
|
270
|
+
if (!LANGUAGES.includes(language)) {
|
|
271
|
+
throw new InvalidRequestError(
|
|
272
|
+
`language must be one of ${JSON.stringify(LANGUAGES)} (got ${JSON.stringify(opts.language)}).`,
|
|
273
|
+
{ code: "invalid_language", param: "language" }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const videoLength = opts.video_length ?? "short";
|
|
277
|
+
if (!VIDEO_LENGTHS.includes(videoLength)) {
|
|
278
|
+
throw new InvalidRequestError(
|
|
279
|
+
`video_length must be one of ${JSON.stringify(VIDEO_LENGTHS)} (got ${JSON.stringify(opts.video_length)}).`,
|
|
280
|
+
{ code: "invalid_video_length", param: "video_length" }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const researchEnabled = opts.research_enabled ?? true;
|
|
284
|
+
if (typeof researchEnabled !== "boolean") {
|
|
285
|
+
throw new InvalidRequestError(
|
|
286
|
+
`research_enabled must be a boolean (got ${typeof researchEnabled}).`,
|
|
287
|
+
{ code: "invalid_research_enabled", param: "research_enabled" }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
body[genSpec.field] = genSpec.value;
|
|
291
|
+
body.language = language;
|
|
292
|
+
body.video_length = videoLength;
|
|
293
|
+
body.research_enabled = researchEnabled;
|
|
294
|
+
} else {
|
|
295
|
+
const media = MEDIA_SOURCES[source];
|
|
296
|
+
if (media.input_kind === "url") {
|
|
297
|
+
const value = source === "video_url" ? opts.video_url : opts.audio_url;
|
|
298
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
299
|
+
throw new InvalidRequestError(
|
|
300
|
+
`${media.field} (a media URL) is required when source='${source}'.`,
|
|
301
|
+
{ code: `missing_${media.field}`, param: media.field }
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
body[media.field] = value.trim();
|
|
305
|
+
} else {
|
|
306
|
+
const file = source === "video_file" ? opts.video_file : opts.audio_file;
|
|
307
|
+
if (!file) {
|
|
308
|
+
throw new InvalidRequestError(
|
|
309
|
+
`${media.field} (a Blob/File) is required when source='${source}'.`,
|
|
310
|
+
{ code: "missing_media_file", param: media.field }
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (typeof file.size === "number" && file.size > MEDIA_MAX_FILE_BYTES) {
|
|
314
|
+
throw new InvalidRequestError(
|
|
315
|
+
`${media.field} is ${(file.size / 1024 / 1024).toFixed(1)} MB; maximum is ${Math.floor(MEDIA_MAX_FILE_BYTES / (1024 * 1024))} MB.`,
|
|
316
|
+
{ code: "file_too_large", param: media.field }
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
uploadField = media.field;
|
|
320
|
+
uploadFile = file;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (opts.wait_for_render) body.wait_for_render = true;
|
|
324
|
+
if (opts.callback_url) body.callback_url = opts.callback_url;
|
|
325
|
+
if (opts.metadata) body.metadata = opts.metadata;
|
|
326
|
+
const idem = opts.idempotency_key ?? randomUuid();
|
|
327
|
+
let data;
|
|
328
|
+
if (uploadField) {
|
|
329
|
+
const form = new FormData();
|
|
330
|
+
for (const [k, v] of Object.entries(body)) {
|
|
331
|
+
form.set(k, typeof v === "object" && v !== null ? JSON.stringify(v) : String(v));
|
|
332
|
+
}
|
|
333
|
+
form.set(
|
|
334
|
+
uploadField,
|
|
335
|
+
uploadFile,
|
|
336
|
+
uploadFile.name ?? "upload"
|
|
337
|
+
);
|
|
338
|
+
data = await __privateMethod(this, _Widecast_instances, requestMultipart_fn).call(this, "/v1/create_video", form, idem);
|
|
339
|
+
} else {
|
|
340
|
+
data = await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/create_video", body, idem);
|
|
341
|
+
}
|
|
342
|
+
return new Video(data, this);
|
|
343
|
+
}
|
|
344
|
+
/** POST /v1/export_video — kick the final-MP4 renderer for an existing
|
|
345
|
+
* scene-output video. Idempotent: calling twice is a no-op.
|
|
346
|
+
* Throws `PreconditionFailedError` if scenes are not yet ready. */
|
|
347
|
+
async export_video(videoId) {
|
|
348
|
+
if (!videoId || typeof videoId !== "string") {
|
|
349
|
+
throw new InvalidRequestError("video_id must be a non-empty string.", { code: "invalid_id", param: "video_id" });
|
|
350
|
+
}
|
|
351
|
+
const data = await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/export_video", { id: videoId });
|
|
352
|
+
return new Video(data, this);
|
|
353
|
+
}
|
|
354
|
+
async get_status(videoId) {
|
|
355
|
+
if (!videoId || typeof videoId !== "string") {
|
|
356
|
+
throw new InvalidRequestError("video_id must be a non-empty string.", { code: "invalid_id", param: "video_id" });
|
|
357
|
+
}
|
|
358
|
+
const data = await __privateMethod(this, _Widecast_instances, request_fn).call(this, "GET", `/v1/status/${videoId}`);
|
|
359
|
+
return new Video(data, this);
|
|
360
|
+
}
|
|
361
|
+
/** POST /v1/create_content — generate written content (blog / social post)
|
|
362
|
+
* from a URL, an idea/topic, or pasted text. Async: returns a Video with
|
|
363
|
+
* `review_url` (the public content viewer) already populated — safe to
|
|
364
|
+
* share before completion; the viewer shows a spinner while content
|
|
365
|
+
* generates. Poll with `.wait()` for the final state. */
|
|
366
|
+
async create_content(opts) {
|
|
367
|
+
if (!opts || typeof opts.content !== "string" || !opts.content.trim()) {
|
|
368
|
+
throw new InvalidRequestError(
|
|
369
|
+
"content (a URL, idea, or text) is required.",
|
|
370
|
+
{ code: "missing_field", param: "content" }
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const contentType = opts.content_type ?? "blog";
|
|
374
|
+
if (!CONTENT_TYPES.includes(contentType)) {
|
|
375
|
+
throw new InvalidRequestError(
|
|
376
|
+
`content_type must be one of ${JSON.stringify(CONTENT_TYPES)} (got ${JSON.stringify(opts.content_type)}).`,
|
|
377
|
+
{ code: "invalid_content_type", param: "content_type" }
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
const language = opts.language ?? "English";
|
|
381
|
+
if (typeof language !== "string" || !language.trim()) {
|
|
382
|
+
throw new InvalidRequestError(
|
|
383
|
+
'language (e.g. "English") is required.',
|
|
384
|
+
{ code: "missing_field", param: "language" }
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
const body = {
|
|
388
|
+
content: opts.content.trim(),
|
|
389
|
+
content_type: contentType,
|
|
390
|
+
language: language.trim()
|
|
391
|
+
};
|
|
392
|
+
if (opts.callback_url) body.callback_url = opts.callback_url;
|
|
393
|
+
if (opts.metadata) body.metadata = opts.metadata;
|
|
394
|
+
const data = await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/create_content", body);
|
|
395
|
+
return new Video(data, this);
|
|
396
|
+
}
|
|
397
|
+
/** POST /v1/enhance_script — improve a DRAFT script with AI. Async: returns
|
|
398
|
+
* a Video with `review_url` (the Script Editor) already populated — safe
|
|
399
|
+
* to share before completion; the editor shows a spinner while enhancing.
|
|
400
|
+
* Poll with `.wait()` for the final enhanced script. */
|
|
401
|
+
async enhance_script(opts) {
|
|
402
|
+
if (!opts || typeof opts.script_text !== "string" || !opts.script_text.trim()) {
|
|
403
|
+
throw new InvalidRequestError(
|
|
404
|
+
"script_text (the draft to enhance) is required.",
|
|
405
|
+
{ code: "missing_field", param: "script_text" }
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const level = opts.intervention_level ?? 1;
|
|
409
|
+
if (!INTERVENTION_LEVELS.includes(level)) {
|
|
410
|
+
throw new InvalidRequestError(
|
|
411
|
+
`intervention_level must be one of ${JSON.stringify(INTERVENTION_LEVELS)} (got ${JSON.stringify(opts.intervention_level)}).`,
|
|
412
|
+
{ code: "invalid_intervention_level", param: "intervention_level" }
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const body = {
|
|
416
|
+
script_text: opts.script_text.trim(),
|
|
417
|
+
intervention_level: level
|
|
418
|
+
};
|
|
419
|
+
if (typeof opts.language === "string" && opts.language.trim()) body.language = opts.language.trim();
|
|
420
|
+
if (opts.callback_url) body.callback_url = opts.callback_url;
|
|
421
|
+
if (opts.metadata) body.metadata = opts.metadata;
|
|
422
|
+
const data = await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/enhance_script", body);
|
|
423
|
+
return new Video(data, this);
|
|
424
|
+
}
|
|
425
|
+
/** POST /v1/suggest_ideas — SYNCHRONOUS. Returns video topic ideas for an
|
|
426
|
+
* industry immediately (no polling). `industry_id` falls back to your
|
|
427
|
+
* account industry if omitted. Consumes credits. */
|
|
428
|
+
async suggest_ideas(opts = {}) {
|
|
429
|
+
const body = { num_topics: opts.num_topics ?? 5 };
|
|
430
|
+
if (opts.industry_id) body.industry_id = opts.industry_id;
|
|
431
|
+
if (opts.sub_industry) body.sub_industry = opts.sub_industry;
|
|
432
|
+
if (opts.user_location) body.user_location = opts.user_location;
|
|
433
|
+
return await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/suggest_ideas", body);
|
|
434
|
+
}
|
|
435
|
+
/** POST /v1/collect_ideas — SYNCHRONOUS. Returns video ideas derived from a
|
|
436
|
+
* product/service description (≥10 chars) immediately. Consumes credits. */
|
|
437
|
+
async collect_ideas(opts) {
|
|
438
|
+
if (!opts || typeof opts.product_service_input !== "string" || opts.product_service_input.trim().length < 10) {
|
|
439
|
+
throw new InvalidRequestError(
|
|
440
|
+
"product_service_input is required (\u226510 chars).",
|
|
441
|
+
{ code: "missing_field", param: "product_service_input" }
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
const body = { product_service_input: opts.product_service_input.trim() };
|
|
445
|
+
if (opts.sub_industry) body.sub_industry = opts.sub_industry;
|
|
446
|
+
if (opts.user_location) body.user_location = opts.user_location;
|
|
447
|
+
return await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/collect_ideas", body);
|
|
448
|
+
}
|
|
449
|
+
/** POST /v1/publish — distribute content to connected social platforms.
|
|
450
|
+
* Provide EXACTLY ONE of topic_id / text / video_url. `platforms` defaults
|
|
451
|
+
* to ALL connected. Charges 1 credit. Returns the accepted-publish envelope
|
|
452
|
+
* (HTTP 202) — publishing is async on the platform side, so poll
|
|
453
|
+
* get_status(id) (with any of `request_ids`) for per-platform post URLs. */
|
|
454
|
+
async publish(opts) {
|
|
455
|
+
const modes = [opts.topic_id, opts.text, opts.video_url].filter(Boolean);
|
|
456
|
+
if (modes.length !== 1) {
|
|
457
|
+
throw new InvalidRequestError(
|
|
458
|
+
"Provide exactly one of topic_id, text, or video_url.",
|
|
459
|
+
{ code: "invalid_publish_input" }
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (opts.video_url && !(typeof opts.title === "string" && opts.title.trim())) {
|
|
463
|
+
throw new InvalidRequestError(
|
|
464
|
+
"title is required when posting an external video_url.",
|
|
465
|
+
{ code: "missing_field", param: "title" }
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
if (opts.platforms !== void 0) {
|
|
469
|
+
const bad = opts.platforms.filter(
|
|
470
|
+
(p) => !PUBLISH_PLATFORMS.includes(p)
|
|
471
|
+
);
|
|
472
|
+
if (bad.length) {
|
|
473
|
+
throw new InvalidRequestError(
|
|
474
|
+
`Unknown platform(s) ${JSON.stringify(bad)}. Valid: ${JSON.stringify(PUBLISH_PLATFORMS)}.`,
|
|
475
|
+
{ code: "invalid_platforms", param: "platforms" }
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const body = {};
|
|
480
|
+
if (opts.topic_id) body.topic_id = opts.topic_id.trim();
|
|
481
|
+
if (opts.text) body.text = opts.text.trim();
|
|
482
|
+
if (opts.video_url) body.video_url = opts.video_url.trim();
|
|
483
|
+
if (opts.title) body.title = opts.title.trim();
|
|
484
|
+
if (opts.description) body.description = opts.description.trim();
|
|
485
|
+
if (opts.photo_urls) body.photo_urls = opts.photo_urls;
|
|
486
|
+
if (opts.platforms) body.platforms = opts.platforms;
|
|
487
|
+
if (opts.scheduled_date) body.scheduled_date = opts.scheduled_date;
|
|
488
|
+
if (opts.timezone) body.timezone = opts.timezone;
|
|
489
|
+
if (opts.callback_url) body.callback_url = opts.callback_url;
|
|
490
|
+
if (opts.metadata) body.metadata = opts.metadata;
|
|
491
|
+
return await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/publish", body, opts.idempotency_key);
|
|
492
|
+
}
|
|
493
|
+
/** GET /v1/videos — list the account's recent videos (20/page). Free. */
|
|
494
|
+
async list_videos(opts = {}) {
|
|
495
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/videos", { from_record: opts.from_record ?? 0 });
|
|
496
|
+
}
|
|
497
|
+
/** GET /v1/search — search the account's content by keywords. Free. */
|
|
498
|
+
async search(query, opts = {}) {
|
|
499
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
500
|
+
throw new InvalidRequestError("query is required.", { code: "missing_field", param: "q" });
|
|
501
|
+
}
|
|
502
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/search", { q: query.trim(), limit: opts.limit ?? 10 });
|
|
503
|
+
}
|
|
504
|
+
/** GET /v1/account — account profile + remaining credits. Free. */
|
|
505
|
+
async account() {
|
|
506
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/account");
|
|
507
|
+
}
|
|
508
|
+
/** GET /v1/analytics — social analytics dashboard. Free but SLOW. */
|
|
509
|
+
async analytics(opts = {}) {
|
|
510
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/analytics", {
|
|
511
|
+
period: opts.period ?? "last_week",
|
|
512
|
+
start_date: opts.start_date,
|
|
513
|
+
end_date: opts.end_date
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** GET /v1/roadmap — the account's content roadmap. Free. */
|
|
517
|
+
async roadmap(opts = {}) {
|
|
518
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/roadmap", { cycle: opts.cycle ?? 1 });
|
|
519
|
+
}
|
|
520
|
+
/** GET /v1/production_plan — the weekly production plan. Free.
|
|
521
|
+
* NOTE: passing both week_start + week_end may backfill rows upstream. */
|
|
522
|
+
async production_plan(opts = {}) {
|
|
523
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/production_plan", {
|
|
524
|
+
page: opts.page ?? 0,
|
|
525
|
+
week_start: opts.week_start,
|
|
526
|
+
week_end: opts.week_end
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/** GET /v1/foundation_videos — curated foundation-video templates. Free. */
|
|
530
|
+
async foundation_videos(opts = {}) {
|
|
531
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/foundation_videos", {
|
|
532
|
+
industry: opts.industry,
|
|
533
|
+
sub_industry: opts.sub_industry,
|
|
534
|
+
page: opts.page ?? 0
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
/** GET /v1/recommendations — recommended video ideas for an industry. Free. */
|
|
538
|
+
async recommendations(opts = {}) {
|
|
539
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/recommendations", {
|
|
540
|
+
industry: opts.industry,
|
|
541
|
+
page: opts.page ?? 0
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
// ── Connections (Batch E — connect / accounts / configure, free) ────────
|
|
545
|
+
/** POST /v1/connect — get an OAuth link to connect a social platform. Free.
|
|
546
|
+
* Returns `{object:"connect", url, expires_in, ...}` — the USER opens `url`
|
|
547
|
+
* to complete the connection on the web (WideCast never performs the OAuth). */
|
|
548
|
+
async connect(opts = {}) {
|
|
549
|
+
if (opts.platform && !PUBLISH_PLATFORMS.includes(opts.platform)) {
|
|
550
|
+
throw new InvalidRequestError(
|
|
551
|
+
`Unknown platform ${JSON.stringify(opts.platform)}. Valid: ${JSON.stringify(PUBLISH_PLATFORMS)}.`,
|
|
552
|
+
{ code: "invalid_platforms", param: "platform" }
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
const body = {};
|
|
556
|
+
if (opts.platform) body.platform = opts.platform;
|
|
557
|
+
return await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/connect", body);
|
|
558
|
+
}
|
|
559
|
+
/** GET /v1/accounts — list the account's connected social platforms. Free. */
|
|
560
|
+
async accounts() {
|
|
561
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/accounts");
|
|
562
|
+
}
|
|
563
|
+
/** GET /v1/platform_settings — load saved per-platform publish settings. Free. */
|
|
564
|
+
async platform_settings() {
|
|
565
|
+
return await __privateMethod(this, _Widecast_instances, get_fn).call(this, "/v1/platform_settings");
|
|
566
|
+
}
|
|
567
|
+
/** POST /v1/platform_settings — save one platform's publish settings. Free. */
|
|
568
|
+
async set_platform_settings(platform, settings) {
|
|
569
|
+
if (!PUBLISH_PLATFORMS.includes(platform)) {
|
|
570
|
+
throw new InvalidRequestError(
|
|
571
|
+
`platform must be one of ${JSON.stringify(PUBLISH_PLATFORMS)} (got ${JSON.stringify(platform)}).`,
|
|
572
|
+
{ code: "invalid_platforms", param: "platform" }
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (!settings || typeof settings !== "object") {
|
|
576
|
+
throw new InvalidRequestError(
|
|
577
|
+
"settings (an object) is required.",
|
|
578
|
+
{ code: "missing_field", param: "settings" }
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
return await __privateMethod(this, _Widecast_instances, request_fn).call(this, "POST", "/v1/platform_settings", { platform, settings });
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
_fetch = new WeakMap();
|
|
585
|
+
_Widecast_instances = new WeakSet();
|
|
586
|
+
// ── Read / library (Batch C — GET, synchronous, free) ───────────────────
|
|
587
|
+
get_fn = function(path, params) {
|
|
588
|
+
const qs = new URLSearchParams();
|
|
589
|
+
for (const [k, v] of Object.entries(params ?? {})) {
|
|
590
|
+
if (v !== void 0 && v !== null && v !== "") qs.set(k, String(v));
|
|
591
|
+
}
|
|
592
|
+
const q = qs.toString();
|
|
593
|
+
return __privateMethod(this, _Widecast_instances, request_fn).call(this, "GET", q ? `${path}?${q}` : path);
|
|
594
|
+
};
|
|
595
|
+
request_fn = async function(method, path, body, idempotencyKey) {
|
|
596
|
+
const url = this.baseUrl + path;
|
|
597
|
+
const headers = {
|
|
598
|
+
"Accept": "application/json",
|
|
599
|
+
"User-Agent": this.userAgent,
|
|
600
|
+
"X-Widecast-Sdk": `js/${VERSION}`
|
|
601
|
+
};
|
|
602
|
+
const disableTel = typeof process !== "undefined" && process.env?.WIDECAST_DISABLE_TELEMETRY || "";
|
|
603
|
+
if (!["1", "true", "yes"].includes(String(disableTel).toLowerCase())) {
|
|
604
|
+
headers["X-Widecast-Telemetry"] = `sdk=js/${VERSION}`;
|
|
605
|
+
}
|
|
606
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
607
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
608
|
+
if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
|
|
609
|
+
let lastErr;
|
|
610
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
611
|
+
const controller = new AbortController();
|
|
612
|
+
const tid = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
613
|
+
try {
|
|
614
|
+
const resp = await __privateGet(this, _fetch).call(this, url, {
|
|
615
|
+
method,
|
|
616
|
+
headers,
|
|
617
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
618
|
+
signal: controller.signal
|
|
619
|
+
});
|
|
620
|
+
clearTimeout(tid);
|
|
621
|
+
if ((resp.status >= 500 || resp.status === 429) && attempt < this.maxRetries) {
|
|
622
|
+
const wait = resp.status === 429 ? Number(resp.headers.get("Retry-After") ?? 2) * 1e3 : backoffMs(attempt);
|
|
623
|
+
await sleep(wait);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
return await decodeResponse(resp);
|
|
627
|
+
} catch (e) {
|
|
628
|
+
clearTimeout(tid);
|
|
629
|
+
lastErr = e;
|
|
630
|
+
if (e instanceof WidecastError) throw e;
|
|
631
|
+
if (attempt >= this.maxRetries) {
|
|
632
|
+
throw new APIError(`Network error after ${attempt + 1} attempts: ${String(e)}`);
|
|
633
|
+
}
|
|
634
|
+
await sleep(backoffMs(attempt));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
throw new APIError(`Exhausted retries: ${String(lastErr)}`);
|
|
638
|
+
};
|
|
639
|
+
requestMultipart_fn = async function(path, form, idempotencyKey) {
|
|
640
|
+
const url = this.baseUrl + path;
|
|
641
|
+
const headers = {
|
|
642
|
+
"Accept": "application/json",
|
|
643
|
+
"User-Agent": this.userAgent,
|
|
644
|
+
"X-Widecast-Sdk": `js/${VERSION}`
|
|
645
|
+
};
|
|
646
|
+
const disableTel = typeof process !== "undefined" && process.env?.WIDECAST_DISABLE_TELEMETRY || "";
|
|
647
|
+
if (!["1", "true", "yes"].includes(String(disableTel).toLowerCase())) {
|
|
648
|
+
headers["X-Widecast-Telemetry"] = `sdk=js/${VERSION}`;
|
|
649
|
+
}
|
|
650
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
651
|
+
if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
|
|
652
|
+
const controller = new AbortController();
|
|
653
|
+
const tid = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
654
|
+
try {
|
|
655
|
+
const resp = await __privateGet(this, _fetch).call(this, url, { method: "POST", headers, body: form, signal: controller.signal });
|
|
656
|
+
clearTimeout(tid);
|
|
657
|
+
return await decodeResponse(resp);
|
|
658
|
+
} catch (e) {
|
|
659
|
+
clearTimeout(tid);
|
|
660
|
+
if (e instanceof WidecastError) throw e;
|
|
661
|
+
throw new APIError(`Network error during upload: ${String(e)}`);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
function sleep(ms) {
|
|
665
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
666
|
+
}
|
|
667
|
+
function backoffMs(attempt) {
|
|
668
|
+
const base = Math.min(500 * 2 ** attempt, 8e3);
|
|
669
|
+
return base + Math.random() * 250;
|
|
670
|
+
}
|
|
671
|
+
function randomUuid() {
|
|
672
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
673
|
+
return crypto.randomUUID();
|
|
674
|
+
}
|
|
675
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
676
|
+
const r = Math.random() * 16 | 0;
|
|
677
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async function decodeResponse(resp) {
|
|
681
|
+
const requestId = resp.headers.get("X-Request-Id") ?? "";
|
|
682
|
+
let data = null;
|
|
683
|
+
try {
|
|
684
|
+
data = await resp.json();
|
|
685
|
+
} catch (_) {
|
|
686
|
+
data = null;
|
|
687
|
+
}
|
|
688
|
+
if (resp.ok) return data;
|
|
689
|
+
const err = data?.error ?? {};
|
|
690
|
+
const ErrClass = ERROR_CLASSES[err.type ?? "api_error"] ?? WidecastError;
|
|
691
|
+
throw new ErrClass(err.message ?? `HTTP ${resp.status}`, {
|
|
692
|
+
code: err.code ?? "",
|
|
693
|
+
requestId: err.request_id ?? requestId,
|
|
694
|
+
status: resp.status,
|
|
695
|
+
docUrl: err.doc_url ?? "",
|
|
696
|
+
param: err.param,
|
|
697
|
+
responseJson: data
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
var WebhookVerificationError = class extends Error {
|
|
701
|
+
constructor(m) {
|
|
702
|
+
super(m);
|
|
703
|
+
this.name = "WebhookVerificationError";
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
async function verifyWebhook(opts) {
|
|
707
|
+
const { body, signatureHeader, secret } = opts;
|
|
708
|
+
const tolerance = opts.toleranceSeconds ?? 300;
|
|
709
|
+
if (!signatureHeader) throw new WebhookVerificationError("missing X-WideCast-Signature header");
|
|
710
|
+
const parts = {};
|
|
711
|
+
for (const piece of signatureHeader.split(",")) {
|
|
712
|
+
const [k, v] = piece.split("=", 2);
|
|
713
|
+
if (k && v !== void 0) parts[k.trim()] = v;
|
|
714
|
+
}
|
|
715
|
+
const ts = parts["t"];
|
|
716
|
+
const sig = parts["v1"];
|
|
717
|
+
if (!ts || !sig) throw new WebhookVerificationError("signature header missing t= or v1=");
|
|
718
|
+
const tsInt = Number(ts);
|
|
719
|
+
if (!Number.isFinite(tsInt)) throw new WebhookVerificationError("t= is not an integer");
|
|
720
|
+
if (Math.abs(Date.now() / 1e3 - tsInt) > tolerance) {
|
|
721
|
+
throw new WebhookVerificationError(
|
|
722
|
+
`signature timestamp outside tolerance window (${tolerance}s) \u2014 possible replay`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const signed = `${ts}.${body}`;
|
|
726
|
+
const expected = await hmacSha256Hex(secret, signed);
|
|
727
|
+
if (!timingSafeEqual(expected, sig)) throw new WebhookVerificationError("signature mismatch");
|
|
728
|
+
try {
|
|
729
|
+
return JSON.parse(body);
|
|
730
|
+
} catch (e) {
|
|
731
|
+
throw new WebhookVerificationError(`body is not valid JSON: ${e?.message}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async function hmacSha256Hex(secret, msg) {
|
|
735
|
+
const enc = new TextEncoder();
|
|
736
|
+
const subtle = globalThis.crypto && globalThis.crypto.subtle || null;
|
|
737
|
+
if (subtle) {
|
|
738
|
+
const key = await subtle.importKey(
|
|
739
|
+
"raw",
|
|
740
|
+
enc.encode(secret),
|
|
741
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
742
|
+
false,
|
|
743
|
+
["sign"]
|
|
744
|
+
);
|
|
745
|
+
const buf = await subtle.sign("HMAC", key, enc.encode(msg));
|
|
746
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
747
|
+
}
|
|
748
|
+
const cryptoMod = await import("crypto");
|
|
749
|
+
return cryptoMod.createHmac("sha256", secret).update(msg).digest("hex");
|
|
750
|
+
}
|
|
751
|
+
function timingSafeEqual(a, b) {
|
|
752
|
+
if (a.length !== b.length) return false;
|
|
753
|
+
let diff = 0;
|
|
754
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
755
|
+
return diff === 0;
|
|
756
|
+
}
|
|
757
|
+
var index_default = Widecast;
|
|
758
|
+
export {
|
|
759
|
+
APIError,
|
|
760
|
+
BLOG_MAX_WORDS,
|
|
761
|
+
BLOG_MIN_WORDS,
|
|
762
|
+
CONTENT_TYPES,
|
|
763
|
+
FACELESS_SOURCES,
|
|
764
|
+
IDEA_MAX_WORDS,
|
|
765
|
+
IDEA_MIN_WORDS,
|
|
766
|
+
INTERVENTION_LEVELS,
|
|
767
|
+
InvalidRequestError,
|
|
768
|
+
LANGUAGES,
|
|
769
|
+
MEDIA_MAX_DURATION_SECONDS,
|
|
770
|
+
MEDIA_MAX_FILE_BYTES,
|
|
771
|
+
MEDIA_SOURCES,
|
|
772
|
+
NotFoundError,
|
|
773
|
+
OUTPUT_TYPES,
|
|
774
|
+
PUBLISH_PLATFORMS,
|
|
775
|
+
PreconditionFailedError,
|
|
776
|
+
RateLimitError,
|
|
777
|
+
SCRIPT_MAX_WORDS,
|
|
778
|
+
SCRIPT_MIN_WORDS,
|
|
779
|
+
SOURCES,
|
|
780
|
+
VERSION,
|
|
781
|
+
VIDEO_LENGTHS,
|
|
782
|
+
Video,
|
|
783
|
+
WebhookVerificationError,
|
|
784
|
+
Widecast,
|
|
785
|
+
WidecastError,
|
|
786
|
+
index_default as default,
|
|
787
|
+
verifyWebhook
|
|
788
|
+
};
|