go-scheduler-node-sdk 1.0.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.
@@ -0,0 +1,14 @@
1
+ import { AxiosRequestConfig } from "axios";
2
+ export declare class HttpClient {
3
+ private client;
4
+ constructor(baseURL: string, timeout?: number, headers?: Record<string, string>);
5
+ get<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
6
+ post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
7
+ postForm<T>(url: string, formData: FormData, config?: AxiosRequestConfig): Promise<T>;
8
+ put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
9
+ patch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
10
+ delete<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
11
+ private handleError;
12
+ setHeader(key: string, value: string): void;
13
+ removeHeader(key: string): void;
14
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HttpClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ class HttpClient {
9
+ constructor(baseURL, timeout = 30000, headers) {
10
+ this.client = axios_1.default.create({
11
+ baseURL,
12
+ timeout,
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ ...headers,
16
+ },
17
+ });
18
+ }
19
+ async get(url, config) {
20
+ try {
21
+ const response = await this.client.get(url, config);
22
+ return response.data;
23
+ }
24
+ catch (error) {
25
+ throw this.handleError(error);
26
+ }
27
+ }
28
+ async post(url, data, config) {
29
+ try {
30
+ const response = await this.client.post(url, data, config);
31
+ return response.data;
32
+ }
33
+ catch (error) {
34
+ throw this.handleError(error);
35
+ }
36
+ }
37
+ async postForm(url, formData, config) {
38
+ try {
39
+ const response = await this.client.post(url, formData, {
40
+ ...config,
41
+ headers: {
42
+ ...config?.headers,
43
+ "Content-Type": "multipart/form-data",
44
+ },
45
+ });
46
+ return response.data;
47
+ }
48
+ catch (error) {
49
+ throw this.handleError(error);
50
+ }
51
+ }
52
+ async put(url, data, config) {
53
+ try {
54
+ const response = await this.client.put(url, data, config);
55
+ return response.data;
56
+ }
57
+ catch (error) {
58
+ throw this.handleError(error);
59
+ }
60
+ }
61
+ async patch(url, data, config) {
62
+ try {
63
+ const response = await this.client.patch(url, data, config);
64
+ return response.data;
65
+ }
66
+ catch (error) {
67
+ throw this.handleError(error);
68
+ }
69
+ }
70
+ async delete(url, config) {
71
+ try {
72
+ const response = await this.client.delete(url, config);
73
+ return response.data;
74
+ }
75
+ catch (error) {
76
+ throw this.handleError(error);
77
+ }
78
+ }
79
+ handleError(error) {
80
+ if (axios_1.default.isAxiosError(error)) {
81
+ const errorData = error.response?.data;
82
+ const message = errorData?.error || error.message;
83
+ const details = errorData?.details;
84
+ const errorMessage = details ? `${message}: ${details}` : message;
85
+ const customError = new Error(errorMessage);
86
+ customError.status = error.response?.status;
87
+ customError.response = error.response;
88
+ return customError;
89
+ }
90
+ return error instanceof Error ? error : new Error(String(error));
91
+ }
92
+ setHeader(key, value) {
93
+ this.client.defaults.headers.common[key] = value;
94
+ }
95
+ removeHeader(key) {
96
+ delete this.client.defaults.headers.common[key];
97
+ }
98
+ }
99
+ exports.HttpClient = HttpClient;
@@ -0,0 +1,112 @@
1
+ import { WebhookPayload, WebhookVerificationResult } from "../types";
2
+ /**
3
+ * Webhook utility class for verifying and handling webhook payloads
4
+ */
5
+ export declare class WebhookUtils {
6
+ /**
7
+ * Verify a webhook signature using HMAC-SHA256
8
+ * @param payload - The raw webhook payload (as string or Buffer)
9
+ * @param signature - The signature from the X-Webhook-Signature header
10
+ * @param secret - The webhook secret
11
+ * @returns Verification result with validity and optional error message
12
+ */
13
+ static verifySignature(payload: string | Buffer, signature: string, secret: string): WebhookVerificationResult;
14
+ /**
15
+ * Compute the signature for a webhook payload
16
+ * Useful for testing webhook endpoints
17
+ * @param payload - The webhook payload object or string
18
+ * @param secret - The webhook secret
19
+ * @returns Signature in the format "sha256=<hex>"
20
+ */
21
+ static computeSignature(payload: WebhookPayload | string | Buffer, secret: string): string;
22
+ /**
23
+ * Parse and verify a webhook payload
24
+ * @param rawPayload - The raw webhook payload (as received from request)
25
+ * @param signature - The signature from the X-Webhook-Signature header
26
+ * @param secret - The webhook secret
27
+ * @returns Parsed payload if valid, null otherwise
28
+ */
29
+ static parseAndVerify<T = any>(rawPayload: string | Buffer, signature: string, secret: string): {
30
+ payload: WebhookPayload<T>;
31
+ error?: string;
32
+ } | null;
33
+ /**
34
+ * Validate webhook payload structure
35
+ * @param payload - The parsed webhook payload
36
+ * @returns True if payload has required fields
37
+ */
38
+ static validatePayloadStructure(payload: any): payload is WebhookPayload;
39
+ /**
40
+ * Check if a webhook timestamp is fresh (within acceptable time window)
41
+ * Helps prevent replay attacks
42
+ * @param timestamp - The webhook timestamp (Unix timestamp in seconds)
43
+ * @param toleranceSeconds - Maximum age of webhook in seconds (default: 300 = 5 minutes)
44
+ * @returns True if timestamp is within tolerance
45
+ */
46
+ static isTimestampFresh(timestamp: number, toleranceSeconds?: number): boolean;
47
+ /**
48
+ * Verify both signature and timestamp freshness
49
+ * @param rawPayload - The raw webhook payload
50
+ * @param signature - The signature from the X-Webhook-Signature header
51
+ * @param secret - The webhook secret
52
+ * @param toleranceSeconds - Maximum age of webhook in seconds (default: 300)
53
+ * @returns Verification result
54
+ */
55
+ static verifyWithTimestamp(rawPayload: string | Buffer, signature: string, secret: string, toleranceSeconds?: number): WebhookVerificationResult;
56
+ /**
57
+ * Extract webhook signature from request headers
58
+ * Supports multiple header formats
59
+ * @param headers - Request headers object
60
+ * @returns Signature string or null if not found
61
+ */
62
+ static extractSignature(headers: Record<string, string | string[] | undefined>): string | null;
63
+ /**
64
+ * Create a webhook response helper for Express/HTTP frameworks
65
+ * @param statusCode - HTTP status code to return
66
+ * @param message - Optional message
67
+ * @returns Response object
68
+ */
69
+ static createResponse(statusCode: number, message?: string): {
70
+ statusCode: number;
71
+ body: string | undefined;
72
+ };
73
+ /**
74
+ * Success response for webhook acknowledgment
75
+ */
76
+ static success(): {
77
+ statusCode: number;
78
+ body: string | undefined;
79
+ };
80
+ /**
81
+ * Error response for invalid signature
82
+ */
83
+ static invalidSignature(): {
84
+ statusCode: number;
85
+ body: string | undefined;
86
+ };
87
+ /**
88
+ * Error response for stale timestamp
89
+ */
90
+ static staleTimestamp(): {
91
+ statusCode: number;
92
+ body: string | undefined;
93
+ };
94
+ /**
95
+ * Error response for invalid payload
96
+ */
97
+ static invalidPayload(): {
98
+ statusCode: number;
99
+ body: string | undefined;
100
+ };
101
+ }
102
+ /**
103
+ * Express middleware factory for webhook verification
104
+ * @param secret - The webhook secret
105
+ * @param options - Optional configuration
106
+ * @returns Express middleware function
107
+ */
108
+ export declare function createWebhookVerificationMiddleware(secret: string, options?: {
109
+ toleranceSeconds?: number;
110
+ rawBodyKey?: string;
111
+ }): (req: any, res: any, next: any) => void;
112
+ export declare const verifySignature: typeof WebhookUtils.verifySignature, computeSignature: typeof WebhookUtils.computeSignature, parseAndVerify: typeof WebhookUtils.parseAndVerify, validatePayloadStructure: typeof WebhookUtils.validatePayloadStructure, isTimestampFresh: typeof WebhookUtils.isTimestampFresh, verifyWithTimestamp: typeof WebhookUtils.verifyWithTimestamp, extractSignature: typeof WebhookUtils.extractSignature;
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractSignature = exports.verifyWithTimestamp = exports.isTimestampFresh = exports.validatePayloadStructure = exports.parseAndVerify = exports.computeSignature = exports.verifySignature = exports.WebhookUtils = void 0;
4
+ exports.createWebhookVerificationMiddleware = createWebhookVerificationMiddleware;
5
+ const crypto_1 = require("crypto");
6
+ /**
7
+ * Webhook utility class for verifying and handling webhook payloads
8
+ */
9
+ class WebhookUtils {
10
+ /**
11
+ * Verify a webhook signature using HMAC-SHA256
12
+ * @param payload - The raw webhook payload (as string or Buffer)
13
+ * @param signature - The signature from the X-Webhook-Signature header
14
+ * @param secret - The webhook secret
15
+ * @returns Verification result with validity and optional error message
16
+ */
17
+ static verifySignature(payload, signature, secret) {
18
+ try {
19
+ // Normalize payload to string
20
+ const payloadString = typeof payload === "string" ? payload : payload.toString("utf8");
21
+ // Extract the signature value (format: "sha256=<hex>")
22
+ if (!signature || !signature.startsWith("sha256=")) {
23
+ return {
24
+ valid: false,
25
+ error: "Invalid signature format. Expected 'sha256=<hex>'",
26
+ };
27
+ }
28
+ const providedSignature = signature.substring(7); // Remove "sha256=" prefix
29
+ // Compute expected signature
30
+ const expectedSignature = (0, crypto_1.createHmac)("sha256", secret)
31
+ .update(payloadString)
32
+ .digest("hex");
33
+ // Use timing-safe comparison to prevent timing attacks
34
+ const providedBuffer = Buffer.from(providedSignature, "hex");
35
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
36
+ if (providedBuffer.length !== expectedBuffer.length) {
37
+ return {
38
+ valid: false,
39
+ error: "Signature length mismatch",
40
+ };
41
+ }
42
+ const isValid = (0, crypto_1.timingSafeEqual)(providedBuffer, expectedBuffer);
43
+ return {
44
+ valid: isValid,
45
+ error: isValid ? undefined : "Signature verification failed",
46
+ };
47
+ }
48
+ catch (error) {
49
+ return {
50
+ valid: false,
51
+ error: `Verification error: ${error instanceof Error ? error.message : String(error)}`,
52
+ };
53
+ }
54
+ }
55
+ /**
56
+ * Compute the signature for a webhook payload
57
+ * Useful for testing webhook endpoints
58
+ * @param payload - The webhook payload object or string
59
+ * @param secret - The webhook secret
60
+ * @returns Signature in the format "sha256=<hex>"
61
+ */
62
+ static computeSignature(payload, secret) {
63
+ const payloadString = typeof payload === "string"
64
+ ? payload
65
+ : Buffer.isBuffer(payload)
66
+ ? payload.toString("utf8")
67
+ : JSON.stringify(payload);
68
+ const signature = (0, crypto_1.createHmac)("sha256", secret)
69
+ .update(payloadString)
70
+ .digest("hex");
71
+ return `sha256=${signature}`;
72
+ }
73
+ /**
74
+ * Parse and verify a webhook payload
75
+ * @param rawPayload - The raw webhook payload (as received from request)
76
+ * @param signature - The signature from the X-Webhook-Signature header
77
+ * @param secret - The webhook secret
78
+ * @returns Parsed payload if valid, null otherwise
79
+ */
80
+ static parseAndVerify(rawPayload, signature, secret) {
81
+ // Verify signature first
82
+ const verification = this.verifySignature(rawPayload, signature, secret);
83
+ if (!verification.valid) {
84
+ return null;
85
+ }
86
+ // Parse payload
87
+ try {
88
+ const payloadString = typeof rawPayload === "string"
89
+ ? rawPayload
90
+ : rawPayload.toString("utf8");
91
+ const payload = JSON.parse(payloadString);
92
+ return { payload };
93
+ }
94
+ catch (error) {
95
+ return null;
96
+ }
97
+ }
98
+ /**
99
+ * Validate webhook payload structure
100
+ * @param payload - The parsed webhook payload
101
+ * @returns True if payload has required fields
102
+ */
103
+ static validatePayloadStructure(payload) {
104
+ return (typeof payload === "object" &&
105
+ payload !== null &&
106
+ typeof payload.webhook_uid === "string" &&
107
+ typeof payload.event_type === "string" &&
108
+ typeof payload.delivery_id === "string" &&
109
+ typeof payload.timestamp === "number" &&
110
+ "data" in payload);
111
+ }
112
+ /**
113
+ * Check if a webhook timestamp is fresh (within acceptable time window)
114
+ * Helps prevent replay attacks
115
+ * @param timestamp - The webhook timestamp (Unix timestamp in seconds)
116
+ * @param toleranceSeconds - Maximum age of webhook in seconds (default: 300 = 5 minutes)
117
+ * @returns True if timestamp is within tolerance
118
+ */
119
+ static isTimestampFresh(timestamp, toleranceSeconds = 300) {
120
+ const now = Math.floor(Date.now() / 1000);
121
+ const age = Math.abs(now - timestamp);
122
+ return age <= toleranceSeconds;
123
+ }
124
+ /**
125
+ * Verify both signature and timestamp freshness
126
+ * @param rawPayload - The raw webhook payload
127
+ * @param signature - The signature from the X-Webhook-Signature header
128
+ * @param secret - The webhook secret
129
+ * @param toleranceSeconds - Maximum age of webhook in seconds (default: 300)
130
+ * @returns Verification result
131
+ */
132
+ static verifyWithTimestamp(rawPayload, signature, secret, toleranceSeconds = 300) {
133
+ // Verify signature
134
+ const signatureResult = this.verifySignature(rawPayload, signature, secret);
135
+ if (!signatureResult.valid) {
136
+ return signatureResult;
137
+ }
138
+ // Parse and check timestamp
139
+ try {
140
+ const payloadString = typeof rawPayload === "string"
141
+ ? rawPayload
142
+ : rawPayload.toString("utf8");
143
+ const payload = JSON.parse(payloadString);
144
+ if (!this.isTimestampFresh(payload.timestamp, toleranceSeconds)) {
145
+ return {
146
+ valid: false,
147
+ error: `Webhook timestamp is too old. Age exceeds ${toleranceSeconds} seconds.`,
148
+ };
149
+ }
150
+ return { valid: true };
151
+ }
152
+ catch (error) {
153
+ return {
154
+ valid: false,
155
+ error: "Failed to parse payload for timestamp verification",
156
+ };
157
+ }
158
+ }
159
+ /**
160
+ * Extract webhook signature from request headers
161
+ * Supports multiple header formats
162
+ * @param headers - Request headers object
163
+ * @returns Signature string or null if not found
164
+ */
165
+ static extractSignature(headers) {
166
+ // Try different header formats
167
+ const signature = headers["x-webhook-signature"] ||
168
+ headers["X-Webhook-Signature"] ||
169
+ headers["x-signature"] ||
170
+ headers["X-Signature"];
171
+ if (Array.isArray(signature)) {
172
+ return signature[0] || null;
173
+ }
174
+ return signature || null;
175
+ }
176
+ /**
177
+ * Create a webhook response helper for Express/HTTP frameworks
178
+ * @param statusCode - HTTP status code to return
179
+ * @param message - Optional message
180
+ * @returns Response object
181
+ */
182
+ static createResponse(statusCode, message) {
183
+ return {
184
+ statusCode,
185
+ body: message ? JSON.stringify({ message }) : undefined,
186
+ };
187
+ }
188
+ /**
189
+ * Success response for webhook acknowledgment
190
+ */
191
+ static success() {
192
+ return this.createResponse(200, "Webhook received");
193
+ }
194
+ /**
195
+ * Error response for invalid signature
196
+ */
197
+ static invalidSignature() {
198
+ return this.createResponse(401, "Invalid webhook signature");
199
+ }
200
+ /**
201
+ * Error response for stale timestamp
202
+ */
203
+ static staleTimestamp() {
204
+ return this.createResponse(400, "Webhook timestamp is too old");
205
+ }
206
+ /**
207
+ * Error response for invalid payload
208
+ */
209
+ static invalidPayload() {
210
+ return this.createResponse(400, "Invalid webhook payload");
211
+ }
212
+ }
213
+ exports.WebhookUtils = WebhookUtils;
214
+ /**
215
+ * Express middleware factory for webhook verification
216
+ * @param secret - The webhook secret
217
+ * @param options - Optional configuration
218
+ * @returns Express middleware function
219
+ */
220
+ function createWebhookVerificationMiddleware(secret, options = {}) {
221
+ const { toleranceSeconds = 300, rawBodyKey = "rawBody" } = options;
222
+ return (req, res, next) => {
223
+ try {
224
+ // Extract signature from headers
225
+ const signature = WebhookUtils.extractSignature(req.headers);
226
+ if (!signature) {
227
+ res.status(401).json({ error: "Missing webhook signature" });
228
+ return;
229
+ }
230
+ // Get raw body (must be preserved by body parser)
231
+ const rawBody = req[rawBodyKey] || req.body;
232
+ if (!rawBody) {
233
+ res.status(400).json({ error: "Missing request body" });
234
+ return;
235
+ }
236
+ // Verify signature and timestamp
237
+ const verification = WebhookUtils.verifyWithTimestamp(rawBody, signature, secret, toleranceSeconds);
238
+ if (!verification.valid) {
239
+ res.status(401).json({ error: verification.error });
240
+ return;
241
+ }
242
+ // Attach parsed payload to request
243
+ try {
244
+ const payloadString = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
245
+ req.webhookPayload = JSON.parse(payloadString);
246
+ }
247
+ catch (error) {
248
+ res.status(400).json({ error: "Invalid JSON payload" });
249
+ return;
250
+ }
251
+ next();
252
+ }
253
+ catch (error) {
254
+ res.status(500).json({
255
+ error: "Webhook verification failed",
256
+ details: error instanceof Error ? error.message : String(error),
257
+ });
258
+ }
259
+ };
260
+ }
261
+ // Export both the class and individual functions for convenience
262
+ exports.verifySignature = WebhookUtils.verifySignature, exports.computeSignature = WebhookUtils.computeSignature, exports.parseAndVerify = WebhookUtils.parseAndVerify, exports.validatePayloadStructure = WebhookUtils.validatePayloadStructure, exports.isTimestampFresh = WebhookUtils.isTimestampFresh, exports.verifyWithTimestamp = WebhookUtils.verifyWithTimestamp, exports.extractSignature = WebhookUtils.extractSignature;
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "go-scheduler-node-sdk",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript SDK for interacting with go-scheduler REST API",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "yarn build",
13
+ "watch": "tsc --watch",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "keywords": [
17
+ "scheduler",
18
+ "calendar",
19
+ "events",
20
+ "reminders",
21
+ "go-scheduler"
22
+ ],
23
+ "author": "jaysongiroux",
24
+ "license": "MIT",
25
+ "private": false,
26
+ "dependencies": {
27
+ "axios": "^1.13.2"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.5",
31
+ "typescript": "^5.9.3"
32
+ }
33
+ }