@fastpix/fastpix-node 2.0.7 → 2.0.8
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/README.md +92 -4
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/lib/config.d.ts +13 -2
- package/dist/commonjs/lib/config.d.ts.map +1 -1
- package/dist/commonjs/lib/config.js +2 -2
- package/dist/commonjs/lib/config.js.map +1 -1
- package/dist/commonjs/lib/env.d.ts +1 -0
- package/dist/commonjs/lib/env.d.ts.map +1 -1
- package/dist/commonjs/lib/env.js +1 -0
- package/dist/commonjs/lib/env.js.map +1 -1
- package/dist/commonjs/lib/sdks.d.ts.map +1 -1
- package/dist/commonjs/lib/sdks.js +9 -1
- package/dist/commonjs/lib/sdks.js.map +1 -1
- package/dist/commonjs/sdk/sdk.d.ts +3 -0
- package/dist/commonjs/sdk/sdk.d.ts.map +1 -1
- package/dist/commonjs/sdk/sdk.js +11 -0
- package/dist/commonjs/sdk/sdk.js.map +1 -1
- package/dist/commonjs/sdk/webhooks.d.ts +151 -0
- package/dist/commonjs/sdk/webhooks.d.ts.map +1 -0
- package/dist/commonjs/sdk/webhooks.js +139 -0
- package/dist/commonjs/sdk/webhooks.js.map +1 -0
- package/dist/commonjs/types/primitives.d.ts +1 -1
- package/dist/commonjs/types/primitives.d.ts.map +1 -1
- package/dist/commonjs/types/primitives.js +7 -3
- package/dist/commonjs/types/primitives.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/config.d.ts +13 -2
- package/dist/esm/lib/config.d.ts.map +1 -1
- package/dist/esm/lib/config.js +2 -2
- package/dist/esm/lib/config.js.map +1 -1
- package/dist/esm/lib/env.d.ts +1 -0
- package/dist/esm/lib/env.d.ts.map +1 -1
- package/dist/esm/lib/env.js +1 -0
- package/dist/esm/lib/env.js.map +1 -1
- package/dist/esm/lib/sdks.d.ts.map +1 -1
- package/dist/esm/lib/sdks.js +9 -1
- package/dist/esm/lib/sdks.js.map +1 -1
- package/dist/esm/sdk/sdk.d.ts +3 -0
- package/dist/esm/sdk/sdk.d.ts.map +1 -1
- package/dist/esm/sdk/sdk.js +11 -0
- package/dist/esm/sdk/sdk.js.map +1 -1
- package/dist/esm/sdk/webhooks.d.ts +151 -0
- package/dist/esm/sdk/webhooks.d.ts.map +1 -0
- package/dist/esm/sdk/webhooks.js +134 -0
- package/dist/esm/sdk/webhooks.js.map +1 -0
- package/dist/esm/types/primitives.d.ts +1 -1
- package/dist/esm/types/primitives.d.ts.map +1 -1
- package/dist/esm/types/primitives.js +7 -3
- package/dist/esm/types/primitives.js.map +1 -1
- package/examples/webhooksServer.example.ts +93 -0
- package/package.json +3 -4
- package/src/index.ts +1 -0
- package/src/lib/config.ts +14 -2
- package/src/lib/env.ts +2 -2
- package/src/lib/sdks.ts +9 -1
- package/src/sdk/sdk.ts +8 -0
- package/src/sdk/webhooks.ts +329 -0
- package/src/types/primitives.ts +12 -7
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ClientSDK } from "../lib/sdks.js";
|
|
2
|
+
import type { CreateLiveStreamResponseDTO } from "../models/createlivestreamresponsedto.js";
|
|
3
|
+
import type { Media } from "../models/media.js";
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
5
|
+
/**
|
|
6
|
+
* The raw, unparsed webhook request body. Always pass the bytes exactly as they
|
|
7
|
+
* arrived on the wire — verification fails if the body was re-serialized.
|
|
8
|
+
*/
|
|
9
|
+
export type WebhookRawBody = string | Buffer | Uint8Array;
|
|
10
|
+
/**
|
|
11
|
+
* Inbound request headers. Accepts a WHATWG `Headers` instance or a plain object
|
|
12
|
+
* such as Node's `IncomingHttpHeaders` (values may be `string` or `string[]`).
|
|
13
|
+
*/
|
|
14
|
+
export type WebhookHeaders = Headers | Record<string, string | string[] | undefined>;
|
|
15
|
+
/** The `object` envelope field: the resource this event is about. */
|
|
16
|
+
export interface WebhookEventObject {
|
|
17
|
+
/** The resource type, e.g. "media" or "live-stream". */
|
|
18
|
+
type: string;
|
|
19
|
+
/** The affected resource id (equal to `data.id`). */
|
|
20
|
+
id: string;
|
|
21
|
+
}
|
|
22
|
+
/** The `workspace` envelope field. */
|
|
23
|
+
export interface WebhookWorkspace {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
/** Every webhook event type whose `data` payload is a {@link Media}. */
|
|
28
|
+
export type MediaWebhookEventType = "video.media.created" | "video.media.updated" | "video.media.ready" | "video.media.failed" | "video.media.deleted" | "video.media.track.created" | "video.media.track.ready" | "video.media.track.updated" | "video.media.track.deleted" | "video.media.upload.cancelled" | "video.media.subtitle.generated.ready" | "video.media.source.ready" | "video.media.source.deleted" | "video.media.mp4Support.ready";
|
|
29
|
+
/**
|
|
30
|
+
* Every webhook event type whose `data` payload is a
|
|
31
|
+
* {@link CreateLiveStreamResponseDTO}.
|
|
32
|
+
*/
|
|
33
|
+
export type LiveStreamWebhookEventType = "video.live_stream.created" | "video.live_stream.updated" | "video.live_stream.deleted" | "video.live_stream.simulcast_target.updated" | "video.live_stream.simulcast_target.deleted";
|
|
34
|
+
/** Union of all known FastPix webhook event `type` strings. */
|
|
35
|
+
export type WebhookEventType = MediaWebhookEventType | LiveStreamWebhookEventType;
|
|
36
|
+
/**
|
|
37
|
+
* A verified FastPix webhook event.
|
|
38
|
+
*
|
|
39
|
+
* Route on `type`, dedupe on the top-level `id` (the idempotency key — NOT
|
|
40
|
+
* `object.id`), and read the affected resource id from `object.id`
|
|
41
|
+
* (== `data.id`). `data` carries the full entity payload.
|
|
42
|
+
*
|
|
43
|
+
* Two type parameters, both with sensible defaults:
|
|
44
|
+
* - `TData` — the shape of `data` (defaults to a loose record).
|
|
45
|
+
* - `TType` — the literal `type` (defaults to any `string`).
|
|
46
|
+
*
|
|
47
|
+
* Most callers don't use these directly: {@link Webhooks.unwrap} returns the
|
|
48
|
+
* discriminated {@link FastpixWebhookEvent} union, which narrows both for you.
|
|
49
|
+
*/
|
|
50
|
+
export interface WebhookEvent<TData = Record<string, unknown>, TType extends string = string> {
|
|
51
|
+
/** Routing key, e.g. "video.media.updated". */
|
|
52
|
+
type: TType;
|
|
53
|
+
/** The resource this event concerns. */
|
|
54
|
+
object: WebhookEventObject;
|
|
55
|
+
/** Event id — the idempotency key. Dedupe on this. */
|
|
56
|
+
id: string;
|
|
57
|
+
/** The workspace that produced the event. */
|
|
58
|
+
workspace: WebhookWorkspace;
|
|
59
|
+
/** Coarse status string, e.g. "media_created". */
|
|
60
|
+
status: string;
|
|
61
|
+
/** Full entity payload (has its own id/status/playbackIds/tracks/...). */
|
|
62
|
+
data: TData;
|
|
63
|
+
/** ISO-8601 timestamp string. */
|
|
64
|
+
createdAt: string;
|
|
65
|
+
/** Delivery attempt metadata. */
|
|
66
|
+
attempts: unknown[];
|
|
67
|
+
}
|
|
68
|
+
/** A `video.media.*` event. `data` is a {@link Media}. */
|
|
69
|
+
export type MediaWebhookEvent = WebhookEvent<Media, MediaWebhookEventType>;
|
|
70
|
+
/**
|
|
71
|
+
* A `video.live_stream.*` event. `data` is a
|
|
72
|
+
* {@link CreateLiveStreamResponseDTO}.
|
|
73
|
+
*/
|
|
74
|
+
export type LiveStreamWebhookEvent = WebhookEvent<CreateLiveStreamResponseDTO, LiveStreamWebhookEventType>;
|
|
75
|
+
/**
|
|
76
|
+
* The discriminated union of every known FastPix webhook event. This is what
|
|
77
|
+
* {@link Webhooks.unwrap} returns: `switch (event.type)` narrows `event.data`
|
|
78
|
+
* to the right payload type automatically.
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* const event = fastpix.webhooks.unwrap(body, headers);
|
|
82
|
+
* switch (event.type) {
|
|
83
|
+
* case "video.media.ready":
|
|
84
|
+
* event.data.playbackIds; // ✅ typed as Media
|
|
85
|
+
* break;
|
|
86
|
+
* case "video.live_stream.created":
|
|
87
|
+
* event.data.streamKey; // ✅ typed as CreateLiveStreamResponseDTO
|
|
88
|
+
* break;
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export type FastpixWebhookEvent = MediaWebhookEvent | LiveStreamWebhookEvent;
|
|
93
|
+
/**
|
|
94
|
+
* Thrown when a webhook cannot be verified. Catch this to return `400` (bad
|
|
95
|
+
* signature / malformed input) versus `500` (unexpected server error).
|
|
96
|
+
*/
|
|
97
|
+
export declare class WebhookVerificationError extends Error {
|
|
98
|
+
constructor(message: string, options?: {
|
|
99
|
+
cause?: unknown;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Webhooks resource — verifies and unwraps inbound FastPix webhook deliveries.
|
|
104
|
+
*
|
|
105
|
+
* Accessed as `fastpix.webhooks`. The signing secret defaults to the client's
|
|
106
|
+
* `webhookSecret` option (which itself falls back to
|
|
107
|
+
* `process.env.FASTPIX_WEBHOOK_SECRET`), and can be overridden per call.
|
|
108
|
+
*/
|
|
109
|
+
export declare class Webhooks extends ClientSDK {
|
|
110
|
+
/**
|
|
111
|
+
* Verify the signature and return the parsed event.
|
|
112
|
+
*
|
|
113
|
+
* This is the single function most integrations need: hand it the raw body and
|
|
114
|
+
* request headers and it returns a typed {@link WebhookEvent}, throwing
|
|
115
|
+
* {@link WebhookVerificationError} if anything is wrong.
|
|
116
|
+
*
|
|
117
|
+
* @param body The raw, unparsed request body (string or Buffer/Uint8Array).
|
|
118
|
+
* @param headers The inbound request headers.
|
|
119
|
+
* @param secret Optional override for the webhook signing secret. Defaults to
|
|
120
|
+
* the client's `webhookSecret` option.
|
|
121
|
+
* @returns The verified, parsed webhook event.
|
|
122
|
+
*/
|
|
123
|
+
unwrap(body: WebhookRawBody, headers: WebhookHeaders, secret?: string | null): FastpixWebhookEvent;
|
|
124
|
+
/**
|
|
125
|
+
* Escape hatch: supply your own `data` shape (e.g. for an event type the SDK
|
|
126
|
+
* doesn't model yet) by passing an explicit type argument.
|
|
127
|
+
*/
|
|
128
|
+
unwrap<TData>(body: WebhookRawBody, headers: WebhookHeaders, secret?: string | null): WebhookEvent<TData>;
|
|
129
|
+
/**
|
|
130
|
+
* Verify the signature without parsing. Throws {@link WebhookVerificationError}
|
|
131
|
+
* on any failure (missing secret, parsed-instead-of-raw body, missing header,
|
|
132
|
+
* or signature mismatch). Returns `void` on success.
|
|
133
|
+
*/
|
|
134
|
+
verifySignature(body: WebhookRawBody, headers: WebhookHeaders, secret?: string | null): void;
|
|
135
|
+
/**
|
|
136
|
+
* Compute the base64 HMAC-SHA256 of the raw payload using the base64-decoded
|
|
137
|
+
* secret as the key. Matches FastPix's signing scheme exactly.
|
|
138
|
+
*/
|
|
139
|
+
private computeSignature;
|
|
140
|
+
/**
|
|
141
|
+
* Constant-time string comparison. Performs a length check first (lengths are
|
|
142
|
+
* not secret), then delegates to `crypto.timingSafeEqual`.
|
|
143
|
+
*/
|
|
144
|
+
private timingSafeEqual;
|
|
145
|
+
/**
|
|
146
|
+
* Read the `FastPix-Signature` header case-insensitively. If a framework hands
|
|
147
|
+
* back an array of values, the first one is used.
|
|
148
|
+
*/
|
|
149
|
+
private extractSignature;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/sdk/webhooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FastPix webhook verification and unwrapping.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the other files under `src/sdk/`, this resource is hand-written rather
|
|
5
|
+
* than code-generated, but it follows the exact same conventions: it extends the
|
|
6
|
+
* shared `ClientSDK` base class and reads its configuration from `this._options`
|
|
7
|
+
* (here, the webhook signing secret resolved by the base constructor).
|
|
8
|
+
*
|
|
9
|
+
* Signature scheme (verified against a live FastPix delivery):
|
|
10
|
+
* - The signature travels in the `FastPix-Signature` header (read lowercase as
|
|
11
|
+
* `fastpix-signature`). It is a SINGLE base64 value — there is no `t=`/`v1=`
|
|
12
|
+
* structure to parse. A real digest looks like
|
|
13
|
+
* "oeDnZHgmhQ3UJ7qUw7uJAzo0O3Dbulfr0w89eoy0lVA=" (44 base64 chars => 32-byte
|
|
14
|
+
* HMAC-SHA256 output).
|
|
15
|
+
* - The signing secret is itself base64-encoded; decode it with
|
|
16
|
+
* `Buffer.from(secret, "base64")` and use those raw bytes as the HMAC key.
|
|
17
|
+
* - The HMAC-SHA256 is computed over the RAW REQUEST BODY ONLY (no timestamp
|
|
18
|
+
* prefix) and the digest is encoded as base64 (not hex).
|
|
19
|
+
* - No timestamp is signed, so there is no replay/tolerance window to enforce.
|
|
20
|
+
* Callers MUST instead dedupe on the top-level event `id` for idempotency.
|
|
21
|
+
*/
|
|
22
|
+
/// <reference types="node" />
|
|
23
|
+
import { createHmac, timingSafeEqual as nodeTimingSafeEqual } from "node:crypto";
|
|
24
|
+
import { ClientSDK } from "../lib/sdks.js";
|
|
25
|
+
import { Buffer } from "node:buffer";
|
|
26
|
+
/**
|
|
27
|
+
* Thrown when a webhook cannot be verified. Catch this to return `400` (bad
|
|
28
|
+
* signature / malformed input) versus `500` (unexpected server error).
|
|
29
|
+
*/
|
|
30
|
+
export class WebhookVerificationError extends Error {
|
|
31
|
+
constructor(message, options) {
|
|
32
|
+
super(message, options);
|
|
33
|
+
this.name = "WebhookVerificationError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Header name we read, always lower-cased per the HTTP spec. */
|
|
37
|
+
const SIGNATURE_HEADER = "fastpix-signature";
|
|
38
|
+
/**
|
|
39
|
+
* Webhooks resource — verifies and unwraps inbound FastPix webhook deliveries.
|
|
40
|
+
*
|
|
41
|
+
* Accessed as `fastpix.webhooks`. The signing secret defaults to the client's
|
|
42
|
+
* `webhookSecret` option (which itself falls back to
|
|
43
|
+
* `process.env.FASTPIX_WEBHOOK_SECRET`), and can be overridden per call.
|
|
44
|
+
*/
|
|
45
|
+
export class Webhooks extends ClientSDK {
|
|
46
|
+
unwrap(body, headers, secret) {
|
|
47
|
+
this.verifySignature(body, headers, secret);
|
|
48
|
+
const raw = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
throw new WebhookVerificationError("Webhook signature verified but the body is not valid JSON.", { cause });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Verify the signature without parsing. Throws {@link WebhookVerificationError}
|
|
58
|
+
* on any failure (missing secret, parsed-instead-of-raw body, missing header,
|
|
59
|
+
* or signature mismatch). Returns `void` on success.
|
|
60
|
+
*/
|
|
61
|
+
verifySignature(body, headers, secret) {
|
|
62
|
+
// 1. Resolve the signing secret. Explicit arg wins, then the client option.
|
|
63
|
+
const signingSecret = secret ?? this._options.webhookSecret;
|
|
64
|
+
if (!signingSecret) {
|
|
65
|
+
throw new WebhookVerificationError("Missing webhook secret. Pass one to unwrap()/verifySignature(), set the "
|
|
66
|
+
+ "`webhookSecret` client option, or set the FASTPIX_WEBHOOK_SECRET "
|
|
67
|
+
+ "environment variable.");
|
|
68
|
+
}
|
|
69
|
+
// 2. The body MUST be the raw bytes. A plain object means the caller already
|
|
70
|
+
// JSON.parsed it, which destroys the exact bytes the signature covers.
|
|
71
|
+
if (typeof body !== "string"
|
|
72
|
+
&& !Buffer.isBuffer(body)
|
|
73
|
+
&& !(body instanceof Uint8Array)) {
|
|
74
|
+
throw new WebhookVerificationError("Webhook body must be the raw request payload as a string or Buffer. It "
|
|
75
|
+
+ "looks like the body was already parsed into an object — configure your "
|
|
76
|
+
+ "framework to expose the raw body (e.g. express.raw({ type: "
|
|
77
|
+
+ "'application/json' })).");
|
|
78
|
+
}
|
|
79
|
+
// 3. Pull the signature header (single base64 value, no `t=`/`v1=` parts).
|
|
80
|
+
const provided = this.extractSignature(headers);
|
|
81
|
+
if (!provided) {
|
|
82
|
+
throw new WebhookVerificationError(`Missing "FastPix-Signature" header on the webhook request.`);
|
|
83
|
+
}
|
|
84
|
+
// 4. Compute the expected signature over the raw body and compare in
|
|
85
|
+
// constant time.
|
|
86
|
+
const expected = this.computeSignature(body, signingSecret);
|
|
87
|
+
if (!this.timingSafeEqual(provided, expected)) {
|
|
88
|
+
throw new WebhookVerificationError("Webhook signature mismatch. The payload may have been tampered with, or "
|
|
89
|
+
+ "a different signing secret was used.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compute the base64 HMAC-SHA256 of the raw payload using the base64-decoded
|
|
94
|
+
* secret as the key. Matches FastPix's signing scheme exactly.
|
|
95
|
+
*/
|
|
96
|
+
computeSignature(payload, secret) {
|
|
97
|
+
const key = Buffer.from(secret, "base64");
|
|
98
|
+
return createHmac("sha256", key).update(payload).digest("base64");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Constant-time string comparison. Performs a length check first (lengths are
|
|
102
|
+
* not secret), then delegates to `crypto.timingSafeEqual`.
|
|
103
|
+
*/
|
|
104
|
+
timingSafeEqual(a, b) {
|
|
105
|
+
const ab = Buffer.from(a);
|
|
106
|
+
const bb = Buffer.from(b);
|
|
107
|
+
if (ab.length !== bb.length) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return nodeTimingSafeEqual(ab, bb);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Read the `FastPix-Signature` header case-insensitively. If a framework hands
|
|
114
|
+
* back an array of values, the first one is used.
|
|
115
|
+
*/
|
|
116
|
+
extractSignature(headers) {
|
|
117
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
118
|
+
return headers.get(SIGNATURE_HEADER) ?? undefined;
|
|
119
|
+
}
|
|
120
|
+
const record = headers;
|
|
121
|
+
// Fast path: HTTP servers (e.g. Node) already lower-case header names.
|
|
122
|
+
let value = record[SIGNATURE_HEADER];
|
|
123
|
+
if (value === undefined) {
|
|
124
|
+
for (const key of Object.keys(record)) {
|
|
125
|
+
if (key.toLowerCase() === SIGNATURE_HEADER) {
|
|
126
|
+
value = record[key];
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return Array.isArray(value) ? value[0] : value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../../src/sdk/webhooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,6 @@ export declare function bigint(): z.ZodMiniType<bigint>;
|
|
|
6
6
|
export declare function date(): z.ZodMiniType<Date>;
|
|
7
7
|
export declare function literal<T extends string | number | boolean>(value: T): z.ZodMiniType<T>;
|
|
8
8
|
export declare function literalBigInt<T extends bigint>(value: T): z.ZodMiniType<T>;
|
|
9
|
-
export declare function optional<T extends z.ZodMiniType>(t: T): z.ZodMiniUnion<readonly [z.
|
|
9
|
+
export declare function optional<T extends z.ZodMiniType>(t: T): z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniPipe<z.ZodMiniNull, z.ZodMiniTransform<never, null>>, T]>>;
|
|
10
10
|
export declare function nullable<T extends z.ZodMiniType>(t: T): z.ZodMiniUnion<readonly [z.ZodMiniNull, z.ZodMiniPipe<z.ZodMiniUndefined, z.ZodMiniTransform<never, undefined>>, T]>;
|
|
11
11
|
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/types/primitives.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,CAAC,MAAM,aAAa,CAAC;AAIjC,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAU9C;AAED,wBAAgB,OAAO,IAAI,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAuBhD;AAED,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAyB9C;AAED,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAoB9C;AAED,wBAAgB,IAAI,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CA0B1C;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,EACzD,KAAK,EAAE,CAAC,GACP,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAElB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAU1E;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/types/primitives.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,CAAC,MAAM,aAAa,CAAC;AAIjC,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAU9C;AAED,wBAAgB,OAAO,IAAI,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAuBhD;AAED,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAyB9C;AAED,wBAAgB,MAAM,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAoB9C;AAED,wBAAgB,IAAI,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CA0B1C;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,EACzD,KAAK,EAAE,CAAC,GACP,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAElB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAU1E;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,kHAarD;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,wHAQrD"}
|
|
@@ -105,12 +105,16 @@ export function literalBigInt(value) {
|
|
|
105
105
|
z.transform(BigInt));
|
|
106
106
|
}
|
|
107
107
|
export function optional(t) {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
// Wrap in `z.optional` so the object key is treated as optional regardless of
|
|
109
|
+
// zod version. zod >=4.4.0 changed object parsing so that a missing key whose
|
|
110
|
+
// value schema is merely a `union` containing `z.undefined()` is treated as
|
|
111
|
+
// required ("nonoptional"); `z.optional(...)` marks the key optional in every
|
|
112
|
+
// 4.x. The inner null->undefined pipe is preserved.
|
|
113
|
+
return z.optional(z.union([
|
|
110
114
|
// Null -> undefined
|
|
111
115
|
z.pipe(z.null(), z.transform(() => unrecognized(undefined))),
|
|
112
116
|
t,
|
|
113
|
-
]);
|
|
117
|
+
]));
|
|
114
118
|
}
|
|
115
119
|
export function nullable(t) {
|
|
116
120
|
return z.union([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../../src/types/primitives.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../../src/types/primitives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: receiving FastPix webhooks with Express.
|
|
3
|
+
*
|
|
4
|
+
* To run this example from the examples directory:
|
|
5
|
+
* npm i express && npm i -D @types/express
|
|
6
|
+
* npm run build && npx tsx webhooksServer.example.ts
|
|
7
|
+
*
|
|
8
|
+
* Set FASTPIX_WEBHOOK_SECRET in your environment (or .env) to the webhook
|
|
9
|
+
* signing secret from the FastPix dashboard. It is base64-encoded and is NOT
|
|
10
|
+
* your API password.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import dotenv from "dotenv";
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
import express from "express";
|
|
17
|
+
import { Fastpix, WebhookVerificationError } from "@fastpix/fastpix-node";
|
|
18
|
+
|
|
19
|
+
// `security` here is only needed if this same client also makes API calls.
|
|
20
|
+
// Webhook verification uses `webhookSecret` (or FASTPIX_WEBHOOK_SECRET), which
|
|
21
|
+
// is completely separate from the API username/password.
|
|
22
|
+
const fastpix = new Fastpix({
|
|
23
|
+
webhookSecret: process.env.FASTPIX_WEBHOOK_SECRET, // defaults to this env var anyway
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const app = express();
|
|
27
|
+
|
|
28
|
+
// Don't advertise the framework/version in responses (avoids the
|
|
29
|
+
// "X-Powered-By: Express" header). Recommended for production endpoints.
|
|
30
|
+
app.disable("x-powered-by");
|
|
31
|
+
|
|
32
|
+
// In-memory idempotency store. In production use a durable store (Redis, a DB
|
|
33
|
+
// table with a unique constraint on the event id, etc.) so dedupe survives
|
|
34
|
+
// restarts and works across instances.
|
|
35
|
+
const processedEventIds = new Set<string>();
|
|
36
|
+
|
|
37
|
+
app.post(
|
|
38
|
+
"/webhooks/fastpix",
|
|
39
|
+
// CRITICAL: capture the RAW body. The signature is computed over the exact
|
|
40
|
+
// bytes FastPix sent — express.json() would re-serialize and break verification.
|
|
41
|
+
express.raw({ type: "application/json" }),
|
|
42
|
+
(req, res) => {
|
|
43
|
+
const signature = req.header("FastPix-Signature");
|
|
44
|
+
|
|
45
|
+
// 1. Endpoint-validation PING.
|
|
46
|
+
// When you add/verify an endpoint, FastPix sends a probe with an empty
|
|
47
|
+
// body (or "{}") and NO signature. Acknowledge it with 200 BEFORE doing
|
|
48
|
+
// any verification, otherwise the endpoint can't be validated.
|
|
49
|
+
const rawText = req.body?.toString("utf8") ?? "";
|
|
50
|
+
const isPing = !signature && (rawText.trim() === "" || rawText.trim() === "{}");
|
|
51
|
+
if (isPing) {
|
|
52
|
+
return res.status(200).send("ok");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Real event: verify + parse in one call.
|
|
56
|
+
try {
|
|
57
|
+
const event = fastpix.webhooks.unwrap(req.body, req.headers);
|
|
58
|
+
|
|
59
|
+
// 3. Dedupe on the TOP-LEVEL event id (the idempotency key), not object.id.
|
|
60
|
+
// There is no signed timestamp, so idempotency is the only replay guard.
|
|
61
|
+
if (processedEventIds.has(event.id)) {
|
|
62
|
+
return res.status(202).send("duplicate ignored");
|
|
63
|
+
}
|
|
64
|
+
processedEventIds.add(event.id);
|
|
65
|
+
|
|
66
|
+
// 4. Route on `type`; the affected resource id is `object.id` (== data.id).
|
|
67
|
+
switch (event.type) {
|
|
68
|
+
case "video.media.created":
|
|
69
|
+
case "video.media.updated":
|
|
70
|
+
console.log(`media ${event.object.id} -> ${event.status}`);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
console.log(`unhandled event type: ${event.type}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Acknowledge fast (202 Accepted) and do heavy work asynchronously.
|
|
77
|
+
return res.status(202).send("accepted");
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Verification failures are client errors -> 400 so FastPix retries later.
|
|
80
|
+
if (err instanceof WebhookVerificationError) {
|
|
81
|
+
console.warn("webhook verification failed:", err.message);
|
|
82
|
+
return res.status(400).send("invalid signature");
|
|
83
|
+
}
|
|
84
|
+
throw err; // unexpected -> let Express return 500
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
90
|
+
app.listen(port, () => {
|
|
91
|
+
console.log(`Listening for FastPix webhooks on http://localhost:${port}/webhooks/fastpix`);
|
|
92
|
+
});
|
|
93
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastpix/fastpix-node",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "FastPix-Dev",
|
|
6
6
|
"email": "devs@fastpix.com",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@eslint/js": "^9.19.0",
|
|
38
38
|
"@types/js-yaml": "^4.0.9",
|
|
39
39
|
"@types/node": "^25.0.2",
|
|
40
|
+
"dotenv": "^17.4.2",
|
|
40
41
|
"eslint": "^9.19.0",
|
|
41
42
|
"exceljs": "^4.4.0",
|
|
42
43
|
"globals": "^15.14.0",
|
|
@@ -48,9 +49,7 @@
|
|
|
48
49
|
"typescript-eslint": "^8.26.0"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"
|
|
52
|
-
"dotenv": "^17.4.2",
|
|
53
|
-
"zod": "^3.25.65 || ^4.0.0"
|
|
52
|
+
"zod": "^3.25.65 || >=4.0.0 <4.4.0"
|
|
54
53
|
},
|
|
55
54
|
"exports": {
|
|
56
55
|
".": {
|
package/src/index.ts
CHANGED
package/src/lib/config.ts
CHANGED
|
@@ -26,6 +26,18 @@ export type SDKOptions = {
|
|
|
26
26
|
*/
|
|
27
27
|
security?: models.Security | (() => Promise<models.Security>) | undefined;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The secret used to verify FastPix webhook signatures.
|
|
31
|
+
*
|
|
32
|
+
* This is the webhook signing secret shown in the FastPix dashboard, NOT the
|
|
33
|
+
* API password supplied via `security`. Keep the two separate: `security`
|
|
34
|
+
* authenticates outbound API requests, while `webhookSecret` verifies inbound
|
|
35
|
+
* webhook deliveries.
|
|
36
|
+
*
|
|
37
|
+
* Defaults to `process.env.FASTPIX_WEBHOOK_SECRET` when not provided.
|
|
38
|
+
*/
|
|
39
|
+
webhookSecret?: string | null | undefined;
|
|
40
|
+
|
|
29
41
|
httpClient?: HTTPClient;
|
|
30
42
|
/**
|
|
31
43
|
* Allows overriding the default server used by the SDK
|
|
@@ -67,8 +79,8 @@ export function serverURLFromOptions(options: SDKOptions): URL | null {
|
|
|
67
79
|
export const SDK_METADATA = {
|
|
68
80
|
language: "typescript",
|
|
69
81
|
openapiDocVersion: "1.0.0",
|
|
70
|
-
sdkVersion: "2.0.
|
|
82
|
+
sdkVersion: "2.0.8",
|
|
71
83
|
genVersion: "2.781.2",
|
|
72
84
|
userAgent:
|
|
73
|
-
"fastpix-sdk/typescript 2.0.
|
|
85
|
+
"fastpix-sdk/typescript 2.0.8 2.781.2 1.0.0 @fastpix/fastpix-node",
|
|
74
86
|
} as const;
|
package/src/lib/env.ts
CHANGED
|
@@ -10,14 +10,14 @@ import { dlv } from "./dlv.js";
|
|
|
10
10
|
export interface Env {
|
|
11
11
|
FASTPIX_USERNAME?: string | undefined;
|
|
12
12
|
FASTPIX_PASSWORD?: string | undefined;
|
|
13
|
-
|
|
13
|
+
FASTPIX_WEBHOOK_SECRET?: string | undefined;
|
|
14
14
|
FASTPIX_DEBUG?: boolean | undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const envSchema: z.ZodMiniType<Env, unknown> = z.object({
|
|
18
18
|
FASTPIX_USERNAME: z.optional(z.string()),
|
|
19
19
|
FASTPIX_PASSWORD: z.optional(z.string()),
|
|
20
|
-
|
|
20
|
+
FASTPIX_WEBHOOK_SECRET: z.optional(z.string()),
|
|
21
21
|
FASTPIX_DEBUG: z.optional(z.coerce.boolean()),
|
|
22
22
|
});
|
|
23
23
|
|
package/src/lib/sdks.ts
CHANGED
|
@@ -198,7 +198,15 @@ export class ClientSDK {
|
|
|
198
198
|
this._baseURL = url;
|
|
199
199
|
this.#httpClient = options.httpClient || defaultHttpClient;
|
|
200
200
|
|
|
201
|
-
this._options = {
|
|
201
|
+
this._options = {
|
|
202
|
+
...options,
|
|
203
|
+
// Resolve the webhook signing secret once, preferring an explicit option
|
|
204
|
+
// and falling back to the FASTPIX_WEBHOOK_SECRET env var. Kept separate
|
|
205
|
+
// from `security` (the API password) on purpose. Carried forward here so
|
|
206
|
+
// any cloned options (e.g. per-resource clients) inherit it.
|
|
207
|
+
webhookSecret: options.webhookSecret ?? env().FASTPIX_WEBHOOK_SECRET ?? null,
|
|
208
|
+
hooks: this.#hooks,
|
|
209
|
+
};
|
|
202
210
|
|
|
203
211
|
this.#logger = this._options.debugLogger;
|
|
204
212
|
if (!this.#logger && env().FASTPIX_DEBUG) {
|
package/src/sdk/sdk.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { SigningKeys } from "./signingkeys.js";
|
|
|
25
25
|
import { Simulcasts } from "./simulcasts.js";
|
|
26
26
|
import { SimulcastStreams } from "./simulcaststreams.js";
|
|
27
27
|
import { Views } from "./views.js";
|
|
28
|
+
import { Webhooks } from "./webhooks.js";
|
|
28
29
|
|
|
29
30
|
export class Fastpix extends ClientSDK {
|
|
30
31
|
private _inputVideo?: InputVideo;
|
|
@@ -146,4 +147,11 @@ export class Fastpix extends ClientSDK {
|
|
|
146
147
|
this._errors ??= new Errors(this._options);
|
|
147
148
|
return this._errors;
|
|
148
149
|
}
|
|
150
|
+
|
|
151
|
+
private _webhooks?: Webhooks;
|
|
152
|
+
get webhooks(): Webhooks {
|
|
153
|
+
this._webhooks ??= new Webhooks(this._options);
|
|
154
|
+
return this._webhooks;
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
}
|