@linear/sdk 70.0.0 → 72.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.
- package/dist/{index-CExL-wAi.d.mts → index-BBxdiqQK.d.mts} +767 -89
- package/dist/index-BBxdiqQK.d.mts.map +1 -0
- package/dist/{index-Bm4jA7_4.d.cts → index-DPQVugF5.d.cts} +767 -89
- package/dist/index-DPQVugF5.d.cts.map +1 -0
- package/dist/index.cjs +1131 -93
- package/dist/index.d.cts +391 -33
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +391 -33
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1113 -92
- package/dist/index.mjs.map +1 -1
- package/dist/webhooks/index.cjs +2 -1
- package/dist/webhooks/index.d.cts +2 -2
- package/dist/webhooks/index.d.mts +2 -2
- package/dist/webhooks/index.mjs +2 -2
- package/dist/{webhooks-CgtCOSvZ.mjs → webhooks-Bbhy0Mv8.mjs} +21 -10
- package/dist/webhooks-Bbhy0Mv8.mjs.map +1 -0
- package/dist/{webhooks-BFArBJks.cjs → webhooks-nAfXvD4i.cjs} +25 -8
- package/package.json +4 -4
- package/dist/index-Bm4jA7_4.d.cts.map +0 -1
- package/dist/index-CExL-wAi.d.mts.map +0 -1
- package/dist/webhooks-CgtCOSvZ.mjs.map +0 -1
package/dist/webhooks/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const require_webhooks = require('../webhooks-
|
|
1
|
+
const require_webhooks = require('../webhooks-nAfXvD4i.cjs');
|
|
2
2
|
|
|
3
3
|
exports.LINEAR_WEBHOOK_SIGNATURE_HEADER = require_webhooks.LINEAR_WEBHOOK_SIGNATURE_HEADER;
|
|
4
4
|
exports.LINEAR_WEBHOOK_TS_FIELD = require_webhooks.LINEAR_WEBHOOK_TS_FIELD;
|
|
5
|
+
exports.LINEAR_WEBHOOK_TS_HEADER = require_webhooks.LINEAR_WEBHOOK_TS_HEADER;
|
|
5
6
|
exports.LinearWebhookClient = require_webhooks.LinearWebhookClient;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { AgentSessionEventWebhookPayload, AppUserNotificationWebhookPayloadWithNotification, AppUserTeamAccessChangedWebhookPayload, EntityWebhookPayloadWithAttachmentData, EntityWebhookPayloadWithAuditEntryData, EntityWebhookPayloadWithCommentData, EntityWebhookPayloadWithCustomerData, EntityWebhookPayloadWithCustomerNeedData, EntityWebhookPayloadWithCycleData, EntityWebhookPayloadWithDocumentData, EntityWebhookPayloadWithEntityData, EntityWebhookPayloadWithInitiativeData, EntityWebhookPayloadWithInitiativeUpdateData, EntityWebhookPayloadWithIssueData, EntityWebhookPayloadWithIssueLabelData, EntityWebhookPayloadWithProjectData, EntityWebhookPayloadWithProjectUpdateData, EntityWebhookPayloadWithReactionData, EntityWebhookPayloadWithUnknownEntityData, EntityWebhookPayloadWithUserData, IssueSlaWebhookPayload, LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LinearWebhookClient, LinearWebhookEventHandler, LinearWebhookEventType, LinearWebhookEventTypeMap, LinearWebhookHandler, LinearWebhookPayload, OAuthAppWebhookPayload };
|
|
1
|
+
import { A as LinearWebhookPayload, C as EntityWebhookPayloadWithUnknownEntityData, D as LinearWebhookEventType, E as LinearWebhookEventHandler, O as LinearWebhookEventTypeMap, S as EntityWebhookPayloadWithReactionData, T as IssueSlaWebhookPayload, _ as EntityWebhookPayloadWithInitiativeUpdateData, a as AgentSessionEventWebhookPayload, b as EntityWebhookPayloadWithProjectData, c as EntityWebhookPayloadWithAttachmentData, d as EntityWebhookPayloadWithCustomerData, f as EntityWebhookPayloadWithCustomerNeedData, g as EntityWebhookPayloadWithInitiativeData, h as EntityWebhookPayloadWithEntityData, i as LinearWebhookClient, j as OAuthAppWebhookPayload, k as LinearWebhookHandler, l as EntityWebhookPayloadWithAuditEntryData, m as EntityWebhookPayloadWithDocumentData, n as LINEAR_WEBHOOK_TS_FIELD, o as AppUserNotificationWebhookPayloadWithNotification, p as EntityWebhookPayloadWithCycleData, r as LINEAR_WEBHOOK_TS_HEADER, s as AppUserTeamAccessChangedWebhookPayload, t as LINEAR_WEBHOOK_SIGNATURE_HEADER, u as EntityWebhookPayloadWithCommentData, v as EntityWebhookPayloadWithIssueData, w as EntityWebhookPayloadWithUserData, x as EntityWebhookPayloadWithProjectUpdateData, y as EntityWebhookPayloadWithIssueLabelData } from "../index-DPQVugF5.cjs";
|
|
2
|
+
export { AgentSessionEventWebhookPayload, AppUserNotificationWebhookPayloadWithNotification, AppUserTeamAccessChangedWebhookPayload, EntityWebhookPayloadWithAttachmentData, EntityWebhookPayloadWithAuditEntryData, EntityWebhookPayloadWithCommentData, EntityWebhookPayloadWithCustomerData, EntityWebhookPayloadWithCustomerNeedData, EntityWebhookPayloadWithCycleData, EntityWebhookPayloadWithDocumentData, EntityWebhookPayloadWithEntityData, EntityWebhookPayloadWithInitiativeData, EntityWebhookPayloadWithInitiativeUpdateData, EntityWebhookPayloadWithIssueData, EntityWebhookPayloadWithIssueLabelData, EntityWebhookPayloadWithProjectData, EntityWebhookPayloadWithProjectUpdateData, EntityWebhookPayloadWithReactionData, EntityWebhookPayloadWithUnknownEntityData, EntityWebhookPayloadWithUserData, IssueSlaWebhookPayload, LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LINEAR_WEBHOOK_TS_HEADER, LinearWebhookClient, LinearWebhookEventHandler, LinearWebhookEventType, LinearWebhookEventTypeMap, LinearWebhookHandler, LinearWebhookPayload, OAuthAppWebhookPayload };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { AgentSessionEventWebhookPayload, AppUserNotificationWebhookPayloadWithNotification, AppUserTeamAccessChangedWebhookPayload, EntityWebhookPayloadWithAttachmentData, EntityWebhookPayloadWithAuditEntryData, EntityWebhookPayloadWithCommentData, EntityWebhookPayloadWithCustomerData, EntityWebhookPayloadWithCustomerNeedData, EntityWebhookPayloadWithCycleData, EntityWebhookPayloadWithDocumentData, EntityWebhookPayloadWithEntityData, EntityWebhookPayloadWithInitiativeData, EntityWebhookPayloadWithInitiativeUpdateData, EntityWebhookPayloadWithIssueData, EntityWebhookPayloadWithIssueLabelData, EntityWebhookPayloadWithProjectData, EntityWebhookPayloadWithProjectUpdateData, EntityWebhookPayloadWithReactionData, EntityWebhookPayloadWithUnknownEntityData, EntityWebhookPayloadWithUserData, IssueSlaWebhookPayload, LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LinearWebhookClient, LinearWebhookEventHandler, LinearWebhookEventType, LinearWebhookEventTypeMap, LinearWebhookHandler, LinearWebhookPayload, OAuthAppWebhookPayload };
|
|
1
|
+
import { A as LinearWebhookPayload, C as EntityWebhookPayloadWithUnknownEntityData, D as LinearWebhookEventType, E as LinearWebhookEventHandler, O as LinearWebhookEventTypeMap, S as EntityWebhookPayloadWithReactionData, T as IssueSlaWebhookPayload, _ as EntityWebhookPayloadWithInitiativeUpdateData, a as AgentSessionEventWebhookPayload, b as EntityWebhookPayloadWithProjectData, c as EntityWebhookPayloadWithAttachmentData, d as EntityWebhookPayloadWithCustomerData, f as EntityWebhookPayloadWithCustomerNeedData, g as EntityWebhookPayloadWithInitiativeData, h as EntityWebhookPayloadWithEntityData, i as LinearWebhookClient, j as OAuthAppWebhookPayload, k as LinearWebhookHandler, l as EntityWebhookPayloadWithAuditEntryData, m as EntityWebhookPayloadWithDocumentData, n as LINEAR_WEBHOOK_TS_FIELD, o as AppUserNotificationWebhookPayloadWithNotification, p as EntityWebhookPayloadWithCycleData, r as LINEAR_WEBHOOK_TS_HEADER, s as AppUserTeamAccessChangedWebhookPayload, t as LINEAR_WEBHOOK_SIGNATURE_HEADER, u as EntityWebhookPayloadWithCommentData, v as EntityWebhookPayloadWithIssueData, w as EntityWebhookPayloadWithUserData, x as EntityWebhookPayloadWithProjectUpdateData, y as EntityWebhookPayloadWithIssueLabelData } from "../index-BBxdiqQK.mjs";
|
|
2
|
+
export { AgentSessionEventWebhookPayload, AppUserNotificationWebhookPayloadWithNotification, AppUserTeamAccessChangedWebhookPayload, EntityWebhookPayloadWithAttachmentData, EntityWebhookPayloadWithAuditEntryData, EntityWebhookPayloadWithCommentData, EntityWebhookPayloadWithCustomerData, EntityWebhookPayloadWithCustomerNeedData, EntityWebhookPayloadWithCycleData, EntityWebhookPayloadWithDocumentData, EntityWebhookPayloadWithEntityData, EntityWebhookPayloadWithInitiativeData, EntityWebhookPayloadWithInitiativeUpdateData, EntityWebhookPayloadWithIssueData, EntityWebhookPayloadWithIssueLabelData, EntityWebhookPayloadWithProjectData, EntityWebhookPayloadWithProjectUpdateData, EntityWebhookPayloadWithReactionData, EntityWebhookPayloadWithUnknownEntityData, EntityWebhookPayloadWithUserData, IssueSlaWebhookPayload, LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LINEAR_WEBHOOK_TS_HEADER, LinearWebhookClient, LinearWebhookEventHandler, LinearWebhookEventType, LinearWebhookEventTypeMap, LinearWebhookHandler, LinearWebhookPayload, OAuthAppWebhookPayload };
|
package/dist/webhooks/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as LINEAR_WEBHOOK_TS_FIELD, r as
|
|
1
|
+
import { i as LinearWebhookClient, n as LINEAR_WEBHOOK_TS_FIELD, r as LINEAR_WEBHOOK_TS_HEADER, t as LINEAR_WEBHOOK_SIGNATURE_HEADER } from "../webhooks-Bbhy0Mv8.mjs";
|
|
2
2
|
|
|
3
|
-
export { LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LinearWebhookClient };
|
|
3
|
+
export { LINEAR_WEBHOOK_SIGNATURE_HEADER, LINEAR_WEBHOOK_TS_FIELD, LINEAR_WEBHOOK_TS_HEADER, LinearWebhookClient };
|
|
@@ -2,6 +2,7 @@ import crypto from "crypto";
|
|
|
2
2
|
|
|
3
3
|
//#region src/webhooks/client.ts
|
|
4
4
|
const LINEAR_WEBHOOK_SIGNATURE_HEADER = "linear-signature";
|
|
5
|
+
const LINEAR_WEBHOOK_TS_HEADER = "linear-timestamp";
|
|
5
6
|
const LINEAR_WEBHOOK_TS_FIELD = "webhookTimestamp";
|
|
6
7
|
/**
|
|
7
8
|
* Client for handling Linear webhook requests with helpers.
|
|
@@ -31,7 +32,7 @@ var LinearWebhookClient = class {
|
|
|
31
32
|
const rawBody = await adapter.readRawBody();
|
|
32
33
|
let parsedPayload;
|
|
33
34
|
try {
|
|
34
|
-
parsedPayload = this.parseVerifiedPayload(rawBody, signature);
|
|
35
|
+
parsedPayload = this.parseVerifiedPayload(rawBody, signature, adapter.timestamp);
|
|
35
36
|
} catch {
|
|
36
37
|
return adapter.send(400, "Invalid webhook");
|
|
37
38
|
}
|
|
@@ -84,6 +85,7 @@ var LinearWebhookClient = class {
|
|
|
84
85
|
return {
|
|
85
86
|
method: request.method,
|
|
86
87
|
signature: request.headers.get(LINEAR_WEBHOOK_SIGNATURE_HEADER),
|
|
88
|
+
timestamp: request.headers.get(LINEAR_WEBHOOK_TS_HEADER),
|
|
87
89
|
readRawBody: async () => Buffer.from(await request.arrayBuffer()),
|
|
88
90
|
send: (status, body) => new Response(body, { status })
|
|
89
91
|
};
|
|
@@ -97,11 +99,14 @@ var LinearWebhookClient = class {
|
|
|
97
99
|
* @returns Helpers to read input and send responses in a unified way
|
|
98
100
|
*/
|
|
99
101
|
createNodeAdapter(incomingMessage, res) {
|
|
100
|
-
const
|
|
101
|
-
const signature = Array.isArray(
|
|
102
|
+
const signatureHeader = incomingMessage.headers[LINEAR_WEBHOOK_SIGNATURE_HEADER];
|
|
103
|
+
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] ?? null : signatureHeader ?? null;
|
|
104
|
+
const timestampHeader = incomingMessage.headers[LINEAR_WEBHOOK_TS_HEADER];
|
|
105
|
+
const timestamp = Array.isArray(timestampHeader) ? timestampHeader[0] ?? null : timestampHeader ?? null;
|
|
102
106
|
return {
|
|
103
107
|
method: incomingMessage.method || "",
|
|
104
108
|
signature,
|
|
109
|
+
timestamp,
|
|
105
110
|
readRawBody: async () => {
|
|
106
111
|
const chunks = [];
|
|
107
112
|
for await (const chunk of incomingMessage) chunks.push(Buffer.from(chunk));
|
|
@@ -131,11 +136,13 @@ var LinearWebhookClient = class {
|
|
|
131
136
|
*
|
|
132
137
|
* @param rawBody - Raw request body as a Buffer
|
|
133
138
|
* @param signature - The value of the `linear-signature` header
|
|
139
|
+
* @param timestampHeader - The value of the `linear-timestamp` header (preferred over body field)
|
|
134
140
|
* @returns The verified and parsed webhook payload
|
|
135
141
|
*/
|
|
136
|
-
parseVerifiedPayload(rawBody, signature) {
|
|
142
|
+
parseVerifiedPayload(rawBody, signature, timestampHeader) {
|
|
137
143
|
const parsedBody = this.parseBodyAsWebhookPayload(rawBody);
|
|
138
|
-
|
|
144
|
+
const timestamp = timestampHeader ?? parsedBody.webhookTimestamp;
|
|
145
|
+
if (!this.verify(rawBody, signature, timestamp)) throw new Error("Invalid webhook signature");
|
|
139
146
|
return parsedBody;
|
|
140
147
|
}
|
|
141
148
|
/**
|
|
@@ -167,7 +174,8 @@ var LinearWebhookClient = class {
|
|
|
167
174
|
*
|
|
168
175
|
* @param rawBody The webhook request raw body
|
|
169
176
|
* @param signature The signature to verify
|
|
170
|
-
* @param timestamp The
|
|
177
|
+
* @param timestamp The timestamp value - either from the `linear-timestamp` header (string)
|
|
178
|
+
* or the `webhookTimestamp` field from the request parsed body (number)
|
|
171
179
|
* @returns True if the signature is valid
|
|
172
180
|
*/
|
|
173
181
|
verify(rawBody, signature, timestamp) {
|
|
@@ -176,7 +184,9 @@ var LinearWebhookClient = class {
|
|
|
176
184
|
if (verificationBuffer.length !== signatureBuffer.length) throw new Error("Invalid webhook signature");
|
|
177
185
|
if (!crypto.timingSafeEqual(verificationBuffer, signatureBuffer)) throw new Error("Invalid webhook signature");
|
|
178
186
|
if (timestamp) {
|
|
179
|
-
|
|
187
|
+
const timestampMs = typeof timestamp === "string" ? parseInt(timestamp, 10) : timestamp;
|
|
188
|
+
if (isNaN(timestampMs)) throw new Error(`Invalid webhook timestamp: ${timestamp}`);
|
|
189
|
+
if (Math.abs((/* @__PURE__ */ new Date()).getTime() - timestampMs) > 1e3 * 60) throw new Error("Invalid webhook timestamp");
|
|
180
190
|
}
|
|
181
191
|
return true;
|
|
182
192
|
}
|
|
@@ -185,7 +195,8 @@ var LinearWebhookClient = class {
|
|
|
185
195
|
*
|
|
186
196
|
* @param rawBody The webhook request raw body
|
|
187
197
|
* @param signature The signature to verify
|
|
188
|
-
* @param timestamp The
|
|
198
|
+
* @param timestamp The timestamp value - either from the `linear-timestamp` header (string)
|
|
199
|
+
* or the `webhookTimestamp` field from the request parsed body (number)
|
|
189
200
|
*/
|
|
190
201
|
parseData(rawBody, signature, timestamp) {
|
|
191
202
|
if (!this.verify(rawBody, signature, timestamp)) throw new Error("Invalid webhook signature");
|
|
@@ -194,5 +205,5 @@ var LinearWebhookClient = class {
|
|
|
194
205
|
};
|
|
195
206
|
|
|
196
207
|
//#endregion
|
|
197
|
-
export { LINEAR_WEBHOOK_TS_FIELD as n,
|
|
198
|
-
//# sourceMappingURL=webhooks-
|
|
208
|
+
export { LinearWebhookClient as i, LINEAR_WEBHOOK_TS_FIELD as n, LINEAR_WEBHOOK_TS_HEADER as r, LINEAR_WEBHOOK_SIGNATURE_HEADER as t };
|
|
209
|
+
//# sourceMappingURL=webhooks-Bbhy0Mv8.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks-Bbhy0Mv8.mjs","names":["secret: string","parsedPayload: LinearWebhookPayload","chunks: Buffer[]"],"sources":["../src/webhooks/client.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport {\n LinearWebhookEventHandler,\n LinearWebhookEventType,\n LinearWebhookHandler,\n LinearWebhookPayload,\n} from \"./types.js\";\n\nexport const LINEAR_WEBHOOK_SIGNATURE_HEADER = \"linear-signature\";\nexport const LINEAR_WEBHOOK_TS_HEADER = \"linear-timestamp\";\nexport const LINEAR_WEBHOOK_TS_FIELD = \"webhookTimestamp\";\n\n/**\n * Internal abstraction that adapts request/response behavior across\n * Fetch API and Node.js HTTP runtimes.\n *\n * Not exported on purpose: this is an implementation detail of\n * `LinearWebhookClient` and should not be relied upon by SDK consumers.\n *\n * - `method` and `signature` expose the necessary request metadata.\n * - `readRawBody` defers reading/streaming the raw body until invoked.\n * - `send` unifies writing responses in both environments.\n */\ninterface HttpAdapter {\n method: string;\n signature: string | null;\n timestamp: string | null;\n readRawBody: () => Promise<Buffer>;\n send: (status: number, body: string) => Response | void;\n}\n\n/**\n * Client for handling Linear webhook requests with helpers.\n */\nexport class LinearWebhookClient {\n /**\n * Creates a new LinearWebhookClient instance\n * @param secret The webhook signing secret. See https://linear.app/developers/webhooks#securing-webhooks.\n */\n public constructor(private secret: string) {}\n\n /**\n * Creates a webhook handler function that can process Linear webhook requests\n * @returns A webhook handler function with event registration capabilities.\n * Supports both Fetch API `(request: Request) => Promise<Response>` and\n * Node.js `(request: IncomingMessage, response: ServerResponse) => Promise<void>`\n */\n public createHandler(): LinearWebhookHandler {\n const eventHandlers = new Map<string, LinearWebhookEventHandler<LinearWebhookPayload>[]>();\n\n const handler = async (\n requestOrMessage: Request | IncomingMessage,\n response?: ServerResponse\n ): Promise<Response | void> => {\n const adapter = this.getHttpAdapter(requestOrMessage, response);\n\n try {\n if (adapter.method !== \"POST\") {\n return adapter.send(405, \"Method not allowed\");\n }\n\n const signature = adapter.signature;\n if (!signature) {\n return adapter.send(400, \"Missing webhook signature\");\n }\n\n const rawBody = await adapter.readRawBody();\n\n let parsedPayload: LinearWebhookPayload;\n try {\n parsedPayload = this.parseVerifiedPayload(rawBody, signature, adapter.timestamp);\n } catch {\n return adapter.send(400, \"Invalid webhook\");\n }\n\n const allHandlers = this.collectHandlers(eventHandlers, parsedPayload.type);\n // eslint-disable-next-line @typescript-eslint/await-thenable\n await Promise.all(allHandlers.map(h => h(parsedPayload)));\n return adapter.send(200, \"OK\");\n } catch {\n return adapter.send(500, \"Internal server error\");\n }\n };\n\n handler.on = function <T extends LinearWebhookEventType>(\n eventType: T,\n eventHandler: LinearWebhookEventHandler<Extract<LinearWebhookPayload, { type: T }>>\n ): void {\n const handlers = eventHandlers.get(eventType) || [];\n handlers.push(eventHandler as LinearWebhookEventHandler<LinearWebhookPayload>);\n eventHandlers.set(eventType, handlers);\n };\n\n handler.off = function <T extends LinearWebhookEventType>(\n eventType: T,\n eventHandler: LinearWebhookEventHandler<Extract<LinearWebhookPayload, { type: T }>>\n ): void {\n const handlers = eventHandlers.get(eventType);\n if (handlers) {\n const index = handlers.indexOf(eventHandler as LinearWebhookEventHandler<LinearWebhookPayload>);\n if (index > -1) {\n handlers.splice(index, 1);\n if (handlers.length === 0) {\n eventHandlers.delete(eventType);\n }\n }\n }\n };\n\n handler.removeAllListeners = function (eventType?: string): void {\n if (eventType) {\n eventHandlers.delete(eventType);\n } else {\n eventHandlers.clear();\n }\n };\n\n return handler as LinearWebhookHandler;\n }\n\n /**\n * Determines whether the provided value is a Fetch API `Request`.\n * Used as a type guard to select the appropriate runtime path.\n *\n * @param value - Unknown request-like value\n * @returns True if `value` is a Fetch API `Request`\n */\n private isFetchRequest(value: unknown): value is Request {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"arrayBuffer\" in value &&\n typeof Reflect.get(value, \"arrayBuffer\") === \"function\"\n );\n }\n\n /**\n * Creates an HTTP adapter for Fetch-based runtimes.\n * The body is not read until `readRawBody` is invoked.\n *\n * @param request - Fetch API `Request`\n * @returns Helpers to read input and send responses in a unified way\n */\n private createFetchAdapter(request: Request): HttpAdapter {\n return {\n method: request.method,\n signature: request.headers.get(LINEAR_WEBHOOK_SIGNATURE_HEADER),\n timestamp: request.headers.get(LINEAR_WEBHOOK_TS_HEADER),\n readRawBody: async () => Buffer.from(await request.arrayBuffer()),\n send: (status, body) => new Response(body, { status }),\n };\n }\n\n /**\n * Creates an HTTP adapter for Node.js HTTP runtimes.\n * The body stream is consumed when `readRawBody` is invoked.\n *\n * @param incomingMessage - Node.js `IncomingMessage`\n * @param res - Node.js `ServerResponse` used to write the response\n * @returns Helpers to read input and send responses in a unified way\n */\n private createNodeAdapter(incomingMessage: IncomingMessage, res: ServerResponse): HttpAdapter {\n const signatureHeader = incomingMessage.headers[LINEAR_WEBHOOK_SIGNATURE_HEADER];\n const signature = Array.isArray(signatureHeader)\n ? (signatureHeader[0] ?? null)\n : ((signatureHeader ?? null) as string | null);\n const timestampHeader = incomingMessage.headers[LINEAR_WEBHOOK_TS_HEADER];\n const timestamp = Array.isArray(timestampHeader)\n ? (timestampHeader[0] ?? null)\n : ((timestampHeader ?? null) as string | null);\n return {\n method: incomingMessage.method || \"\",\n signature,\n timestamp,\n readRawBody: async () => {\n const chunks: Buffer[] = [];\n for await (const chunk of incomingMessage) {\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n },\n send: (status, body) => {\n res.statusCode = status;\n res.end(body);\n },\n };\n }\n\n /**\n * Selects and constructs the appropriate HTTP adapter for the\n * provided request type (Fetch or Node.js HTTP).\n *\n * @param requestOrMessage - A Fetch `Request` or Node.js `IncomingMessage`\n * @param response - Node.js `ServerResponse` (required for Node path)\n * @returns An HTTP adapter with unified IO helpers\n */\n private getHttpAdapter(requestOrMessage: Request | IncomingMessage, response?: ServerResponse): HttpAdapter {\n return this.isFetchRequest(requestOrMessage)\n ? this.createFetchAdapter(requestOrMessage)\n : this.createNodeAdapter(requestOrMessage as IncomingMessage, response as ServerResponse);\n }\n\n /**\n * Parses the JSON body and verifies signature and optional timestamp.\n *\n * Throws if the JSON is invalid, the signature is invalid, or the timestamp check fails.\n *\n * @param rawBody - Raw request body as a Buffer\n * @param signature - The value of the `linear-signature` header\n * @param timestampHeader - The value of the `linear-timestamp` header (preferred over body field)\n * @returns The verified and parsed webhook payload\n */\n private parseVerifiedPayload(\n rawBody: Buffer,\n signature: string,\n timestampHeader: string | null\n ): LinearWebhookPayload {\n const parsedBody = this.parseBodyAsWebhookPayload(rawBody);\n const timestamp = timestampHeader ?? parsedBody.webhookTimestamp;\n\n const verified = this.verify(rawBody, signature, timestamp);\n if (!verified) {\n throw new Error(\"Invalid webhook signature\");\n }\n\n return parsedBody;\n }\n\n /**\n * Parses the raw body as a webhook payload with typing.\n *\n * @param rawBody - Raw request body as a Buffer\n * @returns Parsed webhook payload object\n */\n private parseBodyAsWebhookPayload(rawBody: Buffer): LinearWebhookPayload & { webhookTimestamp?: number } {\n return JSON.parse(rawBody.toString());\n }\n\n /**\n * Returns the list of handlers to invoke for a given event type,\n * including both specific and wildcard handlers.\n *\n * @param eventHandlers - Internal registry of event handlers\n * @param eventType - The webhook `type` field from the payload\n * @returns Ordered list of handlers to be executed\n */\n private collectHandlers(\n eventHandlers: Map<string, LinearWebhookEventHandler<LinearWebhookPayload>[]>,\n eventType: string\n ): LinearWebhookEventHandler<LinearWebhookPayload>[] {\n const specificHandlers = eventHandlers.get(eventType) || [];\n const wildcardHandlers = eventHandlers.get(\"*\") || [];\n return [...specificHandlers, ...wildcardHandlers];\n }\n\n /**\n * Verify the webhook signature\n *\n * Throws an error if the signature or timestamp is invalid.\n *\n * @param rawBody The webhook request raw body\n * @param signature The signature to verify\n * @param timestamp The timestamp value - either from the `linear-timestamp` header (string)\n * or the `webhookTimestamp` field from the request parsed body (number)\n * @returns True if the signature is valid\n */\n public verify(rawBody: Buffer, signature: string, timestamp?: number | string): boolean {\n const verificationBuffer = Buffer.from(crypto.createHmac(\"sha256\", this.secret).update(rawBody).digest(\"hex\"));\n const signatureBuffer = Buffer.from(signature);\n\n if (verificationBuffer.length !== signatureBuffer.length) {\n throw new Error(\"Invalid webhook signature\");\n }\n\n if (!crypto.timingSafeEqual(verificationBuffer, signatureBuffer)) {\n throw new Error(\"Invalid webhook signature\");\n }\n\n if (timestamp) {\n const timestampMs = typeof timestamp === \"string\" ? parseInt(timestamp, 10) : timestamp;\n if (isNaN(timestampMs)) {\n throw new Error(`Invalid webhook timestamp: ${timestamp}`);\n }\n const timeDiff = Math.abs(new Date().getTime() - timestampMs);\n // Throw error if more than one minute delta between provided ts and current time\n if (timeDiff > 1000 * 60) {\n throw new Error(\"Invalid webhook timestamp\");\n }\n }\n\n return true;\n }\n\n /**\n * Parse and verify webhook data, throwing an error if the signature or given timestamp is invalid.\n *\n * @param rawBody The webhook request raw body\n * @param signature The signature to verify\n * @param timestamp The timestamp value - either from the `linear-timestamp` header (string)\n * or the `webhookTimestamp` field from the request parsed body (number)\n */\n public parseData(rawBody: Buffer, signature: string, timestamp?: number | string): LinearWebhookPayload {\n const verified = this.verify(rawBody, signature, timestamp);\n if (!verified) {\n throw new Error(\"Invalid webhook signature\");\n }\n\n return this.parseBodyAsWebhookPayload(rawBody);\n }\n}\n"],"mappings":"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"}
|
|
@@ -44,6 +44,7 @@ crypto = __toESM(crypto);
|
|
|
44
44
|
|
|
45
45
|
//#region src/webhooks/client.ts
|
|
46
46
|
const LINEAR_WEBHOOK_SIGNATURE_HEADER = "linear-signature";
|
|
47
|
+
const LINEAR_WEBHOOK_TS_HEADER = "linear-timestamp";
|
|
47
48
|
const LINEAR_WEBHOOK_TS_FIELD = "webhookTimestamp";
|
|
48
49
|
/**
|
|
49
50
|
* Client for handling Linear webhook requests with helpers.
|
|
@@ -73,7 +74,7 @@ var LinearWebhookClient = class {
|
|
|
73
74
|
const rawBody = await adapter.readRawBody();
|
|
74
75
|
let parsedPayload;
|
|
75
76
|
try {
|
|
76
|
-
parsedPayload = this.parseVerifiedPayload(rawBody, signature);
|
|
77
|
+
parsedPayload = this.parseVerifiedPayload(rawBody, signature, adapter.timestamp);
|
|
77
78
|
} catch {
|
|
78
79
|
return adapter.send(400, "Invalid webhook");
|
|
79
80
|
}
|
|
@@ -126,6 +127,7 @@ var LinearWebhookClient = class {
|
|
|
126
127
|
return {
|
|
127
128
|
method: request.method,
|
|
128
129
|
signature: request.headers.get(LINEAR_WEBHOOK_SIGNATURE_HEADER),
|
|
130
|
+
timestamp: request.headers.get(LINEAR_WEBHOOK_TS_HEADER),
|
|
129
131
|
readRawBody: async () => Buffer.from(await request.arrayBuffer()),
|
|
130
132
|
send: (status, body) => new Response(body, { status })
|
|
131
133
|
};
|
|
@@ -139,11 +141,14 @@ var LinearWebhookClient = class {
|
|
|
139
141
|
* @returns Helpers to read input and send responses in a unified way
|
|
140
142
|
*/
|
|
141
143
|
createNodeAdapter(incomingMessage, res) {
|
|
142
|
-
const
|
|
143
|
-
const signature = Array.isArray(
|
|
144
|
+
const signatureHeader = incomingMessage.headers[LINEAR_WEBHOOK_SIGNATURE_HEADER];
|
|
145
|
+
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] ?? null : signatureHeader ?? null;
|
|
146
|
+
const timestampHeader = incomingMessage.headers[LINEAR_WEBHOOK_TS_HEADER];
|
|
147
|
+
const timestamp = Array.isArray(timestampHeader) ? timestampHeader[0] ?? null : timestampHeader ?? null;
|
|
144
148
|
return {
|
|
145
149
|
method: incomingMessage.method || "",
|
|
146
150
|
signature,
|
|
151
|
+
timestamp,
|
|
147
152
|
readRawBody: async () => {
|
|
148
153
|
const chunks = [];
|
|
149
154
|
for await (const chunk of incomingMessage) chunks.push(Buffer.from(chunk));
|
|
@@ -173,11 +178,13 @@ var LinearWebhookClient = class {
|
|
|
173
178
|
*
|
|
174
179
|
* @param rawBody - Raw request body as a Buffer
|
|
175
180
|
* @param signature - The value of the `linear-signature` header
|
|
181
|
+
* @param timestampHeader - The value of the `linear-timestamp` header (preferred over body field)
|
|
176
182
|
* @returns The verified and parsed webhook payload
|
|
177
183
|
*/
|
|
178
|
-
parseVerifiedPayload(rawBody, signature) {
|
|
184
|
+
parseVerifiedPayload(rawBody, signature, timestampHeader) {
|
|
179
185
|
const parsedBody = this.parseBodyAsWebhookPayload(rawBody);
|
|
180
|
-
|
|
186
|
+
const timestamp = timestampHeader ?? parsedBody.webhookTimestamp;
|
|
187
|
+
if (!this.verify(rawBody, signature, timestamp)) throw new Error("Invalid webhook signature");
|
|
181
188
|
return parsedBody;
|
|
182
189
|
}
|
|
183
190
|
/**
|
|
@@ -209,7 +216,8 @@ var LinearWebhookClient = class {
|
|
|
209
216
|
*
|
|
210
217
|
* @param rawBody The webhook request raw body
|
|
211
218
|
* @param signature The signature to verify
|
|
212
|
-
* @param timestamp The
|
|
219
|
+
* @param timestamp The timestamp value - either from the `linear-timestamp` header (string)
|
|
220
|
+
* or the `webhookTimestamp` field from the request parsed body (number)
|
|
213
221
|
* @returns True if the signature is valid
|
|
214
222
|
*/
|
|
215
223
|
verify(rawBody, signature, timestamp) {
|
|
@@ -218,7 +226,9 @@ var LinearWebhookClient = class {
|
|
|
218
226
|
if (verificationBuffer.length !== signatureBuffer.length) throw new Error("Invalid webhook signature");
|
|
219
227
|
if (!crypto.default.timingSafeEqual(verificationBuffer, signatureBuffer)) throw new Error("Invalid webhook signature");
|
|
220
228
|
if (timestamp) {
|
|
221
|
-
|
|
229
|
+
const timestampMs = typeof timestamp === "string" ? parseInt(timestamp, 10) : timestamp;
|
|
230
|
+
if (isNaN(timestampMs)) throw new Error(`Invalid webhook timestamp: ${timestamp}`);
|
|
231
|
+
if (Math.abs((/* @__PURE__ */ new Date()).getTime() - timestampMs) > 1e3 * 60) throw new Error("Invalid webhook timestamp");
|
|
222
232
|
}
|
|
223
233
|
return true;
|
|
224
234
|
}
|
|
@@ -227,7 +237,8 @@ var LinearWebhookClient = class {
|
|
|
227
237
|
*
|
|
228
238
|
* @param rawBody The webhook request raw body
|
|
229
239
|
* @param signature The signature to verify
|
|
230
|
-
* @param timestamp The
|
|
240
|
+
* @param timestamp The timestamp value - either from the `linear-timestamp` header (string)
|
|
241
|
+
* or the `webhookTimestamp` field from the request parsed body (number)
|
|
231
242
|
*/
|
|
232
243
|
parseData(rawBody, signature, timestamp) {
|
|
233
244
|
if (!this.verify(rawBody, signature, timestamp)) throw new Error("Invalid webhook signature");
|
|
@@ -248,6 +259,12 @@ Object.defineProperty(exports, 'LINEAR_WEBHOOK_TS_FIELD', {
|
|
|
248
259
|
return LINEAR_WEBHOOK_TS_FIELD;
|
|
249
260
|
}
|
|
250
261
|
});
|
|
262
|
+
Object.defineProperty(exports, 'LINEAR_WEBHOOK_TS_HEADER', {
|
|
263
|
+
enumerable: true,
|
|
264
|
+
get: function () {
|
|
265
|
+
return LINEAR_WEBHOOK_TS_HEADER;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
251
268
|
Object.defineProperty(exports, 'LinearWebhookClient', {
|
|
252
269
|
enumerable: true,
|
|
253
270
|
get: function () {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linear/sdk",
|
|
3
3
|
"description": "The Linear Client SDK for interacting with the Linear GraphQL API",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "72.0.0",
|
|
5
5
|
"author": "Linear Orbit, Inc",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "https://github.com/linear/linear",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"typescript": "^5.9",
|
|
46
46
|
"uuid": "^9.0.1",
|
|
47
47
|
"vitest": "^4.0.15",
|
|
48
|
-
"@linear/codegen-
|
|
49
|
-
"@linear/codegen-
|
|
50
|
-
"@linear/codegen-
|
|
48
|
+
"@linear/codegen-doc": "3.3.2",
|
|
49
|
+
"@linear/codegen-test": "2.2.0",
|
|
50
|
+
"@linear/codegen-sdk": "4.0.0"
|
|
51
51
|
},
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|