@seathold/sdk 0.1.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 +132 -0
- package/dist/SeatingChart.d.ts +20 -0
- package/dist/index.d.ts +2 -0
- package/dist/seathold.esm.js +87 -0
- package/dist/seathold.umd.js +1 -0
- package/dist/types.d.ts +99 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# SeatHold SDK
|
|
2
|
+
|
|
3
|
+
Embed an interactive seat map in any web page.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @seathold/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or via CDN:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/@seathold/sdk/dist/seathold.umd.js"></script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<div id="seat-map"></div>
|
|
21
|
+
|
|
22
|
+
<script type="module">
|
|
23
|
+
import { SeatingChart } from '@seathold/sdk';
|
|
24
|
+
|
|
25
|
+
const chart = new SeatingChart({
|
|
26
|
+
divId: 'seat-map',
|
|
27
|
+
baseUrl: 'https://tickets.myapp.com',
|
|
28
|
+
workspaceKey: 'pub_xxxxxxxxxxxx',
|
|
29
|
+
event: 'my-event-slug',
|
|
30
|
+
onReady: (eventId) => {
|
|
31
|
+
console.log('Map ready for event:', eventId);
|
|
32
|
+
},
|
|
33
|
+
onSelectionChanged: (seatIds, ticketTypes) => {
|
|
34
|
+
console.log('Selected seats:', seatIds);
|
|
35
|
+
console.log('Ticket types chosen:', ticketTypes);
|
|
36
|
+
},
|
|
37
|
+
onHoldCreated: (holdId, expiresAt, seatIds, ticketTypes) => {
|
|
38
|
+
console.log('Hold created:', holdId);
|
|
39
|
+
},
|
|
40
|
+
}).render();
|
|
41
|
+
</script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Multiprice
|
|
45
|
+
|
|
46
|
+
Pass `pricing` to define ticket types per category. When the user clicks a seat in that category, a popover shows the available ticket types to choose from.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const chart = new SeatingChart({
|
|
50
|
+
divId: 'seat-map',
|
|
51
|
+
baseUrl: 'https://tickets.myapp.com',
|
|
52
|
+
workspaceKey: 'pub_xxxxxxxxxxxx',
|
|
53
|
+
event: 'my-event-slug',
|
|
54
|
+
|
|
55
|
+
pricing: [
|
|
56
|
+
{
|
|
57
|
+
category: 'pista',
|
|
58
|
+
ticketTypes: [
|
|
59
|
+
{ id: 'pista-inteira', label: 'Inteira', price: 120, currency: 'BRL' },
|
|
60
|
+
{ id: 'pista-meia', label: 'Meia', price: 60, currency: 'BRL', color: '#22c55e' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
category: 'vip',
|
|
65
|
+
ticketTypes: [
|
|
66
|
+
{ id: 'vip-inteira', label: 'VIP Inteira', price: 350, currency: 'BRL' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
onSelectionChanged: (seatIds, ticketTypes) => {
|
|
72
|
+
// ticketTypes: { "seat-42": "pista-inteira", "seat-43": "pista-meia" }
|
|
73
|
+
console.log(seatIds, ticketTypes);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
onHoldCreated: (holdId, expiresAt, seatIds, ticketTypes) => {
|
|
77
|
+
// Send holdId + ticketTypes to your backend to finalize the order
|
|
78
|
+
fetch('/api/orders', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
body: JSON.stringify({ holdId, ticketTypes }),
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
}).render();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
### `new SeatingChart(config)`
|
|
89
|
+
|
|
90
|
+
| Option | Type | Required | Description |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `divId` | `string` | yes | ID of the container element |
|
|
93
|
+
| `baseUrl` | `string` | yes | Base URL of your SeatHold server |
|
|
94
|
+
| `workspaceKey` | `string` | yes | Public workspace key |
|
|
95
|
+
| `event` | `string` | yes | Event slug or ID |
|
|
96
|
+
| `sessionToken` | `string` | | Session token for authenticated holds |
|
|
97
|
+
| `pricing` | `PricingRule[]` | | Multiprice rules per category |
|
|
98
|
+
| `height` | `number or string` | | iframe height (default: `600px`) |
|
|
99
|
+
| `width` | `number or string` | | iframe width (default: `100%`) |
|
|
100
|
+
| `onReady` | `(eventId) => void` | | Fired when the map finishes loading |
|
|
101
|
+
| `onSelectionChanged` | `(seatIds, ticketTypes) => void` | | Fired on every selection change |
|
|
102
|
+
| `onObjectClicked` | `(objectId, objectType) => void` | | Fired when any object is clicked |
|
|
103
|
+
| `onCategoryChanged` | `(categoryId) => void` | | Fired when active category changes |
|
|
104
|
+
| `onViewChanged` | `(zoom, position) => void` | | Fired on pan/zoom |
|
|
105
|
+
| `onHoldCreated` | `(holdId, expiresAt, seatIds, ticketTypes) => void` | | Fired after a hold is created |
|
|
106
|
+
| `onHoldReleased` | `() => void` | | Fired after a hold is released |
|
|
107
|
+
| `onError` | `(action, message) => void` | | Fired on errors |
|
|
108
|
+
|
|
109
|
+
### Instance methods
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
chart.render() // Inject the iframe and start listening
|
|
113
|
+
chart.destroy() // Remove the iframe and all listeners
|
|
114
|
+
chart.setSelectedSeats([id1, id2]) // Programmatically select seats
|
|
115
|
+
chart.createHold([id1, id2]) // Trigger a hold (optional seat list)
|
|
116
|
+
chart.releaseHold() // Release the current hold
|
|
117
|
+
chart.updateSession(token, expiresAt) // Refresh the session token
|
|
118
|
+
chart.requestState() // Ask for current state snapshot
|
|
119
|
+
chart.setPricing(rules) // Update pricing rules at runtime
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## TicketType
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
type TicketType = {
|
|
126
|
+
id: string;
|
|
127
|
+
label: string;
|
|
128
|
+
color?: string | null;
|
|
129
|
+
price?: number | null;
|
|
130
|
+
currency?: string | null;
|
|
131
|
+
};
|
|
132
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PricingRule, SeatingChartConfig } from './types';
|
|
2
|
+
export declare class SeatingChart {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private iframe;
|
|
5
|
+
private messageHandler;
|
|
6
|
+
private iframeOrigin;
|
|
7
|
+
constructor(config: SeatingChartConfig);
|
|
8
|
+
render(): this;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
setSelectedSeats(seatIds: Array<string | number>): void;
|
|
11
|
+
createHold(seatIds?: Array<string | number>): void;
|
|
12
|
+
releaseHold(): void;
|
|
13
|
+
updateSession(sessionToken: string, expiresAt?: number | null): void;
|
|
14
|
+
requestState(): void;
|
|
15
|
+
setPricing(pricing: PricingRule[]): void;
|
|
16
|
+
private send;
|
|
17
|
+
private handleMessage;
|
|
18
|
+
private buildEmbedUrl;
|
|
19
|
+
private resolveSize;
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class u {
|
|
2
|
+
constructor(e) {
|
|
3
|
+
this.iframe = null, this.messageHandler = null, this.iframeOrigin = "", this.config = e;
|
|
4
|
+
}
|
|
5
|
+
render() {
|
|
6
|
+
const e = document.getElementById(this.config.divId);
|
|
7
|
+
if (!e)
|
|
8
|
+
throw new Error(`[SeatHold] Element #${this.config.divId} not found.`);
|
|
9
|
+
const s = this.buildEmbedUrl();
|
|
10
|
+
this.iframeOrigin = new URL(this.config.baseUrl).origin;
|
|
11
|
+
const i = document.createElement("iframe");
|
|
12
|
+
return i.src = s, i.style.width = this.resolveSize(this.config.width, "100%"), i.style.height = this.resolveSize(this.config.height, "600px"), i.style.border = "none", i.allow = "fullscreen", this.iframe = i, e.innerHTML = "", e.appendChild(i), this.messageHandler = (t) => {
|
|
13
|
+
t.origin === this.iframeOrigin && this.handleMessage(t.data);
|
|
14
|
+
}, window.addEventListener("message", this.messageHandler), this;
|
|
15
|
+
}
|
|
16
|
+
destroy() {
|
|
17
|
+
this.messageHandler && (window.removeEventListener("message", this.messageHandler), this.messageHandler = null), this.iframe && (this.iframe.remove(), this.iframe = null);
|
|
18
|
+
}
|
|
19
|
+
setSelectedSeats(e) {
|
|
20
|
+
this.send({ type: "seathold:set_selected_seats", seatIds: e });
|
|
21
|
+
}
|
|
22
|
+
createHold(e) {
|
|
23
|
+
this.send({ type: "seathold:create_hold", seatIds: e });
|
|
24
|
+
}
|
|
25
|
+
releaseHold() {
|
|
26
|
+
this.send({ type: "seathold:release_hold" });
|
|
27
|
+
}
|
|
28
|
+
updateSession(e, s) {
|
|
29
|
+
this.send({ type: "seathold:update_session", sessionToken: e, expiresAt: s });
|
|
30
|
+
}
|
|
31
|
+
requestState() {
|
|
32
|
+
this.send({ type: "seathold:request_state" });
|
|
33
|
+
}
|
|
34
|
+
setPricing(e) {
|
|
35
|
+
this.send({ type: "seathold:set_pricing", pricing: e });
|
|
36
|
+
}
|
|
37
|
+
send(e) {
|
|
38
|
+
var s;
|
|
39
|
+
if (!((s = this.iframe) != null && s.contentWindow)) {
|
|
40
|
+
console.warn("[SeatHold] iframe not ready yet.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.iframe.contentWindow.postMessage(e, this.iframeOrigin);
|
|
44
|
+
}
|
|
45
|
+
handleMessage(e) {
|
|
46
|
+
var s, i, t, n, o, r, h, a, c, d, l, g, f, m, p, y;
|
|
47
|
+
switch (e.type) {
|
|
48
|
+
case "seathold:ready":
|
|
49
|
+
this.config.pricing && this.config.pricing.length > 0 && this.setPricing(this.config.pricing), (i = (s = this.config).onReady) == null || i.call(s, e.eventId);
|
|
50
|
+
break;
|
|
51
|
+
case "seathold:selection_changed":
|
|
52
|
+
(n = (t = this.config).onSelectionChanged) == null || n.call(t, e.seatIds, e.ticketTypes);
|
|
53
|
+
break;
|
|
54
|
+
case "seathold:object_clicked":
|
|
55
|
+
(r = (o = this.config).onObjectClicked) == null || r.call(o, e.objectId, e.objectType);
|
|
56
|
+
break;
|
|
57
|
+
case "seathold:category_changed":
|
|
58
|
+
(a = (h = this.config).onCategoryChanged) == null || a.call(h, e.categoryId);
|
|
59
|
+
break;
|
|
60
|
+
case "seathold:view_changed":
|
|
61
|
+
(d = (c = this.config).onViewChanged) == null || d.call(c, e.zoom, e.position);
|
|
62
|
+
break;
|
|
63
|
+
case "seathold:hold_created":
|
|
64
|
+
(g = (l = this.config).onHoldCreated) == null || g.call(l, e.holdId, e.expiresAt, e.seatIds, e.ticketTypes);
|
|
65
|
+
break;
|
|
66
|
+
case "seathold:hold_released":
|
|
67
|
+
(m = (f = this.config).onHoldReleased) == null || m.call(f);
|
|
68
|
+
break;
|
|
69
|
+
case "seathold:error":
|
|
70
|
+
(y = (p = this.config).onError) == null || y.call(p, e.action, e.message);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
buildEmbedUrl() {
|
|
75
|
+
const e = this.config.baseUrl.replace(/\/$/, ""), s = new URLSearchParams({
|
|
76
|
+
workspace_key: this.config.workspaceKey,
|
|
77
|
+
...this.config.sessionToken ? { session_token: this.config.sessionToken } : {}
|
|
78
|
+
});
|
|
79
|
+
return `${e}/embed/${this.config.event}?${s.toString()}`;
|
|
80
|
+
}
|
|
81
|
+
resolveSize(e, s) {
|
|
82
|
+
return e == null ? s : typeof e == "number" ? `${e}px` : e;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
u as SeatingChart
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(t,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(t=typeof globalThis<"u"?globalThis:t||self,n(t.SeatHold={}))})(this,function(t){"use strict";class n{constructor(e){this.iframe=null,this.messageHandler=null,this.iframeOrigin="",this.config=e}render(){const e=document.getElementById(this.config.divId);if(!e)throw new Error(`[SeatHold] Element #${this.config.divId} not found.`);const s=this.buildEmbedUrl();this.iframeOrigin=new URL(this.config.baseUrl).origin;const i=document.createElement("iframe");return i.src=s,i.style.width=this.resolveSize(this.config.width,"100%"),i.style.height=this.resolveSize(this.config.height,"600px"),i.style.border="none",i.allow="fullscreen",this.iframe=i,e.innerHTML="",e.appendChild(i),this.messageHandler=o=>{o.origin===this.iframeOrigin&&this.handleMessage(o.data)},window.addEventListener("message",this.messageHandler),this}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&(this.iframe.remove(),this.iframe=null)}setSelectedSeats(e){this.send({type:"seathold:set_selected_seats",seatIds:e})}createHold(e){this.send({type:"seathold:create_hold",seatIds:e})}releaseHold(){this.send({type:"seathold:release_hold"})}updateSession(e,s){this.send({type:"seathold:update_session",sessionToken:e,expiresAt:s})}requestState(){this.send({type:"seathold:request_state"})}setPricing(e){this.send({type:"seathold:set_pricing",pricing:e})}send(e){var s;if(!((s=this.iframe)!=null&&s.contentWindow)){console.warn("[SeatHold] iframe not ready yet.");return}this.iframe.contentWindow.postMessage(e,this.iframeOrigin)}handleMessage(e){var s,i,o,r,h,a,d,c,l,g,f,m,p,u,y,b;switch(e.type){case"seathold:ready":this.config.pricing&&this.config.pricing.length>0&&this.setPricing(this.config.pricing),(i=(s=this.config).onReady)==null||i.call(s,e.eventId);break;case"seathold:selection_changed":(r=(o=this.config).onSelectionChanged)==null||r.call(o,e.seatIds,e.ticketTypes);break;case"seathold:object_clicked":(a=(h=this.config).onObjectClicked)==null||a.call(h,e.objectId,e.objectType);break;case"seathold:category_changed":(c=(d=this.config).onCategoryChanged)==null||c.call(d,e.categoryId);break;case"seathold:view_changed":(g=(l=this.config).onViewChanged)==null||g.call(l,e.zoom,e.position);break;case"seathold:hold_created":(m=(f=this.config).onHoldCreated)==null||m.call(f,e.holdId,e.expiresAt,e.seatIds,e.ticketTypes);break;case"seathold:hold_released":(u=(p=this.config).onHoldReleased)==null||u.call(p);break;case"seathold:error":(b=(y=this.config).onError)==null||b.call(y,e.action,e.message);break}}buildEmbedUrl(){const e=this.config.baseUrl.replace(/\/$/,""),s=new URLSearchParams({workspace_key:this.config.workspaceKey,...this.config.sessionToken?{session_token:this.config.sessionToken}:{}});return`${e}/embed/${this.config.event}?${s.toString()}`}resolveSize(e,s){return e==null?s:typeof e=="number"?`${e}px`:e}}t.SeatingChart=n,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type TicketType = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
color?: string | null;
|
|
5
|
+
price?: number | null;
|
|
6
|
+
currency?: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type PricingRule = {
|
|
9
|
+
category: string;
|
|
10
|
+
ticketTypes: TicketType[];
|
|
11
|
+
};
|
|
12
|
+
export type IncomingMessage = {
|
|
13
|
+
type: 'seathold:set_selected_seats';
|
|
14
|
+
seatIds: Array<string | number>;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'seathold:create_hold';
|
|
17
|
+
seatIds?: Array<string | number>;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'seathold:release_hold';
|
|
20
|
+
} | {
|
|
21
|
+
type: 'seathold:update_session';
|
|
22
|
+
sessionToken: string;
|
|
23
|
+
expiresAt?: number | null;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'seathold:request_state';
|
|
26
|
+
} | {
|
|
27
|
+
type: 'seathold:set_pricing';
|
|
28
|
+
pricing: PricingRule[];
|
|
29
|
+
};
|
|
30
|
+
export type OutgoingMessage = {
|
|
31
|
+
type: 'seathold:ready';
|
|
32
|
+
eventId: string;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'seathold:selection_changed';
|
|
35
|
+
seatIds: Array<string | number>;
|
|
36
|
+
ticketTypes: Record<string, string | null>;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'seathold:object_clicked';
|
|
39
|
+
objectId: number | string;
|
|
40
|
+
objectType: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'seathold:category_changed';
|
|
43
|
+
categoryId: string | number | null;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'seathold:view_changed';
|
|
46
|
+
zoom: number;
|
|
47
|
+
position: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
};
|
|
51
|
+
} | {
|
|
52
|
+
type: 'seathold:hold_created';
|
|
53
|
+
holdId: number | string;
|
|
54
|
+
expiresAt: number | null;
|
|
55
|
+
seatIds: Array<string | number>;
|
|
56
|
+
ticketTypes: Record<string, string | null>;
|
|
57
|
+
} | {
|
|
58
|
+
type: 'seathold:hold_released';
|
|
59
|
+
} | {
|
|
60
|
+
type: 'seathold:state';
|
|
61
|
+
eventId: string;
|
|
62
|
+
selectedSeatIds: Array<string | number>;
|
|
63
|
+
holdId: number | string | null;
|
|
64
|
+
holdToken: string | null;
|
|
65
|
+
expiresAt: number | null;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'seathold:error';
|
|
68
|
+
action: string;
|
|
69
|
+
message: string;
|
|
70
|
+
};
|
|
71
|
+
export type SeatingChartConfig = {
|
|
72
|
+
/** DOM element ID where the iframe will be injected */
|
|
73
|
+
divId: string;
|
|
74
|
+
/** Your public workspace key */
|
|
75
|
+
workspaceKey: string;
|
|
76
|
+
/** Event slug or ID */
|
|
77
|
+
event: string;
|
|
78
|
+
/** Base URL of your SeatHold server (e.g. https://tickets.myapp.com) */
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
/** Optional session token for authenticated holds */
|
|
81
|
+
sessionToken?: string;
|
|
82
|
+
/** Multiprice rules: define ticket types per category key */
|
|
83
|
+
pricing?: PricingRule[];
|
|
84
|
+
/** iframe height (default: 600px) */
|
|
85
|
+
height?: number | string;
|
|
86
|
+
/** iframe width (default: 100%) */
|
|
87
|
+
width?: number | string;
|
|
88
|
+
onReady?: (eventId: string) => void;
|
|
89
|
+
onSelectionChanged?: (seatIds: Array<string | number>, ticketTypes: Record<string, string | null>) => void;
|
|
90
|
+
onObjectClicked?: (objectId: number | string, objectType: string) => void;
|
|
91
|
+
onCategoryChanged?: (categoryId: string | number | null) => void;
|
|
92
|
+
onViewChanged?: (zoom: number, position: {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
}) => void;
|
|
96
|
+
onHoldCreated?: (holdId: number | string, expiresAt: number | null, seatIds: Array<string | number>, ticketTypes: Record<string, string | null>) => void;
|
|
97
|
+
onHoldReleased?: () => void;
|
|
98
|
+
onError?: (action: string, message: string) => void;
|
|
99
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@seathold/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SeatHold embed SDK — renders an interactive seat map inside an iframe and communicates via postMessage",
|
|
5
|
+
"main": "dist/seathold.umd.js",
|
|
6
|
+
"module": "dist/seathold.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vite build && tsc --emitDeclarationOnly --declaration --declarationDir dist",
|
|
13
|
+
"dev": "vite build --watch"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.4.0",
|
|
17
|
+
"vite": "^5.2.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"seathold",
|
|
21
|
+
"seat-map",
|
|
22
|
+
"ticketing",
|
|
23
|
+
"sdk"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|