@prismer/sdk 1.8.2 → 1.9.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/README.md +1 -1
- package/dist/chunk-6DZX6EAA.mjs +37 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli.d.ts +2748 -4
- package/dist/cli.js +4707 -500
- package/dist/index.d.mts +507 -1
- package/dist/index.d.ts +507 -1
- package/dist/index.js +5064 -17
- package/dist/index.mjs +9749 -50
- package/dist/webhook.d.mts +114 -0
- package/dist/webhook.d.ts +114 -0
- package/dist/webhook.js +200 -0
- package/dist/webhook.mjs +175 -0
- package/package.json +5 -4
- package/dist/chunk-BWZXMXL7.mjs +0 -4762
- package/dist/chunk-VSAVCMMZ.mjs +0 -4761
- package/dist/cli.d.mts +0 -15
- package/dist/cli.mjs +0 -3838
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prismer IM Webhook Handler
|
|
3
|
+
*
|
|
4
|
+
* Receives, verifies, and parses webhook payloads from Prismer IM server.
|
|
5
|
+
* Provides framework adapters for Express and Hono.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { PrismerWebhook } from '@prismer/sdk/webhook';
|
|
10
|
+
*
|
|
11
|
+
* const webhook = new PrismerWebhook({
|
|
12
|
+
* secret: process.env.WEBHOOK_SECRET!,
|
|
13
|
+
* onMessage: async (payload) => {
|
|
14
|
+
* console.log(`[${payload.sender.displayName}]: ${payload.message.content}`);
|
|
15
|
+
* return { content: 'Got it!' };
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Express
|
|
20
|
+
* app.post('/webhook', webhook.express());
|
|
21
|
+
*
|
|
22
|
+
* // Hono
|
|
23
|
+
* app.post('/webhook', webhook.hono());
|
|
24
|
+
*
|
|
25
|
+
* // Raw fetch/Request API
|
|
26
|
+
* const response = await webhook.handle(request);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
/** Prismer IM webhook payload (POST to agent endpoint) */
|
|
30
|
+
interface WebhookPayload {
|
|
31
|
+
source: 'prismer_im';
|
|
32
|
+
event: 'message.new';
|
|
33
|
+
timestamp: number;
|
|
34
|
+
message: WebhookMessage;
|
|
35
|
+
sender: WebhookSender;
|
|
36
|
+
conversation: WebhookConversation;
|
|
37
|
+
}
|
|
38
|
+
interface WebhookMessage {
|
|
39
|
+
id: string;
|
|
40
|
+
type: string;
|
|
41
|
+
content: string;
|
|
42
|
+
senderId: string;
|
|
43
|
+
conversationId: string;
|
|
44
|
+
parentId: string | null;
|
|
45
|
+
metadata: Record<string, any>;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
}
|
|
48
|
+
interface WebhookSender {
|
|
49
|
+
id: string;
|
|
50
|
+
username: string;
|
|
51
|
+
displayName: string;
|
|
52
|
+
role: 'human' | 'agent';
|
|
53
|
+
}
|
|
54
|
+
interface WebhookConversation {
|
|
55
|
+
id: string;
|
|
56
|
+
type: 'direct' | 'group';
|
|
57
|
+
title: string | null;
|
|
58
|
+
}
|
|
59
|
+
interface WebhookReply {
|
|
60
|
+
content: string;
|
|
61
|
+
type?: 'text' | 'markdown' | 'code';
|
|
62
|
+
}
|
|
63
|
+
interface WebhookHandlerOptions {
|
|
64
|
+
/** HMAC-SHA256 secret for verifying webhook signatures */
|
|
65
|
+
secret: string;
|
|
66
|
+
/** Called when a verified webhook payload is received */
|
|
67
|
+
onMessage: (payload: WebhookPayload) => Promise<WebhookReply | void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Verify a Prismer IM webhook signature using HMAC-SHA256.
|
|
71
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
72
|
+
*/
|
|
73
|
+
declare function verifyWebhookSignature(body: string, signature: string, secret: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Parse a raw webhook body into a typed WebhookPayload.
|
|
76
|
+
* Throws if the body is not valid JSON or missing required fields.
|
|
77
|
+
*/
|
|
78
|
+
declare function parseWebhookPayload(body: string): WebhookPayload;
|
|
79
|
+
declare class PrismerWebhook {
|
|
80
|
+
private readonly secret;
|
|
81
|
+
private readonly onMessage;
|
|
82
|
+
constructor(options: WebhookHandlerOptions);
|
|
83
|
+
/** Verify an HMAC-SHA256 signature */
|
|
84
|
+
verify(body: string, signature: string): boolean;
|
|
85
|
+
/** Parse raw body into a typed WebhookPayload */
|
|
86
|
+
parse(body: string): WebhookPayload;
|
|
87
|
+
/**
|
|
88
|
+
* Process a webhook request (verify + parse + call handler).
|
|
89
|
+
* Works with the standard Web Request/Response API.
|
|
90
|
+
*/
|
|
91
|
+
handle(request: Request): Promise<Response>;
|
|
92
|
+
/**
|
|
93
|
+
* Express middleware adapter.
|
|
94
|
+
* Expects `express.raw({ type: 'application/json' })` or a raw body parser
|
|
95
|
+
* so that `req.body` is a Buffer.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), webhook.express());
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
express(): (req: any, res: any, next?: any) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Hono middleware adapter.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* app.post('/webhook', webhook.hono());
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
hono(): (c: any) => Promise<any>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { PrismerWebhook, type WebhookConversation, type WebhookHandlerOptions, type WebhookMessage, type WebhookPayload, type WebhookReply, type WebhookSender, parseWebhookPayload, verifyWebhookSignature };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prismer IM Webhook Handler
|
|
3
|
+
*
|
|
4
|
+
* Receives, verifies, and parses webhook payloads from Prismer IM server.
|
|
5
|
+
* Provides framework adapters for Express and Hono.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { PrismerWebhook } from '@prismer/sdk/webhook';
|
|
10
|
+
*
|
|
11
|
+
* const webhook = new PrismerWebhook({
|
|
12
|
+
* secret: process.env.WEBHOOK_SECRET!,
|
|
13
|
+
* onMessage: async (payload) => {
|
|
14
|
+
* console.log(`[${payload.sender.displayName}]: ${payload.message.content}`);
|
|
15
|
+
* return { content: 'Got it!' };
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Express
|
|
20
|
+
* app.post('/webhook', webhook.express());
|
|
21
|
+
*
|
|
22
|
+
* // Hono
|
|
23
|
+
* app.post('/webhook', webhook.hono());
|
|
24
|
+
*
|
|
25
|
+
* // Raw fetch/Request API
|
|
26
|
+
* const response = await webhook.handle(request);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
/** Prismer IM webhook payload (POST to agent endpoint) */
|
|
30
|
+
interface WebhookPayload {
|
|
31
|
+
source: 'prismer_im';
|
|
32
|
+
event: 'message.new';
|
|
33
|
+
timestamp: number;
|
|
34
|
+
message: WebhookMessage;
|
|
35
|
+
sender: WebhookSender;
|
|
36
|
+
conversation: WebhookConversation;
|
|
37
|
+
}
|
|
38
|
+
interface WebhookMessage {
|
|
39
|
+
id: string;
|
|
40
|
+
type: string;
|
|
41
|
+
content: string;
|
|
42
|
+
senderId: string;
|
|
43
|
+
conversationId: string;
|
|
44
|
+
parentId: string | null;
|
|
45
|
+
metadata: Record<string, any>;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
}
|
|
48
|
+
interface WebhookSender {
|
|
49
|
+
id: string;
|
|
50
|
+
username: string;
|
|
51
|
+
displayName: string;
|
|
52
|
+
role: 'human' | 'agent';
|
|
53
|
+
}
|
|
54
|
+
interface WebhookConversation {
|
|
55
|
+
id: string;
|
|
56
|
+
type: 'direct' | 'group';
|
|
57
|
+
title: string | null;
|
|
58
|
+
}
|
|
59
|
+
interface WebhookReply {
|
|
60
|
+
content: string;
|
|
61
|
+
type?: 'text' | 'markdown' | 'code';
|
|
62
|
+
}
|
|
63
|
+
interface WebhookHandlerOptions {
|
|
64
|
+
/** HMAC-SHA256 secret for verifying webhook signatures */
|
|
65
|
+
secret: string;
|
|
66
|
+
/** Called when a verified webhook payload is received */
|
|
67
|
+
onMessage: (payload: WebhookPayload) => Promise<WebhookReply | void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Verify a Prismer IM webhook signature using HMAC-SHA256.
|
|
71
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
72
|
+
*/
|
|
73
|
+
declare function verifyWebhookSignature(body: string, signature: string, secret: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Parse a raw webhook body into a typed WebhookPayload.
|
|
76
|
+
* Throws if the body is not valid JSON or missing required fields.
|
|
77
|
+
*/
|
|
78
|
+
declare function parseWebhookPayload(body: string): WebhookPayload;
|
|
79
|
+
declare class PrismerWebhook {
|
|
80
|
+
private readonly secret;
|
|
81
|
+
private readonly onMessage;
|
|
82
|
+
constructor(options: WebhookHandlerOptions);
|
|
83
|
+
/** Verify an HMAC-SHA256 signature */
|
|
84
|
+
verify(body: string, signature: string): boolean;
|
|
85
|
+
/** Parse raw body into a typed WebhookPayload */
|
|
86
|
+
parse(body: string): WebhookPayload;
|
|
87
|
+
/**
|
|
88
|
+
* Process a webhook request (verify + parse + call handler).
|
|
89
|
+
* Works with the standard Web Request/Response API.
|
|
90
|
+
*/
|
|
91
|
+
handle(request: Request): Promise<Response>;
|
|
92
|
+
/**
|
|
93
|
+
* Express middleware adapter.
|
|
94
|
+
* Expects `express.raw({ type: 'application/json' })` or a raw body parser
|
|
95
|
+
* so that `req.body` is a Buffer.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), webhook.express());
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
express(): (req: any, res: any, next?: any) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Hono middleware adapter.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* app.post('/webhook', webhook.hono());
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
hono(): (c: any) => Promise<any>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { PrismerWebhook, type WebhookConversation, type WebhookHandlerOptions, type WebhookMessage, type WebhookPayload, type WebhookReply, type WebhookSender, parseWebhookPayload, verifyWebhookSignature };
|
package/dist/webhook.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/webhook.ts
|
|
21
|
+
var webhook_exports = {};
|
|
22
|
+
__export(webhook_exports, {
|
|
23
|
+
PrismerWebhook: () => PrismerWebhook,
|
|
24
|
+
parseWebhookPayload: () => parseWebhookPayload,
|
|
25
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(webhook_exports);
|
|
28
|
+
var import_node_crypto = require("crypto");
|
|
29
|
+
function verifyWebhookSignature(body, signature, secret) {
|
|
30
|
+
if (!body || !signature || !secret) return false;
|
|
31
|
+
const sig = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
32
|
+
if (!sig) return false;
|
|
33
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(body).digest("hex");
|
|
34
|
+
if (sig.length !== expected.length) return false;
|
|
35
|
+
try {
|
|
36
|
+
return (0, import_node_crypto.timingSafeEqual)(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parseWebhookPayload(body) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(body);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error("Invalid JSON in webhook body");
|
|
47
|
+
}
|
|
48
|
+
if (!parsed || typeof parsed !== "object") {
|
|
49
|
+
throw new Error("Webhook body must be a JSON object");
|
|
50
|
+
}
|
|
51
|
+
if (parsed.source !== "prismer_im") {
|
|
52
|
+
throw new Error(`Unknown webhook source: ${parsed.source}`);
|
|
53
|
+
}
|
|
54
|
+
if (!parsed.event) {
|
|
55
|
+
throw new Error("Missing event field in webhook payload");
|
|
56
|
+
}
|
|
57
|
+
if (!parsed.message || !parsed.sender || !parsed.conversation) {
|
|
58
|
+
throw new Error("Missing required fields in webhook payload (message, sender, conversation)");
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
var PrismerWebhook = class {
|
|
63
|
+
constructor(options) {
|
|
64
|
+
if (!options.secret) {
|
|
65
|
+
throw new Error("Webhook secret is required");
|
|
66
|
+
}
|
|
67
|
+
this.secret = options.secret;
|
|
68
|
+
this.onMessage = options.onMessage;
|
|
69
|
+
}
|
|
70
|
+
/** Verify an HMAC-SHA256 signature */
|
|
71
|
+
verify(body, signature) {
|
|
72
|
+
return verifyWebhookSignature(body, signature, this.secret);
|
|
73
|
+
}
|
|
74
|
+
/** Parse raw body into a typed WebhookPayload */
|
|
75
|
+
parse(body) {
|
|
76
|
+
return parseWebhookPayload(body);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Process a webhook request (verify + parse + call handler).
|
|
80
|
+
* Works with the standard Web Request/Response API.
|
|
81
|
+
*/
|
|
82
|
+
async handle(request) {
|
|
83
|
+
if (request.method !== "POST") {
|
|
84
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
85
|
+
status: 405,
|
|
86
|
+
headers: { "Content-Type": "application/json" }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const body = await request.text();
|
|
90
|
+
const signature = request.headers.get("x-prismer-signature") || "";
|
|
91
|
+
if (!this.verify(body, signature)) {
|
|
92
|
+
return new Response(JSON.stringify({ error: "Invalid signature" }), {
|
|
93
|
+
status: 401,
|
|
94
|
+
headers: { "Content-Type": "application/json" }
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
let payload;
|
|
98
|
+
try {
|
|
99
|
+
payload = this.parse(body);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return new Response(
|
|
102
|
+
JSON.stringify({ error: err instanceof Error ? err.message : "Invalid payload" }),
|
|
103
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const reply = await this.onMessage(payload);
|
|
108
|
+
if (reply) {
|
|
109
|
+
return new Response(JSON.stringify(reply), {
|
|
110
|
+
status: 200,
|
|
111
|
+
headers: { "Content-Type": "application/json" }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
115
|
+
status: 200,
|
|
116
|
+
headers: { "Content-Type": "application/json" }
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return new Response(
|
|
120
|
+
JSON.stringify({ error: err instanceof Error ? err.message : "Handler error" }),
|
|
121
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Express middleware adapter.
|
|
127
|
+
* Expects `express.raw({ type: 'application/json' })` or a raw body parser
|
|
128
|
+
* so that `req.body` is a Buffer.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), webhook.express());
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
express() {
|
|
136
|
+
return async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const body = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf-8") : JSON.stringify(req.body);
|
|
139
|
+
const signature = req.headers["x-prismer-signature"] || "";
|
|
140
|
+
if (!this.verify(body, signature)) {
|
|
141
|
+
res.status(401).json({ error: "Invalid signature" });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let payload;
|
|
145
|
+
try {
|
|
146
|
+
payload = this.parse(body);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
res.status(400).json({ error: err instanceof Error ? err.message : "Invalid payload" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const reply = await this.onMessage(payload);
|
|
152
|
+
if (reply) {
|
|
153
|
+
res.status(200).json(reply);
|
|
154
|
+
} else {
|
|
155
|
+
res.status(200).json({ ok: true });
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Handler error" });
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Hono middleware adapter.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* app.post('/webhook', webhook.hono());
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
hono() {
|
|
171
|
+
return async (c) => {
|
|
172
|
+
const body = await c.req.text();
|
|
173
|
+
const signature = c.req.header("x-prismer-signature") || "";
|
|
174
|
+
if (!this.verify(body, signature)) {
|
|
175
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
176
|
+
}
|
|
177
|
+
let payload;
|
|
178
|
+
try {
|
|
179
|
+
payload = this.parse(body);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
return c.json({ error: err instanceof Error ? err.message : "Invalid payload" }, 400);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const reply = await this.onMessage(payload);
|
|
185
|
+
if (reply) {
|
|
186
|
+
return c.json(reply, 200);
|
|
187
|
+
}
|
|
188
|
+
return c.json({ ok: true }, 200);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return c.json({ error: err instanceof Error ? err.message : "Handler error" }, 500);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
196
|
+
0 && (module.exports = {
|
|
197
|
+
PrismerWebhook,
|
|
198
|
+
parseWebhookPayload,
|
|
199
|
+
verifyWebhookSignature
|
|
200
|
+
});
|
package/dist/webhook.mjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import "./chunk-6DZX6EAA.mjs";
|
|
2
|
+
|
|
3
|
+
// src/webhook.ts
|
|
4
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
5
|
+
function verifyWebhookSignature(body, signature, secret) {
|
|
6
|
+
if (!body || !signature || !secret) return false;
|
|
7
|
+
const sig = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
8
|
+
if (!sig) return false;
|
|
9
|
+
const expected = createHmac("sha256", secret).update(body).digest("hex");
|
|
10
|
+
if (sig.length !== expected.length) return false;
|
|
11
|
+
try {
|
|
12
|
+
return timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function parseWebhookPayload(body) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(body);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error("Invalid JSON in webhook body");
|
|
23
|
+
}
|
|
24
|
+
if (!parsed || typeof parsed !== "object") {
|
|
25
|
+
throw new Error("Webhook body must be a JSON object");
|
|
26
|
+
}
|
|
27
|
+
if (parsed.source !== "prismer_im") {
|
|
28
|
+
throw new Error(`Unknown webhook source: ${parsed.source}`);
|
|
29
|
+
}
|
|
30
|
+
if (!parsed.event) {
|
|
31
|
+
throw new Error("Missing event field in webhook payload");
|
|
32
|
+
}
|
|
33
|
+
if (!parsed.message || !parsed.sender || !parsed.conversation) {
|
|
34
|
+
throw new Error("Missing required fields in webhook payload (message, sender, conversation)");
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
var PrismerWebhook = class {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
if (!options.secret) {
|
|
41
|
+
throw new Error("Webhook secret is required");
|
|
42
|
+
}
|
|
43
|
+
this.secret = options.secret;
|
|
44
|
+
this.onMessage = options.onMessage;
|
|
45
|
+
}
|
|
46
|
+
/** Verify an HMAC-SHA256 signature */
|
|
47
|
+
verify(body, signature) {
|
|
48
|
+
return verifyWebhookSignature(body, signature, this.secret);
|
|
49
|
+
}
|
|
50
|
+
/** Parse raw body into a typed WebhookPayload */
|
|
51
|
+
parse(body) {
|
|
52
|
+
return parseWebhookPayload(body);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Process a webhook request (verify + parse + call handler).
|
|
56
|
+
* Works with the standard Web Request/Response API.
|
|
57
|
+
*/
|
|
58
|
+
async handle(request) {
|
|
59
|
+
if (request.method !== "POST") {
|
|
60
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
61
|
+
status: 405,
|
|
62
|
+
headers: { "Content-Type": "application/json" }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const body = await request.text();
|
|
66
|
+
const signature = request.headers.get("x-prismer-signature") || "";
|
|
67
|
+
if (!this.verify(body, signature)) {
|
|
68
|
+
return new Response(JSON.stringify({ error: "Invalid signature" }), {
|
|
69
|
+
status: 401,
|
|
70
|
+
headers: { "Content-Type": "application/json" }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
let payload;
|
|
74
|
+
try {
|
|
75
|
+
payload = this.parse(body);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return new Response(
|
|
78
|
+
JSON.stringify({ error: err instanceof Error ? err.message : "Invalid payload" }),
|
|
79
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const reply = await this.onMessage(payload);
|
|
84
|
+
if (reply) {
|
|
85
|
+
return new Response(JSON.stringify(reply), {
|
|
86
|
+
status: 200,
|
|
87
|
+
headers: { "Content-Type": "application/json" }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
91
|
+
status: 200,
|
|
92
|
+
headers: { "Content-Type": "application/json" }
|
|
93
|
+
});
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return new Response(
|
|
96
|
+
JSON.stringify({ error: err instanceof Error ? err.message : "Handler error" }),
|
|
97
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Express middleware adapter.
|
|
103
|
+
* Expects `express.raw({ type: 'application/json' })` or a raw body parser
|
|
104
|
+
* so that `req.body` is a Buffer.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), webhook.express());
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
express() {
|
|
112
|
+
return async (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
const body = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf-8") : JSON.stringify(req.body);
|
|
115
|
+
const signature = req.headers["x-prismer-signature"] || "";
|
|
116
|
+
if (!this.verify(body, signature)) {
|
|
117
|
+
res.status(401).json({ error: "Invalid signature" });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
let payload;
|
|
121
|
+
try {
|
|
122
|
+
payload = this.parse(body);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
res.status(400).json({ error: err instanceof Error ? err.message : "Invalid payload" });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const reply = await this.onMessage(payload);
|
|
128
|
+
if (reply) {
|
|
129
|
+
res.status(200).json(reply);
|
|
130
|
+
} else {
|
|
131
|
+
res.status(200).json({ ok: true });
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Handler error" });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Hono middleware adapter.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* app.post('/webhook', webhook.hono());
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
hono() {
|
|
147
|
+
return async (c) => {
|
|
148
|
+
const body = await c.req.text();
|
|
149
|
+
const signature = c.req.header("x-prismer-signature") || "";
|
|
150
|
+
if (!this.verify(body, signature)) {
|
|
151
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
152
|
+
}
|
|
153
|
+
let payload;
|
|
154
|
+
try {
|
|
155
|
+
payload = this.parse(body);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return c.json({ error: err instanceof Error ? err.message : "Invalid payload" }, 400);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const reply = await this.onMessage(payload);
|
|
161
|
+
if (reply) {
|
|
162
|
+
return c.json(reply, 200);
|
|
163
|
+
}
|
|
164
|
+
return c.json({ ok: true }, 200);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return c.json({ error: err instanceof Error ? err.message : "Handler error" }, 500);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export {
|
|
172
|
+
PrismerWebhook,
|
|
173
|
+
parseWebhookPayload,
|
|
174
|
+
verifyWebhookSignature
|
|
175
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prismer/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Official TypeScript SDK for Prismer Cloud API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"prismer": "./dist/cli.js"
|
|
10
|
-
},
|
|
11
8
|
"files": [
|
|
12
9
|
"dist",
|
|
13
10
|
"icon"
|
|
@@ -58,6 +55,10 @@
|
|
|
58
55
|
"types": "./dist/webhook.d.ts",
|
|
59
56
|
"import": "./dist/webhook.mjs",
|
|
60
57
|
"require": "./dist/webhook.js"
|
|
58
|
+
},
|
|
59
|
+
"./cli": {
|
|
60
|
+
"types": "./dist/cli.d.ts",
|
|
61
|
+
"require": "./dist/cli.js"
|
|
61
62
|
}
|
|
62
63
|
},
|
|
63
64
|
"publishConfig": {
|