@venue-family/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.
- package/README.md +123 -0
- package/dist/client.d.ts +117 -0
- package/dist/client.js +231 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.js +2 -0
- package/dist/webhooks.d.ts +21 -0
- package/dist/webhooks.js +63 -0
- package/package.json +29 -0
- package/src/client.ts +275 -0
- package/src/index.ts +3 -0
- package/src/types.ts +218 -0
- package/src/webhooks.ts +102 -0
package/src/webhooks.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { VenueFamilyEmbedEvent } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verify an incoming Venue Family webhook or conversion postback HMAC-SHA256 signature.
|
|
5
|
+
* Works in Node.js, Deno, Bun, and Web standard runtimes (Cloudflare Workers, Browsers).
|
|
6
|
+
*/
|
|
7
|
+
export async function verifyWebhookSignature(
|
|
8
|
+
payload: string,
|
|
9
|
+
signatureHeader: string,
|
|
10
|
+
secret: string
|
|
11
|
+
): Promise<boolean> {
|
|
12
|
+
if (!signatureHeader || !secret) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const expectedPrefix = 'sha256=';
|
|
17
|
+
const provided = signatureHeader.startsWith(expectedPrefix)
|
|
18
|
+
? signatureHeader.slice(expectedPrefix.length)
|
|
19
|
+
: signatureHeader;
|
|
20
|
+
|
|
21
|
+
// Standard Web Crypto API
|
|
22
|
+
const encoder = new TextEncoder();
|
|
23
|
+
const key = await crypto.subtle.importKey(
|
|
24
|
+
'raw',
|
|
25
|
+
encoder.encode(secret),
|
|
26
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
27
|
+
false,
|
|
28
|
+
['sign']
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const signatureBuffer = await crypto.subtle.sign(
|
|
32
|
+
'HMAC',
|
|
33
|
+
key,
|
|
34
|
+
encoder.encode(payload)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const hexHash = Array.from(new Uint8Array(signatureBuffer))
|
|
38
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
39
|
+
.join('');
|
|
40
|
+
|
|
41
|
+
return hexHash === provided;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EmbedListenerOptions {
|
|
45
|
+
allowedOrigin?: string;
|
|
46
|
+
onResize?: (height: number) => void;
|
|
47
|
+
onTicketPurchase?: (event: Extract<VenueFamilyEmbedEvent, { type: 'ticket-purchase-complete' }>) => void;
|
|
48
|
+
onFormSubmitted?: (event: Extract<VenueFamilyEmbedEvent, { type: 'form-submitted' }>) => void;
|
|
49
|
+
onEvent?: (event: VenueFamilyEmbedEvent) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convenience helper to attach an iframe auto-resize and event listener to any web page.
|
|
54
|
+
*/
|
|
55
|
+
export function attachEmbedListener(
|
|
56
|
+
iframe: HTMLIFrameElement | string,
|
|
57
|
+
options: EmbedListenerOptions = {}
|
|
58
|
+
): () => void {
|
|
59
|
+
const allowedOrigin = options.allowedOrigin ?? 'https://venuefamily.com';
|
|
60
|
+
|
|
61
|
+
const handler = (event: MessageEvent) => {
|
|
62
|
+
if (allowedOrigin !== '*' && event.origin !== allowedOrigin) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = event.data as VenueFamilyEmbedEvent;
|
|
67
|
+
if (!data || typeof data !== 'object' || !('type' in data)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const frameEl = typeof iframe === 'string' ? (document.getElementById(iframe) as HTMLIFrameElement | null) : iframe;
|
|
72
|
+
|
|
73
|
+
if (data.type === 'resize' && frameEl && typeof data.height === 'number') {
|
|
74
|
+
frameEl.style.height = `${data.height}px`;
|
|
75
|
+
options.onResize?.(data.height);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (data.type === 'scroll-into-view' && frameEl && typeof data.top === 'number') {
|
|
79
|
+
const rect = frameEl.getBoundingClientRect();
|
|
80
|
+
window.scrollTo({
|
|
81
|
+
top: window.scrollY + rect.top + data.top - 20,
|
|
82
|
+
behavior: 'smooth',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (data.type === 'ticket-purchase-complete') {
|
|
87
|
+
options.onTicketPurchase?.(data);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (data.type === 'form-submitted') {
|
|
91
|
+
options.onFormSubmitted?.(data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
options.onEvent?.(data);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
window.addEventListener('message', handler);
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
window.removeEventListener('message', handler);
|
|
101
|
+
};
|
|
102
|
+
}
|