@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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhooks-CgtCOSvZ.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_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 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);\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 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 headerValue = incomingMessage.headers[LINEAR_WEBHOOK_SIGNATURE_HEADER];\n const signature = Array.isArray(headerValue) ? (headerValue[0] ?? null) : ((headerValue ?? null) as string | null);\n return {\n method: incomingMessage.method || \"\",\n signature,\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 * @returns The verified and parsed webhook payload\n */\n private parseVerifiedPayload(rawBody: Buffer, signature: string): LinearWebhookPayload {\n const parsedBody = this.parseBodyAsWebhookPayload(rawBody);\n\n const verified = this.verify(rawBody, signature, parsedBody.webhookTimestamp);\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 `webhookTimestamp` field from the request parsed body\n * @returns True if the signature is valid\n */\n public verify(rawBody: Buffer, signature: string, timestamp?: number): 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 timeDiff = Math.abs(new Date().getTime() - timestamp);\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 `webhookTimestamp` field from the request parsed body\n */\n public parseData(rawBody: Buffer, signature: string, timestamp?: number): 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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"}