@websitescreenshotworld/sdk 0.0.0-stage → 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.
Files changed (81) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +399 -2
  4. package/dist/cjs/auto-schedule.d.ts +20 -0
  5. package/dist/cjs/auto-schedule.js +34 -0
  6. package/dist/cjs/client.d.ts +41 -0
  7. package/dist/cjs/client.js +58 -0
  8. package/dist/cjs/error-codes.d.ts +54 -0
  9. package/dist/cjs/error-codes.js +94 -0
  10. package/dist/cjs/errors.d.ts +56 -0
  11. package/dist/cjs/errors.js +112 -0
  12. package/dist/cjs/files.d.ts +6 -0
  13. package/dist/cjs/files.js +45 -0
  14. package/dist/cjs/index.d.ts +16 -0
  15. package/dist/cjs/index.js +37 -0
  16. package/dist/cjs/instant-snap.d.ts +99 -0
  17. package/dist/cjs/instant-snap.js +205 -0
  18. package/dist/cjs/models/capture-file.d.ts +35 -0
  19. package/dist/cjs/models/capture-file.js +34 -0
  20. package/dist/cjs/models/capture-options.d.ts +144 -0
  21. package/dist/cjs/models/capture-options.js +2 -0
  22. package/dist/cjs/models/capture.d.ts +108 -0
  23. package/dist/cjs/models/capture.js +63 -0
  24. package/dist/cjs/models/defaults.d.ts +82 -0
  25. package/dist/cjs/models/defaults.js +46 -0
  26. package/dist/cjs/models/tokens.d.ts +92 -0
  27. package/dist/cjs/models/tokens.js +85 -0
  28. package/dist/cjs/models/trigger-result.d.ts +20 -0
  29. package/dist/cjs/models/trigger-result.js +13 -0
  30. package/dist/cjs/package.json +3 -0
  31. package/dist/cjs/transport.d.ts +55 -0
  32. package/dist/cjs/transport.js +206 -0
  33. package/dist/cjs/version.d.ts +2 -0
  34. package/dist/cjs/version.js +5 -0
  35. package/dist/cjs/webhooks/events.d.ts +128 -0
  36. package/dist/cjs/webhooks/events.js +13 -0
  37. package/dist/cjs/webhooks/parse-event.d.ts +10 -0
  38. package/dist/cjs/webhooks/parse-event.js +149 -0
  39. package/dist/cjs/webhooks/webhook.d.ts +26 -0
  40. package/dist/cjs/webhooks/webhook.js +78 -0
  41. package/dist/cjs/wire.d.ts +15 -0
  42. package/dist/cjs/wire.js +71 -0
  43. package/dist/esm/auto-schedule.d.ts +20 -0
  44. package/dist/esm/auto-schedule.js +30 -0
  45. package/dist/esm/client.d.ts +41 -0
  46. package/dist/esm/client.js +54 -0
  47. package/dist/esm/error-codes.d.ts +54 -0
  48. package/dist/esm/error-codes.js +91 -0
  49. package/dist/esm/errors.d.ts +56 -0
  50. package/dist/esm/errors.js +98 -0
  51. package/dist/esm/files.d.ts +6 -0
  52. package/dist/esm/files.js +41 -0
  53. package/dist/esm/index.d.ts +16 -0
  54. package/dist/esm/index.js +7 -0
  55. package/dist/esm/instant-snap.d.ts +99 -0
  56. package/dist/esm/instant-snap.js +201 -0
  57. package/dist/esm/models/capture-file.d.ts +35 -0
  58. package/dist/esm/models/capture-file.js +31 -0
  59. package/dist/esm/models/capture-options.d.ts +144 -0
  60. package/dist/esm/models/capture-options.js +1 -0
  61. package/dist/esm/models/capture.d.ts +108 -0
  62. package/dist/esm/models/capture.js +57 -0
  63. package/dist/esm/models/defaults.d.ts +82 -0
  64. package/dist/esm/models/defaults.js +42 -0
  65. package/dist/esm/models/tokens.d.ts +92 -0
  66. package/dist/esm/models/tokens.js +82 -0
  67. package/dist/esm/models/trigger-result.d.ts +20 -0
  68. package/dist/esm/models/trigger-result.js +10 -0
  69. package/dist/esm/transport.d.ts +55 -0
  70. package/dist/esm/transport.js +201 -0
  71. package/dist/esm/version.d.ts +2 -0
  72. package/dist/esm/version.js +2 -0
  73. package/dist/esm/webhooks/events.d.ts +128 -0
  74. package/dist/esm/webhooks/events.js +10 -0
  75. package/dist/esm/webhooks/parse-event.d.ts +10 -0
  76. package/dist/esm/webhooks/parse-event.js +146 -0
  77. package/dist/esm/webhooks/webhook.d.ts +26 -0
  78. package/dist/esm/webhooks/webhook.js +75 -0
  79. package/dist/esm/wire.d.ts +15 -0
  80. package/dist/esm/wire.js +66 -0
  81. package/package.json +68 -4
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseDefaults = parseDefaults;
4
+ exports.parseCatalog = parseCatalog;
5
+ const wire_js_1 = require("../wire.js");
6
+ function parseDefaults(wire) {
7
+ const defaults = {
8
+ specVersion: wire.spec_version,
9
+ responseType: wire.response_type,
10
+ mediaCategory: wire.media_category,
11
+ format: wire.format,
12
+ formats: wire.formats,
13
+ deviceCategory: wire.device_category,
14
+ emulatedDevice: wire.emulated_device,
15
+ viewport: wire.viewport && {
16
+ width: wire.viewport.width,
17
+ height: wire.viewport.height,
18
+ deviceScaleFactor: wire.viewport.device_scale_factor,
19
+ },
20
+ zoom: wire.zoom,
21
+ scrollMode: wire.scroll_mode,
22
+ autoHeight: wire.auto_height,
23
+ autoHeightMaxHeight: wire.auto_height_max_height,
24
+ autoHeightMaxHeightWebp: wire.auto_height_max_height_webp,
25
+ blockAds: wire.block_ads,
26
+ blockPopUps: wire.block_pop_ups,
27
+ pressEscape: wire.press_escape,
28
+ pageTimeoutMs: wire.page_timeout_ms,
29
+ scrollingDelayMs: wire.scrolling_delay_ms,
30
+ geolocation: wire.geolocation,
31
+ geolocationAccuracy: wire.geolocation_accuracy,
32
+ waitForCloudSync: wire.wait_for_cloud_sync,
33
+ timestampWatermark: (0, wire_js_1.camelCaseKeys)(wire.timestamp_watermark),
34
+ pageSource: (0, wire_js_1.camelCaseKeys)(wire.page_source),
35
+ limits: (0, wire_js_1.camelCaseKeys)(wire.limits),
36
+ leftToTheBrowser: wire.left_to_the_browser,
37
+ };
38
+ // Fields the API did not send stay absent rather than showing up as `undefined`.
39
+ return Object.fromEntries(Object.entries(defaults).filter(([, value]) => value !== undefined));
40
+ }
41
+ function parseCatalog(wire) {
42
+ return {
43
+ emulatedDevices: wire.emulated_devices,
44
+ proxyCountries: wire.proxy_countries,
45
+ };
46
+ }
@@ -0,0 +1,92 @@
1
+ /** Where a capture is in its life. */
2
+ export declare const CaptureStatus: {
3
+ readonly Pending: "pending";
4
+ /** An async capture accepted and waiting for a browser. */
5
+ readonly Queued: "queued";
6
+ /** An async capture a browser is taking now. */
7
+ readonly Running: "running";
8
+ readonly Succeeded: "succeeded";
9
+ /** The end state of `captureFile`: the file went back in the answer and is not kept. */
10
+ readonly Delivered: "delivered";
11
+ readonly Failed: "failed";
12
+ readonly TimedOut: "timed_out";
13
+ readonly Expired: "expired";
14
+ };
15
+ export type CaptureStatus = (typeof CaptureStatus)[keyof typeof CaptureStatus] | (string & {});
16
+ /** How the API answered a capture. */
17
+ export declare const ResponseType: {
18
+ readonly Json: "json";
19
+ readonly ByFormat: "by_format";
20
+ readonly CloudStorage: "cloud_storage";
21
+ };
22
+ export type ResponseType = (typeof ResponseType)[keyof typeof ResponseType] | (string & {});
23
+ export declare const MediaCategory: {
24
+ readonly Image: "image";
25
+ readonly Video: "video";
26
+ readonly Pdf: "pdf";
27
+ };
28
+ export type MediaCategory = (typeof MediaCategory)[keyof typeof MediaCategory] | (string & {});
29
+ /** The file format. It must belong to the capture's media category. */
30
+ export declare const CaptureFormat: {
31
+ readonly Png: "png";
32
+ readonly Jpeg: "jpeg";
33
+ readonly Webp: "webp";
34
+ readonly Tiff: "tiff";
35
+ readonly Mp4: "mp4";
36
+ readonly Webm: "webm";
37
+ readonly Mov: "mov";
38
+ readonly Gif: "gif";
39
+ readonly ImagePdf: "image_pdf";
40
+ readonly DocumentPdf: "document_pdf";
41
+ };
42
+ export type CaptureFormat = (typeof CaptureFormat)[keyof typeof CaptureFormat] | (string & {});
43
+ export declare const DeviceCategory: {
44
+ readonly Desktop: "desktop";
45
+ readonly Mobile: "mobile";
46
+ };
47
+ export type DeviceCategory = (typeof DeviceCategory)[keyof typeof DeviceCategory] | (string & {});
48
+ export declare const ScrollMode: {
49
+ /** Only what fits in the viewport. */
50
+ readonly Viewport: "viewport";
51
+ /** The full page. */
52
+ readonly Scroll: "scroll";
53
+ };
54
+ export type ScrollMode = (typeof ScrollMode)[keyof typeof ScrollMode] | (string & {});
55
+ export declare const WatermarkLocation: {
56
+ readonly Top: "top";
57
+ readonly Bottom: "bottom";
58
+ };
59
+ export type WatermarkLocation = (typeof WatermarkLocation)[keyof typeof WatermarkLocation] | (string & {});
60
+ export declare const PageSourceFormat: {
61
+ readonly Html: "html";
62
+ readonly Json: "json";
63
+ readonly Text: "text";
64
+ };
65
+ export type PageSourceFormat = (typeof PageSourceFormat)[keyof typeof PageSourceFormat] | (string & {});
66
+ /** Where an upload to your bucket stands. */
67
+ export declare const S3DeliveryStatus: {
68
+ readonly NotStarted: "not_started";
69
+ readonly InProgress: "in_progress";
70
+ readonly Completed: "completed";
71
+ readonly Failed: "failed";
72
+ };
73
+ export type S3DeliveryStatus = (typeof S3DeliveryStatus)[keyof typeof S3DeliveryStatus] | (string & {});
74
+ /** What a trigger-now call did. */
75
+ export declare const TriggerStatus: {
76
+ readonly Triggered: "triggered";
77
+ /** A capture was already queued or running; nothing new was started or charged. */
78
+ readonly AlreadyPending: "already_pending";
79
+ };
80
+ export type TriggerStatus = (typeof TriggerStatus)[keyof typeof TriggerStatus] | (string & {});
81
+ /** Who started a scheduled web page's pending capture. */
82
+ export declare const TriggeredBy: {
83
+ readonly Api: "api";
84
+ readonly Ui: "ui";
85
+ };
86
+ export type TriggeredBy = (typeof TriggeredBy)[keyof typeof TriggeredBy] | (string & {});
87
+ /** The product a webhook event comes from. */
88
+ export declare const Product: {
89
+ readonly InstantSnap: "instant_snap";
90
+ readonly AutoSchedule: "auto_schedule";
91
+ };
92
+ export type Product = (typeof Product)[keyof typeof Product] | (string & {});
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ // Each token is a set of named constants plus a type that also accepts any other string, so a value
3
+ // the API adds later still comes through while the known ones keep their autocompletion.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Product = exports.TriggeredBy = exports.TriggerStatus = exports.S3DeliveryStatus = exports.PageSourceFormat = exports.WatermarkLocation = exports.ScrollMode = exports.DeviceCategory = exports.CaptureFormat = exports.MediaCategory = exports.ResponseType = exports.CaptureStatus = void 0;
6
+ /** Where a capture is in its life. */
7
+ exports.CaptureStatus = {
8
+ Pending: "pending",
9
+ /** An async capture accepted and waiting for a browser. */
10
+ Queued: "queued",
11
+ /** An async capture a browser is taking now. */
12
+ Running: "running",
13
+ Succeeded: "succeeded",
14
+ /** The end state of `captureFile`: the file went back in the answer and is not kept. */
15
+ Delivered: "delivered",
16
+ Failed: "failed",
17
+ TimedOut: "timed_out",
18
+ Expired: "expired",
19
+ };
20
+ /** How the API answered a capture. */
21
+ exports.ResponseType = {
22
+ Json: "json",
23
+ ByFormat: "by_format",
24
+ CloudStorage: "cloud_storage",
25
+ };
26
+ exports.MediaCategory = {
27
+ Image: "image",
28
+ Video: "video",
29
+ Pdf: "pdf",
30
+ };
31
+ /** The file format. It must belong to the capture's media category. */
32
+ exports.CaptureFormat = {
33
+ Png: "png",
34
+ Jpeg: "jpeg",
35
+ Webp: "webp",
36
+ Tiff: "tiff",
37
+ Mp4: "mp4",
38
+ Webm: "webm",
39
+ Mov: "mov",
40
+ Gif: "gif",
41
+ ImagePdf: "image_pdf",
42
+ DocumentPdf: "document_pdf",
43
+ };
44
+ exports.DeviceCategory = {
45
+ Desktop: "desktop",
46
+ Mobile: "mobile",
47
+ };
48
+ exports.ScrollMode = {
49
+ /** Only what fits in the viewport. */
50
+ Viewport: "viewport",
51
+ /** The full page. */
52
+ Scroll: "scroll",
53
+ };
54
+ exports.WatermarkLocation = {
55
+ Top: "top",
56
+ Bottom: "bottom",
57
+ };
58
+ exports.PageSourceFormat = {
59
+ Html: "html",
60
+ Json: "json",
61
+ Text: "text",
62
+ };
63
+ /** Where an upload to your bucket stands. */
64
+ exports.S3DeliveryStatus = {
65
+ NotStarted: "not_started",
66
+ InProgress: "in_progress",
67
+ Completed: "completed",
68
+ Failed: "failed",
69
+ };
70
+ /** What a trigger-now call did. */
71
+ exports.TriggerStatus = {
72
+ Triggered: "triggered",
73
+ /** A capture was already queued or running; nothing new was started or charged. */
74
+ AlreadyPending: "already_pending",
75
+ };
76
+ /** Who started a scheduled web page's pending capture. */
77
+ exports.TriggeredBy = {
78
+ Api: "api",
79
+ Ui: "ui",
80
+ };
81
+ /** The product a webhook event comes from. */
82
+ exports.Product = {
83
+ InstantSnap: "instant_snap",
84
+ AutoSchedule: "auto_schedule",
85
+ };
@@ -0,0 +1,20 @@
1
+ import type { TriggerStatus, TriggeredBy } from "./tokens.js";
2
+ /** What `autoSchedule.triggerNow` did. The capture's outcome arrives later by webhook. */
3
+ export interface TriggerResult {
4
+ /** `triggered`, or `already_pending` when a capture was already queued or running. */
5
+ status: TriggerStatus;
6
+ webPageId: string;
7
+ triggeredBy: TriggeredBy;
8
+ /** When the capture was queued; for `already_pending`, when the earlier trigger was. */
9
+ triggeredAt: Date;
10
+ /** One sentence for a person. */
11
+ message: string;
12
+ }
13
+ export interface WireTriggerResult {
14
+ status: string;
15
+ web_page_id: string;
16
+ triggered_by: string;
17
+ triggered_at: string;
18
+ message: string;
19
+ }
20
+ export declare function parseTriggerResult(wire: WireTriggerResult): TriggerResult;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTriggerResult = parseTriggerResult;
4
+ const wire_js_1 = require("../wire.js");
5
+ function parseTriggerResult(wire) {
6
+ return {
7
+ status: wire.status,
8
+ webPageId: wire.web_page_id,
9
+ triggeredBy: wire.triggered_by,
10
+ triggeredAt: (0, wire_js_1.parseDate)(wire.triggered_at),
11
+ message: wire.message,
12
+ };
13
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,55 @@
1
+ /** Options every call accepts as its last argument. */
2
+ export interface RequestOptions {
3
+ /**
4
+ * Milliseconds the call may take, retries and the waits between them included. Defaults to the
5
+ * client's `captureTimeout` for captures and `timeout` for everything else.
6
+ */
7
+ timeout?: number;
8
+ /**
9
+ * Stops the call, and any retry it would make, as soon as it aborts; the call then rejects with
10
+ * the signal's reason. Once a file has started to arrive, cancel its stream instead.
11
+ */
12
+ signal?: AbortSignal;
13
+ }
14
+ /** Options the capture calls accept as their last argument. */
15
+ export interface CaptureRequestOptions extends RequestOptions {
16
+ /**
17
+ * Makes the capture safe to repeat within 24 hours. A random UUID is used when none is given;
18
+ * either way, every retry of this call sends the same key.
19
+ */
20
+ idempotencyKey?: string;
21
+ }
22
+ export interface TransportOptions {
23
+ /** Looked up on each request when left out, so a global fetch replaced later is used. */
24
+ fetch: typeof fetch | undefined;
25
+ maxRetries: number;
26
+ userAgent: string;
27
+ }
28
+ /** One API call, which may take several attempts. */
29
+ export interface Call {
30
+ method: "GET" | "POST";
31
+ url: string;
32
+ /** Left out for the one endpoint that needs no key. */
33
+ apiKey?: string;
34
+ /** A JSON body; left out for calls without one. */
35
+ body?: unknown;
36
+ idempotencyKey?: string;
37
+ /** The answer is a file rather than JSON. */
38
+ file?: boolean;
39
+ timeout: number;
40
+ signal?: AbortSignal | undefined;
41
+ }
42
+ /** Sends calls with the shared headers, timeouts and retries. */
43
+ export declare class Transport {
44
+ #private;
45
+ constructor(options: TransportOptions);
46
+ /** Makes the call and parses its JSON answer. */
47
+ json(call: Call): Promise<unknown>;
48
+ /** Makes the call and returns the answer as soon as its headers arrive, with the body unread. */
49
+ file(call: Call): Promise<Response>;
50
+ }
51
+ /**
52
+ * How long to wait before the next attempt: the API's hint when it gave one, capped at a minute,
53
+ * otherwise a short backoff with jitter so that clients refused together do not return together.
54
+ */
55
+ export declare function retryDelay(retryAfterSeconds: number | null, attempt: number): number;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transport = void 0;
4
+ exports.retryDelay = retryDelay;
5
+ const error_codes_js_1 = require("./error-codes.js");
6
+ const errors_js_1 = require("./errors.js");
7
+ const MAX_RETRY_AFTER_SECONDS = 60;
8
+ const BACKOFF_MS = [500, 1000];
9
+ const JITTER = 0.25;
10
+ // setTimeout fires at once for anything longer, so such a timeout means "no timeout".
11
+ const MAX_TIMER_MS = 2 ** 31 - 1;
12
+ /** Sends calls with the shared headers, timeouts and retries. */
13
+ class Transport {
14
+ #options;
15
+ constructor(options) {
16
+ this.#options = options;
17
+ }
18
+ /** Makes the call and parses its JSON answer. */
19
+ json(call) {
20
+ return this.#send(call, async (response) => {
21
+ const text = await response.text();
22
+ try {
23
+ return JSON.parse(text);
24
+ }
25
+ catch (cause) {
26
+ throw new errors_js_1.WebsiteScreenshotWorldError("The API answered with a body that is not JSON.", { httpStatus: response.status, rawBody: text, cause });
27
+ }
28
+ });
29
+ }
30
+ /** Makes the call and returns the answer as soon as its headers arrive, with the body unread. */
31
+ file(call) {
32
+ return this.#send(call, (response) => Promise.resolve(response));
33
+ }
34
+ async #send(call, read) {
35
+ const deadline = new Deadline(call.timeout, call.signal);
36
+ try {
37
+ const init = this.#requestInit(call, deadline.signal);
38
+ for (let attempt = 0;; attempt++) {
39
+ const outcome = await this.#attempt(call.url, init, read, deadline);
40
+ if (outcome.done)
41
+ return outcome.value;
42
+ const delay = retryDelay(outcome.error.retryAfterSeconds, attempt);
43
+ const retry = outcome.retryable &&
44
+ attempt < this.#options.maxRetries &&
45
+ deadline.allows(delay);
46
+ if (!retry)
47
+ throw outcome.error;
48
+ await deadline.sleep(delay);
49
+ }
50
+ }
51
+ finally {
52
+ deadline.end();
53
+ }
54
+ }
55
+ async #attempt(url, init, read, deadline) {
56
+ try {
57
+ const fetch = this.#options.fetch ?? globalThis.fetch;
58
+ const response = await fetch(url, init);
59
+ if (response.ok)
60
+ return { done: true, value: await read(response) };
61
+ const error = await refusalFrom(response);
62
+ return {
63
+ done: false,
64
+ error,
65
+ retryable: error_codes_js_1.RETRYABLE_CODES.has(error.errorCode ?? ""),
66
+ };
67
+ }
68
+ catch (cause) {
69
+ deadline.throwIfStopped();
70
+ if (cause instanceof errors_js_1.WebsiteScreenshotWorldError)
71
+ throw cause;
72
+ const error = new errors_js_1.ConnectionError(`The request to ${new URL(url).origin} failed before an answer came back.`, { cause });
73
+ return { done: false, error, retryable: true };
74
+ }
75
+ }
76
+ #requestInit(call, signal) {
77
+ const headers = {
78
+ "User-Agent": this.#options.userAgent,
79
+ };
80
+ if (call.apiKey !== undefined)
81
+ headers["X-Api-Key"] = call.apiKey;
82
+ if (call.file !== true)
83
+ headers.Accept = "application/json";
84
+ if (call.body !== undefined)
85
+ headers["Content-Type"] = "application/json";
86
+ if (call.idempotencyKey !== undefined) {
87
+ headers["Idempotency-Key"] = call.idempotencyKey;
88
+ }
89
+ /* Redirects are never followed (code-review plan 7.4, 28 Oct 2026): fetch would send the
90
+ X-Api-Key to wherever a 3xx pointed. The API never redirects, and neither does the download
91
+ link, so a 3xx is answered as the refusal it is - "manual", not "error", which would surface
92
+ as a ConnectionError and be retried. */
93
+ const init = {
94
+ method: call.method,
95
+ headers,
96
+ signal,
97
+ redirect: "manual",
98
+ };
99
+ if (call.body !== undefined)
100
+ init.body = JSON.stringify(call.body);
101
+ return init;
102
+ }
103
+ }
104
+ exports.Transport = Transport;
105
+ /**
106
+ * How long to wait before the next attempt: the API's hint when it gave one, capped at a minute,
107
+ * otherwise a short backoff with jitter so that clients refused together do not return together.
108
+ */
109
+ function retryDelay(retryAfterSeconds, attempt) {
110
+ if (retryAfterSeconds !== null) {
111
+ return Math.min(retryAfterSeconds, MAX_RETRY_AFTER_SECONDS) * 1000;
112
+ }
113
+ const base = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)] ?? 1000;
114
+ return base * (1 + Math.random() * JITTER);
115
+ }
116
+ async function refusalFrom(response) {
117
+ const rawBody = await response.text();
118
+ const body = errorBodyFrom(rawBody);
119
+ const errorCode = body?.error_code ??
120
+ error_codes_js_1.FALLBACK_CODE_BY_STATUS[response.status] ??
121
+ error_codes_js_1.FALLBACK_CODE_FOR_OTHER_STATUS;
122
+ const message = typeof body?.error_message === "string"
123
+ ? body.error_message
124
+ : `The API answered with HTTP ${String(response.status)} and no error details.`;
125
+ const retryAfterSeconds = typeof body?.retry_after_seconds === "number"
126
+ ? body.retry_after_seconds
127
+ : retryAfterHeader(response.headers);
128
+ return (0, errors_js_1.errorFor)(errorCode, message, {
129
+ httpStatus: response.status,
130
+ retryAfterSeconds,
131
+ rawBody,
132
+ });
133
+ }
134
+ function errorBodyFrom(text) {
135
+ let value;
136
+ try {
137
+ value = JSON.parse(text);
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ const isErrorBody = typeof value === "object" &&
143
+ value !== null &&
144
+ typeof value.error_code === "string";
145
+ return isErrorBody ? value : null;
146
+ }
147
+ function retryAfterHeader(headers) {
148
+ const value = headers.get("retry-after")?.trim();
149
+ return value !== undefined && /^\d+$/.test(value) ? Number(value) : null;
150
+ }
151
+ /** Ends a call when its timeout passes or the caller's signal aborts, whichever comes first. */
152
+ class Deadline {
153
+ #controller = new AbortController();
154
+ #expiresAt;
155
+ #timer;
156
+ #callerSignal;
157
+ #onCallerAbort = () => {
158
+ this.#controller.abort(this.#callerSignal?.reason);
159
+ };
160
+ constructor(timeout, callerSignal) {
161
+ callerSignal?.throwIfAborted();
162
+ this.#callerSignal = callerSignal;
163
+ this.#expiresAt = performance.now() + timeout;
164
+ callerSignal?.addEventListener("abort", this.#onCallerAbort, {
165
+ once: true,
166
+ });
167
+ if (timeout <= MAX_TIMER_MS) {
168
+ this.#timer = setTimeout(() => {
169
+ this.#controller.abort(new errors_js_1.ConnectionError(`The call did not finish within ${String(timeout / 1000)} seconds.`));
170
+ }, timeout);
171
+ }
172
+ }
173
+ get signal() {
174
+ return this.#controller.signal;
175
+ }
176
+ /** Whether waiting `milliseconds` still leaves time for another attempt. */
177
+ allows(milliseconds) {
178
+ return performance.now() + milliseconds < this.#expiresAt;
179
+ }
180
+ /** Throws the reason the call was stopped, if it was. */
181
+ throwIfStopped() {
182
+ this.#controller.signal.throwIfAborted();
183
+ }
184
+ sleep(milliseconds) {
185
+ const signal = this.#controller.signal;
186
+ return new Promise((resolve, reject) => {
187
+ if (signal.aborted) {
188
+ reject(signal.reason);
189
+ return;
190
+ }
191
+ const onAbort = () => {
192
+ clearTimeout(timer);
193
+ reject(signal.reason);
194
+ };
195
+ const timer = setTimeout(() => {
196
+ signal.removeEventListener("abort", onAbort);
197
+ resolve();
198
+ }, milliseconds);
199
+ signal.addEventListener("abort", onAbort, { once: true });
200
+ });
201
+ }
202
+ end() {
203
+ clearTimeout(this.#timer);
204
+ this.#callerSignal?.removeEventListener("abort", this.#onCallerAbort);
205
+ }
206
+ }
@@ -0,0 +1,2 @@
1
+ /** The version of this library. Kept equal to `version` in package.json. */
2
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ /** The version of this library. Kept equal to `version` in package.json. */
5
+ exports.VERSION = "0.1.0";
@@ -0,0 +1,128 @@
1
+ import type { CaptureFields, PageSource, S3Delivery } from "../models/capture.js";
2
+ import type { CaptureFormat, MediaCategory, PageSourceFormat, Product } from "../models/tokens.js";
3
+ /** The event types this version knows. */
4
+ export declare const WebhookEventType: {
5
+ readonly CaptureSucceeded: "capture.succeeded";
6
+ readonly CaptureFailed: "capture.failed";
7
+ readonly DeliveryCompleted: "delivery.completed";
8
+ readonly CaptureChanged: "capture.changed";
9
+ readonly SchedulePaused: "schedule.paused";
10
+ readonly SitemapRunCompleted: "sitemap_run.completed";
11
+ readonly Ping: "ping";
12
+ };
13
+ export type WebhookEventType = (typeof WebhookEventType)[keyof typeof WebhookEventType];
14
+ interface EventEnvelope<Type extends string, EventProduct, Data> {
15
+ /** The delivery id; the same on every retry of this delivery. */
16
+ id: string;
17
+ type: Type;
18
+ createdAt: Date;
19
+ product: EventProduct;
20
+ data: Data;
21
+ }
22
+ /** An Instant Snap capture as its webhook carries it: no download links or access key. */
23
+ export interface InstantSnapCaptureData extends CaptureFields {
24
+ pageSource: Omit<PageSource, "downloadUrl"> | null;
25
+ s3Delivery: Omit<S3Delivery, "accessKeyId"> | null;
26
+ }
27
+ /** A scheduled web page's capture. */
28
+ export interface ScheduledCapture {
29
+ id: string;
30
+ /** The web page's id; `autoSchedule.triggerNow` calls it `webPageId`. */
31
+ scheduleId: string;
32
+ scheduleName: string;
33
+ url: string;
34
+ status: string;
35
+ mediaCategory: MediaCategory;
36
+ format: CaptureFormat | null;
37
+ pageHttpStatus: number | null;
38
+ capturedAt: Date | null;
39
+ finalUrl: string | null;
40
+ mediaSha256: string | null;
41
+ pageSource: {
42
+ format: PageSourceFormat;
43
+ sha256: string | null;
44
+ } | null;
45
+ /** Set when the capture was part of a sitemap run. */
46
+ sitemapRunId: string | null;
47
+ }
48
+ /** One place a scheduled capture was delivered to. */
49
+ export interface Destination {
50
+ slot: number;
51
+ name: string;
52
+ provider: string;
53
+ status: string;
54
+ completedAt: Date | null;
55
+ }
56
+ export interface DeliveryCompletedData extends ScheduledCapture {
57
+ destinations: Destination[];
58
+ }
59
+ export interface CaptureChangedData extends ScheduledCapture {
60
+ /** How much of the image changed since the capture it was compared with. */
61
+ changePercent: number | null;
62
+ comparedToCaptureId: string | null;
63
+ changeImageAvailable: boolean;
64
+ }
65
+ export interface SchedulePausedData {
66
+ scheduleId: string;
67
+ scheduleName: string;
68
+ url: string;
69
+ /** Why the schedule stopped, such as `quota_exhausted`. */
70
+ reason: string;
71
+ pausedAt: Date | null;
72
+ }
73
+ export interface SitemapRunCompletedData {
74
+ sitemapRunId: string;
75
+ scheduleId: string;
76
+ scheduleName: string;
77
+ sitemapUrl: string;
78
+ status: string;
79
+ runAt: Date | null;
80
+ completedAt: Date | null;
81
+ pagesListed: number | null;
82
+ pagesMatched: number | null;
83
+ pagesQueued: number | null;
84
+ pagesSucceeded: number | null;
85
+ pagesFailed: number | null;
86
+ pagesOverLimit: number | null;
87
+ pagesOverQuota: number | null;
88
+ message: string | null;
89
+ }
90
+ export interface PingData {
91
+ endpointId: string;
92
+ endpointName: string;
93
+ message: string;
94
+ sentAt: Date | null;
95
+ }
96
+ type CaptureOutcome = "capture.succeeded" | "capture.failed";
97
+ /** An Instant Snap capture finished. */
98
+ export type InstantSnapCaptureEvent = EventEnvelope<CaptureOutcome, "instant_snap", InstantSnapCaptureData>;
99
+ /** A scheduled web page's capture finished. */
100
+ export type AutoScheduleCaptureEvent = EventEnvelope<CaptureOutcome, "auto_schedule", ScheduledCapture>;
101
+ /** A scheduled capture reached all of its destinations. */
102
+ export type DeliveryCompletedEvent = EventEnvelope<"delivery.completed", "auto_schedule", DeliveryCompletedData>;
103
+ /** A scheduled capture differs from the one before it. */
104
+ export type CaptureChangedEvent = EventEnvelope<"capture.changed", "auto_schedule", CaptureChangedData>;
105
+ /** A schedule stopped running. */
106
+ export type SchedulePausedEvent = EventEnvelope<"schedule.paused", "auto_schedule", SchedulePausedData>;
107
+ /** Every page of a sitemap run has been captured or skipped. */
108
+ export type SitemapRunCompletedEvent = EventEnvelope<"sitemap_run.completed", "auto_schedule", SitemapRunCompletedData>;
109
+ /** A test delivery sent from the dashboard, by either product. */
110
+ export type PingEvent = EventEnvelope<"ping", Product, PingData>;
111
+ /**
112
+ * An event type this version does not know yet, with its payload as sent in `rawData`.
113
+ *
114
+ * Its `data` is never set: that keeps `event.data` precisely typed after `switch (event.type)`
115
+ * narrows to a known type, since TypeScript cannot exclude one string from a `string` type.
116
+ */
117
+ export interface UnknownWebhookEvent {
118
+ id: string;
119
+ type: string;
120
+ createdAt: Date;
121
+ product: string;
122
+ data: never;
123
+ /** The event's `data` exactly as sent, with snake_case keys. */
124
+ rawData: Record<string, unknown>;
125
+ }
126
+ /** Every event `Webhook.verify` returns, told apart by `type` and, for captures, `product`. */
127
+ export type WebhookEvent = InstantSnapCaptureEvent | AutoScheduleCaptureEvent | DeliveryCompletedEvent | CaptureChangedEvent | SchedulePausedEvent | SitemapRunCompletedEvent | PingEvent | UnknownWebhookEvent;
128
+ export {};
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebhookEventType = void 0;
4
+ /** The event types this version knows. */
5
+ exports.WebhookEventType = {
6
+ CaptureSucceeded: "capture.succeeded",
7
+ CaptureFailed: "capture.failed",
8
+ DeliveryCompleted: "delivery.completed",
9
+ CaptureChanged: "capture.changed",
10
+ SchedulePaused: "schedule.paused",
11
+ SitemapRunCompleted: "sitemap_run.completed",
12
+ Ping: "ping",
13
+ };