1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>Alter Product Customizer — postMessage (Handshake + Token)</title>
7 <style>
8 body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
9 iframe { width: 100%; height: 720px; border: 0; display: block; border-radius: 12px; }
10 pre { background: #0b1020; color: #d7e1ff; padding: 12px; border-radius: 12px; overflow:auto; }
11 </style>
12</head>
13<body>
14 <h1>My E-commerce</h1>
15
16 <iframe
17 id="customizerWidget"
18 src="https://alterproduct.com/app/customizer/1"
19 title="Alter Product Customizer"
20 allowfullscreen>
21 </iframe>
22
23 <h3>Logs</h3>
24 <pre id="log"></pre>
25
26 <script>
27 const IFRAME_ORIGIN = 'https://alterproduct.com';
28 const iframe = document.getElementById('customizerWidget');
29 const logEl = document.getElementById('log');
30
31 let handshakeOk = false;
32
33 function log(...args) {
34 const line = args.map(a => (typeof a === 'string' ? a : JSON.stringify(a, null, 2))).join(' ');
35 logEl.textContent += line + '\n';
36 }
37
38 function postToIframe(payload) {
39 iframe.contentWindow.postMessage(payload, IFRAME_ORIGIN);
40 }
41
42 4546
47 async function getSessionTokenFromYourBackend({ tool, mode, uiDesignId }) {
48 const url = new URL('/api/alter/session-token', window.location.origin);
49 url.searchParams.set('tool', tool); // viewer | configurator | customizer
50 url.searchParams.set('mode', mode || 'design');
51 url.searchParams.set('uiDesignId', String(uiDesignId || 0));
52
53 const res = await fetch(url.toString(), { method: 'GET' });
54 if (!res.ok) throw new Error('Failed to get session token');
55 const data = await res.json();
56 if (!data || !data.sessionToken) throw new Error('Missing sessionToken');
57 return data.sessionToken;
58 }
59
60 window.addEventListener('message', async (event) => {
61 // ✅ 1) Validate origin
62 if (event.origin !== IFRAME_ORIGIN) return;
63
64 // ✅ 2) Validate source
65 if (event.source !== iframe.contentWindow) return;
66
67 const msg = event.data || {};
68 if (!msg.type || typeof msg.type !== 'string') return;
69
70 // -----------------------
71 // A) HANDSHAKE
72 // Customizer -> Host: ALTER_CHILD_HELLO { nonce }
73 // Host -> Customizer: ALTER_PARENT_ACK { nonce }
74 // -----------------------
75 if (msg.type === 'ALTER_CHILD_HELLO') {
76 const nonce = msg.nonce;
77 if (!nonce || typeof nonce !== 'string') return;
78
79 handshakeOk = true;
80 log('[Customizer] -> Host: ALTER_CHILD_HELLO', { nonce });
81
82 log('[Host] -> Customizer: ALTER_PARENT_ACK');
83 postToIframe({ type: 'ALTER_PARENT_ACK', nonce });
84 return;
85 }
86
87 // Ignore everything until handshake is done
88 if (!handshakeOk) return;
89
90 // -----------------------
91 // B) TOKEN INIT (on-demand)
92 // Customizer -> Host: ALTER_TOOL_INIT_SESSION { payload: { tool, mode, uiDesignId } }
93 // Host -> Customizer: ALTER_TOOL_SESSION_READY { token }
94 // -----------------------
95 if (msg.type === 'ALTER_TOOL_INIT_SESSION') {
96 try {
97 const payload = msg.payload || {};
98 const tool = String(payload.tool || 'customizer').toLowerCase();
99 const mode = String(payload.mode || 'design');
100 const uiDesignId = Number(payload.uiDesignId || 0);
101
102 log('[Customizer] -> Host: ALTER_TOOL_INIT_SESSION', { tool, mode, uiDesignId });
103
104 const token = await getSessionTokenFromYourBackend({ tool, mode, uiDesignId });
105
106 log('[Host] -> Customizer: ALTER_TOOL_SESSION_READY');
107 postToIframe({ type: 'ALTER_TOOL_SESSION_READY', token });
108 } catch (e) {
109 log('[Host] -> Customizer: ALTER_TOOL_SESSION_ERROR', String(e && e.message ? e.message : e));
110 postToIframe({ type: 'ALTER_TOOL_SESSION_ERROR', error: String(e && e.message ? e.message : e) });
111 }
112 return;
113 }
114
115 // -----------------------
116 // C) Customizer events
117 // -----------------------
118 if (msg.type === 'ALTER_TOOL_READY') {
119 log('[Customizer] -> Host: ALTER_TOOL_READY', msg);
120 return;
121 }
122
123 if (msg.type === 'ALTER_CUSTOMIZER_ADD_TO_CART') {
124 log('[Customizer] -> Host: ALTER_CUSTOMIZER_ADD_TO_CART', msg.payload);
125 // Send payload to your backend and add item to cart in your e-commerce system
126 return;
127 }
128
129 if (msg.type === 'ALTER_TOOL_ERROR') {
130 log('[Customizer] -> Host: ALTER_TOOL_ERROR', msg);
131 return;
132 }
133 });
134 </script>
135</body>
136</html>
{
"userDesignId": 10,
"designName": "Mug 450ml (15oz)",
"totalPrice": {
"value": 0,
"currency": "EUR"
},
"productGroup": {
"id": 1,
"name": {
"pl": "Kubek 450ml (15oz)",
"en": "Mug 450ml (15oz)"
}
},
"productItems": [
{
"model3d": {
"id": 3
},
"size": {
"id": 9,
"name": {
"pl": "450ml (15oz)",
"en": "450ml (15oz)"
},
"measureSize": {
"D": 8.65,
"H": 11.95
},
"externalMapping": {
"attribute": {
"internalId": 123,
"slug": "pa_size",
"label": "Size"
},
"term": {
"internalId": 456,
"slug": "450ml-15oz",
"label": "450ml (15oz)"
}
}
},
"material": {
"id": 3,
"name": {
"pl": "Ceramika",
"en": "Ceramic"
},
"externalMapping": {
"attribute": {
"internalId": 124,
"slug": "pa_material",
"label": "Material"
},
"term": {
"internalId": 457,
"slug": "ceramic",
"label": "Ceramic"
}
}
},
"printMethod": {
"id": 5,
"name": {
"pl": "Sublimacja",
"en": "Sublimation"
},
"externalMapping": {
"attribute": {
"internalId": 125,
"slug": "pa_printing-method",
"label": "Printing method"
},
"term": {
"internalId": 458,
"slug": "sublimation",
"label": "Sublimation"
}
}
},
"color": {
"id": 11,
"name": {
"pl": "Domyślny",
"en": "Default"
},
"hex": "#FFFFFF",
"externalMapping": {
"attribute": {
"internalId": 126,
"slug": "pa_color",
"label": "Color"
},
"term": {
"internalId": 459,
"slug": "default",
"label": "Default"
}
}
},
"variant": {
"id": 11,
"productGroupId": 1,
"productModel3dId": 3,
"sizeId": 9,
"materialId": 3,
"printTypeId": 5,
"colorId": 11,
"metadata": null,
"minOrderQuantity": null,
"processingTime": null,
"stockQuantity": null,
"volume": null,
"weight": null
},
"unitPrice": {
"value": 0,
"currency": "EUR"
},
"totalPrice": {
"value": 0,
"currency": "EUR"
},
"quantity": 1
}
]
}