@ticketlayer/backstage 0.2.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.
@@ -0,0 +1,62 @@
1
+ export declare const EMBED_PROTOCOL_VERSION: 1;
2
+ export type EmbedWidget = 'events' | 'event' | 'orders' | 'order';
3
+ export interface EmbedWidgetParams {
4
+ events: {
5
+ action?: 'new';
6
+ };
7
+ event: {
8
+ id: string;
9
+ tab?: 'details' | 'tickets' | 'occurrences';
10
+ };
11
+ orders: Record<string, never>;
12
+ order: {
13
+ id: string;
14
+ };
15
+ }
16
+ export interface EmbedWidgetTarget<W extends EmbedWidget = EmbedWidget> {
17
+ widget: W;
18
+ params?: EmbedWidgetParams[W];
19
+ }
20
+ export type WidgetMessage = {
21
+ type: 'tl:ready';
22
+ version: 1;
23
+ } | {
24
+ type: 'tl:height';
25
+ version: 1;
26
+ height: number;
27
+ } | {
28
+ type: 'tl:navigate';
29
+ version: 1;
30
+ widget: EmbedWidget;
31
+ params: Record<string, string>;
32
+ } | {
33
+ type: 'tl:event';
34
+ version: 1;
35
+ name: string;
36
+ payload: unknown;
37
+ } | {
38
+ type: 'tl:session-expired';
39
+ version: 1;
40
+ reason?: string;
41
+ } | {
42
+ type: 'tl:error';
43
+ version: 1;
44
+ code: string;
45
+ message: string;
46
+ };
47
+ export interface SessionMessage {
48
+ type: 'tl:session';
49
+ version: 1;
50
+ token: string;
51
+ }
52
+ export declare function widgetPath<W extends EmbedWidget>(widget: W, params?: EmbedWidgetParams[W]): string;
53
+ export interface EmbedUrlInput<W extends EmbedWidget = EmbedWidget> extends EmbedWidgetTarget<W> {
54
+ baseUrl: string;
55
+ session: string;
56
+ parentOrigin: string;
57
+ }
58
+ export declare function buildEmbedUrl<W extends EmbedWidget>(input: EmbedUrlInput<W>): string;
59
+ export declare function originOf(url: string): string | null;
60
+ export declare function parseWidgetMessage(data: unknown, eventOrigin: string, widgetOrigin: string): WidgetMessage | null;
61
+ export declare function buildSessionMessage(token: string): SessionMessage;
62
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/embed/helpers.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAEhC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,KAAK,CAAA;KAAE,CAAC;IAE3B,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,aAAa,CAAA;KAAE,CAAC;IAEnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAE9B,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACpE,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC/B;AAGD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,CAAC,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACxF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAGpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAUD,wBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,CAsBlG;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,CAAE,SAAQ,iBAAiB,CAAC,CAAC,CAAC;IAE9F,OAAO,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,MAAM,CAAC;IAEhB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAMpF;AAGD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQnD;AAOD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,aAAa,GAAG,IAAI,CAoCtB;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAKjE"}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EMBED_PROTOCOL_VERSION = void 0;
4
+ exports.widgetPath = widgetPath;
5
+ exports.buildEmbedUrl = buildEmbedUrl;
6
+ exports.originOf = originOf;
7
+ exports.parseWidgetMessage = parseWidgetMessage;
8
+ exports.buildSessionMessage = buildSessionMessage;
9
+ exports.EMBED_PROTOCOL_VERSION = 1;
10
+ const WIDGET_TYPES = new Set(['tl:ready', 'tl:height', 'tl:navigate', 'tl:event', 'tl:session-expired', 'tl:error']);
11
+ const WIDGETS = new Set(['events', 'event', 'orders', 'order']);
12
+ function segment(value) {
13
+ return encodeURIComponent(value);
14
+ }
15
+ function widgetPath(widget, params) {
16
+ switch (widget) {
17
+ case 'events': {
18
+ const p = (params ?? {});
19
+ return p.action === 'new' ? '/embed/events/new' : '/embed/events';
20
+ }
21
+ case 'event': {
22
+ const p = params;
23
+ if (!p || !p.id)
24
+ throw new Error('The event widget needs params.id');
25
+ const base = `/embed/events/${segment(p.id)}`;
26
+ return p.tab && p.tab !== 'details' ? `${base}/${p.tab}` : base;
27
+ }
28
+ case 'orders':
29
+ return '/embed/orders';
30
+ case 'order': {
31
+ const p = params;
32
+ if (!p || !p.id)
33
+ throw new Error('The order widget needs params.id');
34
+ return `/embed/orders/${segment(p.id)}`;
35
+ }
36
+ default:
37
+ throw new Error(`Unknown widget: ${String(widget)}`);
38
+ }
39
+ }
40
+ function buildEmbedUrl(input) {
41
+ const base = input.baseUrl.replace(/\/+$/, '');
42
+ const query = `session=${encodeURIComponent(input.session)}` +
43
+ `&parent_origin=${encodeURIComponent(input.parentOrigin)}`;
44
+ return `${base}${widgetPath(input.widget, input.params)}?${query}`;
45
+ }
46
+ function originOf(url) {
47
+ try {
48
+ const parsed = new URL(url);
49
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
50
+ return null;
51
+ return parsed.origin;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function parseWidgetMessage(data, eventOrigin, widgetOrigin) {
58
+ if (eventOrigin !== widgetOrigin)
59
+ return null;
60
+ if (!data || typeof data !== 'object')
61
+ return null;
62
+ const m = data;
63
+ if (m.version !== exports.EMBED_PROTOCOL_VERSION)
64
+ return null;
65
+ if (typeof m.type !== 'string' || !WIDGET_TYPES.has(m.type))
66
+ return null;
67
+ switch (m.type) {
68
+ case 'tl:height':
69
+ return typeof m.height === 'number' && Number.isFinite(m.height) && m.height >= 0
70
+ ? { type: 'tl:height', version: 1, height: m.height }
71
+ : null;
72
+ case 'tl:navigate': {
73
+ if (typeof m.widget !== 'string' || !WIDGETS.has(m.widget))
74
+ return null;
75
+ const params = {};
76
+ if (m.params && typeof m.params === 'object') {
77
+ for (const [key, value] of Object.entries(m.params)) {
78
+ if (typeof value === 'string')
79
+ params[key] = value;
80
+ }
81
+ }
82
+ return { type: 'tl:navigate', version: 1, widget: m.widget, params };
83
+ }
84
+ case 'tl:event':
85
+ return typeof m.name === 'string' && m.name.length > 0
86
+ ? { type: 'tl:event', version: 1, name: m.name, payload: m.payload }
87
+ : null;
88
+ case 'tl:error':
89
+ return typeof m.code === 'string' && typeof m.message === 'string'
90
+ ? { type: 'tl:error', version: 1, code: m.code, message: m.message }
91
+ : null;
92
+ case 'tl:session-expired':
93
+ return { type: 'tl:session-expired', version: 1, ...(typeof m.reason === 'string' ? { reason: m.reason } : {}) };
94
+ case 'tl:ready':
95
+ return { type: 'tl:ready', version: 1 };
96
+ default:
97
+ return null;
98
+ }
99
+ }
100
+ function buildSessionMessage(token) {
101
+ if (typeof token !== 'string' || token.length === 0) {
102
+ throw new Error('A session token is required');
103
+ }
104
+ return { type: 'tl:session', version: exports.EMBED_PROTOCOL_VERSION, token };
105
+ }
@@ -0,0 +1,27 @@
1
+ import { type EmbedWidget, type EmbedWidgetParams, type EmbedWidgetTarget } from './helpers';
2
+ export { EMBED_PROTOCOL_VERSION, buildEmbedUrl, buildSessionMessage, originOf, parseWidgetMessage, widgetPath, } from './helpers';
3
+ export type { EmbedWidget, EmbedWidgetParams, EmbedWidgetTarget, EmbedUrlInput, SessionMessage, WidgetMessage, } from './helpers';
4
+ export interface BackstageEmbedError {
5
+ code: string;
6
+ message: string;
7
+ }
8
+ export interface BackstageEmbedOptions {
9
+ baseUrl: string;
10
+ session: string;
11
+ sessionProvider?: () => Promise<string> | string;
12
+ onSessionExpired?: () => void;
13
+ onEvent?: (name: string, payload: unknown) => void;
14
+ onNavigate?: (widget: EmbedWidget, params: Record<string, string>) => void;
15
+ onError?: (error: BackstageEmbedError) => void;
16
+ onReady?: () => void;
17
+ initialHeight?: number;
18
+ }
19
+ export interface BackstageEmbed {
20
+ mount<W extends EmbedWidget>(el: HTMLElement, target: EmbedWidgetTarget<W>): HTMLIFrameElement;
21
+ navigate<W extends EmbedWidget>(widget: W, params?: EmbedWidgetParams[W]): void;
22
+ setSession(token: string): void;
23
+ unmount(): void;
24
+ readonly iframe: HTMLIFrameElement | null;
25
+ }
26
+ export declare function createBackstageEmbed(options: BackstageEmbedOptions): BackstageEmbed;
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embed/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAEvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,QAAQ,EACR,kBAAkB,EAClB,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAEpC,OAAO,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,MAAM,CAAC;IAOhB,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAEjD,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAEnD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC3E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAE/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAE7B,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC;IAE/F,QAAQ,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAEhF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC,OAAO,IAAI,IAAI,CAAC;IAEhB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC3C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAiHnF"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.widgetPath = exports.parseWidgetMessage = exports.originOf = exports.buildSessionMessage = exports.buildEmbedUrl = exports.EMBED_PROTOCOL_VERSION = void 0;
4
+ exports.createBackstageEmbed = createBackstageEmbed;
5
+ const helpers_1 = require("./helpers");
6
+ var helpers_2 = require("./helpers");
7
+ Object.defineProperty(exports, "EMBED_PROTOCOL_VERSION", { enumerable: true, get: function () { return helpers_2.EMBED_PROTOCOL_VERSION; } });
8
+ Object.defineProperty(exports, "buildEmbedUrl", { enumerable: true, get: function () { return helpers_2.buildEmbedUrl; } });
9
+ Object.defineProperty(exports, "buildSessionMessage", { enumerable: true, get: function () { return helpers_2.buildSessionMessage; } });
10
+ Object.defineProperty(exports, "originOf", { enumerable: true, get: function () { return helpers_2.originOf; } });
11
+ Object.defineProperty(exports, "parseWidgetMessage", { enumerable: true, get: function () { return helpers_2.parseWidgetMessage; } });
12
+ Object.defineProperty(exports, "widgetPath", { enumerable: true, get: function () { return helpers_2.widgetPath; } });
13
+ function createBackstageEmbed(options) {
14
+ const widgetOrigin = (0, helpers_1.originOf)(options.baseUrl);
15
+ if (!widgetOrigin) {
16
+ throw new Error(`createBackstageEmbed: baseUrl must be an http(s) URL, got "${options.baseUrl}"`);
17
+ }
18
+ let session = options.session;
19
+ let iframe = null;
20
+ let listener = null;
21
+ let refreshing = null;
22
+ const parentOrigin = () => window.location.origin;
23
+ const post = (message) => {
24
+ iframe?.contentWindow?.postMessage(message, widgetOrigin);
25
+ };
26
+ const setSession = (token) => {
27
+ session = token;
28
+ post((0, helpers_1.buildSessionMessage)(token));
29
+ };
30
+ const refresh = () => {
31
+ if (refreshing)
32
+ return refreshing;
33
+ refreshing = (async () => {
34
+ if (!options.sessionProvider) {
35
+ options.onSessionExpired?.();
36
+ return;
37
+ }
38
+ try {
39
+ const token = await options.sessionProvider();
40
+ setSession(token);
41
+ }
42
+ catch (err) {
43
+ options.onError?.({ code: 'session-refresh-failed', message: err instanceof Error ? err.message : String(err) });
44
+ options.onSessionExpired?.();
45
+ }
46
+ })().finally(() => {
47
+ refreshing = null;
48
+ });
49
+ return refreshing;
50
+ };
51
+ const handle = (message) => {
52
+ switch (message.type) {
53
+ case 'tl:ready':
54
+ options.onReady?.();
55
+ break;
56
+ case 'tl:height':
57
+ if (iframe)
58
+ iframe.style.height = `${Math.ceil(message.height)}px`;
59
+ break;
60
+ case 'tl:navigate':
61
+ options.onNavigate?.(message.widget, message.params);
62
+ break;
63
+ case 'tl:event':
64
+ options.onEvent?.(message.name, message.payload);
65
+ break;
66
+ case 'tl:session-expired':
67
+ void refresh();
68
+ break;
69
+ case 'tl:error':
70
+ options.onError?.({ code: message.code, message: message.message });
71
+ break;
72
+ }
73
+ };
74
+ const unmount = () => {
75
+ if (listener) {
76
+ window.removeEventListener('message', listener);
77
+ listener = null;
78
+ }
79
+ iframe?.remove();
80
+ iframe = null;
81
+ };
82
+ const mount = (el, target) => {
83
+ unmount();
84
+ const frame = document.createElement('iframe');
85
+ frame.src = (0, helpers_1.buildEmbedUrl)({ baseUrl: options.baseUrl, session, parentOrigin: parentOrigin(), ...target });
86
+ frame.title = 'Backstage';
87
+ frame.style.width = '100%';
88
+ frame.style.border = '0';
89
+ frame.style.display = 'block';
90
+ frame.style.height = `${options.initialHeight ?? 480}px`;
91
+ frame.setAttribute('allow', 'clipboard-write');
92
+ el.appendChild(frame);
93
+ iframe = frame;
94
+ listener = (event) => {
95
+ if (!iframe || event.source !== iframe.contentWindow)
96
+ return;
97
+ const message = (0, helpers_1.parseWidgetMessage)(event.data, event.origin, widgetOrigin);
98
+ if (message)
99
+ handle(message);
100
+ };
101
+ window.addEventListener('message', listener);
102
+ return frame;
103
+ };
104
+ const navigate = (widget, params) => {
105
+ if (!iframe)
106
+ throw new Error('navigate() called before mount()');
107
+ iframe.src = (0, helpers_1.buildEmbedUrl)({ baseUrl: options.baseUrl, session, parentOrigin: parentOrigin(), widget, params });
108
+ };
109
+ return {
110
+ mount,
111
+ navigate,
112
+ setSession,
113
+ unmount,
114
+ get iframe() {
115
+ return iframe;
116
+ },
117
+ };
118
+ }