Skip to content

Commit 10717e1

Browse files
committed
move code to wrapper
1 parent 99005db commit 10717e1

File tree

3 files changed

+82
-63
lines changed

3 files changed

+82
-63
lines changed

src/api/resources/webhooks/client/Client.ts

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import * as Webflow from "../../../index";
88
import urlJoin from "url-join";
99
import * as serializers from "../../../../serialization/index";
1010
import * as errors from "../../../../errors/index";
11-
import crypto from "crypto";
1211

1312
export declare namespace Webhooks {
1413
interface Options {
@@ -26,75 +25,13 @@ export declare namespace Webhooks {
2625
/** Additional headers to include in the request. */
2726
headers?: Record<string, string>;
2827
}
29-
30-
interface RequestSignatureDetails {
31-
/** The headers of the incoming webhook request as a record-like object */
32-
headers: Record<string, string>;
33-
/** The body of the incoming webhook request as a string */
34-
body: string;
35-
/** The secret key generated when creating the webhook or the OAuth client secret */
36-
secret: string;
37-
}
3828
}
3929

4030
/**
4131
* Webhooks are the webhooks in your Webflow site.
4232
*/
4333
export class Webhooks {
4434
constructor(protected readonly _options: Webhooks.Options) {}
45-
46-
/**
47-
* Verify that the signature on the webhook message is from Webflow
48-
* @link https://developers.webflow.com/data/docs/working-with-webhooks#validating-request-signatures
49-
*
50-
* @param {Webhooks.RequestSignatureDetails.headers} requestSignatureDetails - details of the incoming webhook request
51-
* @example
52-
* function incomingWebhookRouteHandler(req, res) {
53-
* const headers = req.headers;
54-
* const body = JSON.stringify(req.body);
55-
* const secret = getWebhookSecret(WEBHOOK_ID);
56-
* const isAuthenticated = await client.webhooks.verifySignature({ headers, body, secret });
57-
*
58-
* if (isAuthenticated) {
59-
* // Process the webhook
60-
* } else {
61-
* // Alert the user that the webhook is not authenticated
62-
* }
63-
* res.sendStatus(200);
64-
* }
65-
*
66-
*/
67-
public async verifySignature({ headers, body, secret }: Webhooks.RequestSignatureDetails): Promise<boolean> {
68-
// Creates a HMAC signature following directions from https://developers.webflow.com/data/docs/working-with-webhooks#steps-to-validate-the-request-signature
69-
const createHmac = async (signingSecret: string, message: string) => {
70-
const encoder = new TextEncoder();
71-
72-
// Encode the signingSecret key
73-
// @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
74-
const key = await crypto.subtle.importKey(
75-
"raw",
76-
encoder.encode(signingSecret),
77-
{ name: "HMAC", hash: "SHA-256" },
78-
false,
79-
["sign"]
80-
);
81-
82-
// Encode the message and compute HMAC signature
83-
// @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
84-
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
85-
86-
// Convert signature to hex string
87-
return Array.from(new Uint8Array(signature))
88-
.map((b) => b.toString(16).padStart(2, "0"))
89-
.join("");
90-
};
91-
92-
const message = `${headers["x-webflow-timestamp"]}:${body}`;
93-
94-
const generatedSignature = await createHmac(secret, message);
95-
return headers["x-webflow-signature"] === generatedSignature;
96-
}
97-
9835
/**
9936
* List all App-created Webhooks registered for a given site
10037
*

src/wrapper/WebflowClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ import { OauthScope } from "../api/types/OAuthScope";
44
import * as core from "../core";
55
import * as errors from "../errors";
66
import { SDK_VERSION } from "../version";
7+
import { Client as Webhooks } from "./WebhooksClient";
78

89
export class WebflowClient extends FernClient {
910
constructor(protected readonly _options: FernClient.Options) {
1011
super(_options);
1112
}
1213

14+
protected _webhooks: Webhooks | undefined;
15+
16+
public get webhooks(): Webhooks {
17+
return (this._webhooks ??= new Webhooks(this._options));
18+
}
19+
20+
1321
/**
1422
* @param clientId The OAuth client ID
1523
* @param state The state

src/wrapper/WebhooksClient.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Webhooks } from "../api/resources/webhooks/client/Client";
2+
import crypto from "crypto";
3+
4+
// Extends the namespace declared in the Fern generated client
5+
declare module "../api/resources/webhooks/client/Client" {
6+
export namespace Webhooks {
7+
interface RequestSignatureDetails {
8+
/** The headers of the incoming webhook request as a record-like object */
9+
headers: Record<string, string>;
10+
/** The body of the incoming webhook request as a string */
11+
body: string;
12+
/** The secret key generated when creating the webhook or the OAuth client secret */
13+
secret: string;
14+
}
15+
}
16+
}
17+
18+
export class Client extends Webhooks {
19+
constructor(protected readonly _options: Webhooks.Options) {
20+
super(_options);
21+
}
22+
23+
/**
24+
* Verify that the signature on the webhook message is from Webflow
25+
* @link https://developers.webflow.com/data/docs/working-with-webhooks#validating-request-signatures
26+
*
27+
* @param {Webhooks.RequestSignatureDetails.headers} requestSignatureDetails - details of the incoming webhook request
28+
* @example
29+
* function incomingWebhookRouteHandler(req, res) {
30+
* const headers = req.headers;
31+
* const body = JSON.stringify(req.body);
32+
* const secret = getWebhookSecret(WEBHOOK_ID);
33+
* const isAuthenticated = await client.webhooks.verifySignature({ headers, body, secret });
34+
*
35+
* if (isAuthenticated) {
36+
* // Process the webhook
37+
* } else {
38+
* // Alert the user that the webhook is not authenticated
39+
* }
40+
* res.sendStatus(200);
41+
* }
42+
*
43+
*/
44+
public async verifySignature({ headers, body, secret }: Webhooks.RequestSignatureDetails): Promise<boolean> {
45+
// Creates a HMAC signature following directions from https://developers.webflow.com/data/docs/working-with-webhooks#steps-to-validate-the-request-signature
46+
const createHmac = async (signingSecret: string, message: string) => {
47+
const encoder = new TextEncoder();
48+
49+
// Encode the signingSecret key
50+
// @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
51+
const key = await crypto.subtle.importKey(
52+
"raw",
53+
encoder.encode(signingSecret),
54+
{ name: "HMAC", hash: "SHA-256" },
55+
false,
56+
["sign"]
57+
);
58+
59+
// Encode the message and compute HMAC signature
60+
// @ts-expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
61+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
62+
63+
// Convert signature to hex string
64+
return Array.from(new Uint8Array(signature))
65+
.map((b) => b.toString(16).padStart(2, "0"))
66+
.join("");
67+
};
68+
69+
const message = `${headers["x-webflow-timestamp"]}:${body}`;
70+
71+
const generatedSignature = await createHmac(secret, message);
72+
return headers["x-webflow-signature"] === generatedSignature;
73+
}
74+
}

0 commit comments

Comments
 (0)