@layercode/node-server-sdk
Version:
Layercode Node.js Server Side SDK
77 lines (73 loc) • 2.85 kB
JavaScript
import { createHmac } from 'crypto';
/**
* Creates a server-sent events (SSE) stream response for the Layercode pipeline.
* @param requestBody - The full request body (parsed JSON) from the webhook route.
* @param handler - Async function that receives helpers for writing to the stream.
* @returns Response object
*/
function streamResponse(requestBody, handler) {
let streamController;
const encoder = new TextEncoder();
const body = requestBody || {};
// Helper to format and enqueue SSE events, merging turn_id if present
function sendEvent(eventType, content) {
const merged = { ...content };
if (body.turn_id)
merged.turn_id = body.turn_id;
const sse = `data: ${JSON.stringify({ type: eventType, ...merged })}\n\n`;
streamController.enqueue(encoder.encode(sse));
}
// Write helpers
const stream = {
tts: (content) => sendEvent('response.tts', { content }),
ttsTextStream: async (textStream) => {
for await (const chunk of textStream) {
stream.tts(chunk);
}
},
data: (content) => sendEvent('response.data', { content }),
// other: (type: string, payload: any) => sendEvent(type, payload),
end: () => {
sendEvent('response.end', {});
streamController.close();
},
};
const readable = new ReadableStream({
async start(controller) {
streamController = controller;
try {
await handler({ stream });
}
catch (err) {
sendEvent('response.error', { error: err.message });
controller.close();
}
},
});
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
'Content-Encoding': 'none',
},
});
}
// timestampTolerance is the number of seconds that the timestamp can be off by. Default is 300 seconds (5 minutes).
function verifySignature({ payload, signature, secret, timestampTolerance = 300 }) {
// signature is expected to be in the format: t=timestamp,v1=signature
const match = signature.match(/t=(\d+),v1=([a-fA-F0-9]+)/);
if (!match)
return false;
const timestamp = parseInt(match[1], 10);
const v1 = match[2];
const now = Math.floor(Date.now() / 1000); // current time in seconds
if (Math.abs(now - timestamp) > timestampTolerance) {
return false;
}
const signedPayload = `${timestamp}.${payload}`;
const expected = createHmac('sha256', secret).update(signedPayload).digest('hex');
return expected === v1;
}
export { streamResponse, verifySignature };
//# sourceMappingURL=index.js.map