Configurator - tovejskommunikation via postMessage Indholdsfortegnelse Indlejrede Configurator-instanser bruger samme sikre transport som Viewer: et nonce-håndtryk efterfulgt af et værktøjssessionstoken, der anmodes om via ALTER_TOOL_INIT_SESSION.
Opsætning:
Åbn fanen E-handelsindstillinger i Alter Product-appen. Angiv det domæne, hvor iframen skal indlejres, i afsnittet Integration (f.eks. https://yourwebsite.com eller http://localhost:3000 ). Forløb:
Dit websted indlæser Configurator-iframen. Iframen sender ALTER_CHILD_HELLO; din side svarer med ALTER_PARENT_ACK og samme nonce. Iframen anmoder om et token med ALTER_TOOL_INIT_SESSION og payload.tool sat til configurator. Din backend opretter tokenet via Alter Product Public API, og din side svarer med ALTER_TOOL_SESSION_READY. Med add_to_cart=1 kan Configurator sende ALTER_CONFIGURATOR_ADD_TO_CART. Værten kan også anmode om den aktuelle payload med ALTER_CONFIGURATOR_GET_PRODUCT_DATA og modtage ALTER_CONFIGURATOR_DATA_RESPONSE. Variantmetadata, der er konfigureret i Produktkonfiguration, indgår i payloaden.
Type Beskrivelse ALTER_CHILD_HELLOStarter håndtryk (indeholder en nonce) ALTER_TOOL_INIT_SESSIONAnmoder om et sessionstoken fra værtswebstedet ALTER_CONFIGURATOR_ADD_TO_CARTBrugeren klikkede på ”Læg i kurv” i en indlejret Configurator med add_to_cart=1 ALTER_CONFIGURATOR_DATA_RESPONSESvar på anmodning om produktdata ALTER_WP_RUNTIME_CONTEXT_REQUESTAnmodning om WordPress/WooCommerce-runtimekontekst ALTER_WP_LOCAL_DESIGN_REQUESTAnmodning om opstartsdata til lokalt WordPress-design, når wp_local_design=1
Type Beskrivelse ALTER_PARENT_ACKBekræftelse af håndtryk (skal indeholde noncen) ALTER_TOOL_SESSION_READYLeverer sessionstoken til godkendelse af Configurator ALTER_TOOL_SESSION_ERRORTokenanmodning mislykkedes (fejl på værtssiden) ALTER_CONFIGURATOR_GET_PRODUCT_DATAAnmoder om konfigurerede produktdata fra Configurator ALTER_WP_RUNTIME_CONTEXT_RESPONSESvar med WordPress/WooCommerce-runtimekontekst ALTER_WP_LOCAL_DESIGN_RESPONSESvar med opstartsdata til lokalt WordPress-design
1 <! DOCTYPE html >
2 < html lang = " en " >
3 < head >
4 < meta charset = " UTF-8 " />
5 < title > Alter Product Configurator Communication (Handshake + Token + postMessage) </ title >
6 < meta name = " viewport " content = " width=device-width, initial-scale=1 " />
7 < style >
8 body { font-family : system-ui , -apple-system , Segoe UI , Roboto , sans-serif ; margin : 24 px ; }
9 iframe { width : 100 % ; height : 600 px ; border : 0 ; display : block ; border-radius : 12 px ; }
10 .row { display : flex ; gap : 12 px ; flex-wrap : wrap ; margin-bottom : 12 px ; }
11 button { padding : 10 px 14 px ; border-radius : 10 px ; border : 1 px solid #ddd ; background : #fff ; cursor : pointer ; }
12 button :disabled { opacity : 0.5 ; cursor : not-allowed ; }
13 pre { background : #0b1020 ; color : #d7e1ff ; padding : 12 px ; border-radius : 12 px ; overflow : auto ; }
14 </ style >
15 </ head >
16 < body >
17 < h1 > Configurator - Two-Way Communication via postMessage </ h1 >
18
19 < div class = " row " >
20 < button id = " getDataBtn " disabled > Request Current Payload </ button >
21 </ div >
22
23 < iframe
24 id = " configuratorWidget "
25 src = " https://alterproduct.com/app/configurator/1?nav=0&add_to_cart=1 "
26 title = " Alter Product Configurator "
27 allowfullscreen >
28 </ iframe >
29
30 < h3 > Logs </ h3 >
31 < pre id = " log " > </ pre >
32
33 < script >
34 const IFRAME_ORIGIN = 'https://alterproduct.com' ;
35
36 const iframe = document . getElementById ( 'configuratorWidget' ) ;
37 const getDataBtn = document . getElementById ( 'getDataBtn' ) ;
38 const logEl = document . getElementById ( 'log' ) ;
39
40 let handshakeOk = false ;
41 let tokenReady = false ;
42
43 function log ( ... args ) {
44 const line = args . map ( a => ( typeof a === 'string' ? a : JSON . stringify ( a , null , 2 ) ) ) . join ( ' ' ) ;
45 logEl . textContent += line + '\n' ;
46 }
47
48 function postToIframe ( payload ) {
49 iframe . contentWindow . postMessage ( payload , IFRAME_ORIGIN ) ;
50 }
51
52 55 56
57 async function getSessionTokenFromYourBackend ( { tool , mode , uiDesignId } ) {
58 const url = new URL ( '/api/alter/session-token' , window . location . origin ) ;
59 url . searchParams . set ( 'tool' , tool ) ; // viewer | configurator | customizer
60 url . searchParams . set ( 'mode' , mode || 'design' ) ;
61 url . searchParams . set ( 'uiDesignId' , String ( uiDesignId || 0 ) ) ;
62
63 const res = await fetch ( url . toString ( ) , { method : 'GET' } ) ;
64 if ( ! res . ok ) throw new Error ( 'Failed to get session token' ) ;
65 const data = await res . json ( ) ;
66 if ( ! data || ! data . sessionToken ) throw new Error ( 'Missing sessionToken' ) ;
67 return data . sessionToken ;
68 }
69
70 // UI: request configured product data after the iframe has received a session token.
71 getDataBtn . addEventListener ( 'click' , ( ) => {
72 if ( ! tokenReady ) return ;
73 log ( '[Host] -> Configurator: ALTER_CONFIGURATOR_GET_PRODUCT_DATA' ) ;
74 postToIframe ( { type : 'ALTER_CONFIGURATOR_GET_PRODUCT_DATA' } ) ;
75 } ) ;
76
77 window . addEventListener ( 'message' , async ( event ) => {
78 // 1) Validate origin
79 if ( event . origin !== IFRAME_ORIGIN ) return ;
80
81 // 2) Validate source (must be the embedded iframe)
82 if ( event . source !== iframe . contentWindow ) return ;
83
84 const msg = event . data || { } ;
85 if ( ! msg . type || typeof msg . type !== 'string' ) return ;
86
87 // -----------------------
88 // A) HANDSHAKE
89 // Configurator -> Host: ALTER_CHILD_HELLO { nonce }
90 // Host -> Configurator: ALTER_PARENT_ACK { nonce }
91 // -----------------------
92 if ( msg . type === 'ALTER_CHILD_HELLO' ) {
93 const nonce = msg . nonce ;
94 if ( ! nonce || typeof nonce !== 'string' ) return ;
95
96 handshakeOk = true ;
97 log ( '[Configurator] -> Host: ALTER_CHILD_HELLO' , { nonce } ) ;
98
99 log ( '[Host] -> Configurator: ALTER_PARENT_ACK' ) ;
100 postToIframe ( { type : 'ALTER_PARENT_ACK' , nonce } ) ;
101
102 return ;
103 }
104
105 // Ignore everything until handshake is done
106 if ( ! handshakeOk ) return ;
107
108 // -----------------------
109 // B) TOKEN INIT (on-demand)
110 // Configurator -> Host: ALTER_TOOL_INIT_SESSION { payload: { tool, mode, uiDesignId } }
111 // Host -> Configurator: ALTER_TOOL_SESSION_READY { token }
112 // -----------------------
113 if ( msg . type === 'ALTER_TOOL_INIT_SESSION' ) {
114 try {
115 const payload = msg . payload || { } ;
116 const tool = String ( payload . tool || 'configurator' ) . toLowerCase ( ) ;
117 const mode = String ( payload . mode || 'design' ) ;
118 const uiDesignId = Number ( payload . uiDesignId || 0 ) ;
119
120 log ( '[Configurator] -> Host: ALTER_TOOL_INIT_SESSION' , { tool , mode , uiDesignId } ) ;
121
122 const token = await getSessionTokenFromYourBackend ( { tool , mode , uiDesignId } ) ;
123
124 log ( '[Host] -> Configurator: ALTER_TOOL_SESSION_READY' ) ;
125 postToIframe ( { type : 'ALTER_TOOL_SESSION_READY' , tool , token } ) ;
126 tokenReady = true ;
127 getDataBtn . disabled = false ;
128 } catch ( e ) {
129 log ( '[Host] -> Configurator: ALTER_TOOL_SESSION_ERROR' , String ( e && e . message ? e . message : e ) ) ;
130 postToIframe ( { type : 'ALTER_TOOL_SESSION_ERROR' , message : String ( e && e . message ? e . message : e ) } ) ;
131 }
132 return ;
133 }
134
135 // -----------------------
136 // C) Configurator events
137 // -----------------------
138 if ( msg . type === 'ALTER_CONFIGURATOR_DATA_RESPONSE' ) {
139 log ( '[Configurator] -> Host: ALTER_CONFIGURATOR_DATA_RESPONSE' , msg . payload ) ;
140 return ;
141 }
142
143 if ( msg . type === 'ALTER_CONFIGURATOR_ADD_TO_CART' ) {
144 log ( '[Configurator] -> Host: ALTER_CONFIGURATOR_ADD_TO_CART' , msg . payload ) ;
145 return ;
146 }
147 } ) ;
148 </ script >
149 </ body >
150 </ html > {
"userDesignId" : 10 ,
"designName" : "Mug 450ml (15oz)" ,
"totalPrice" : {
"value" : 24.99 ,
"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"
}
}
} ,
"printingMethod" : {
"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" ,
"customColor" : false ,
"pickedColors" : { } ,
"patternId" : null ,
"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 ,
"printingMethodId" : 5 ,
"colorId" : 11 ,
"metadata" : {
"sku" : "MUG-450-WHITE"
} ,
"minOrderQuantity" : 1 ,
"processingTime" : null ,
"stockQuantity" : null ,
"volume" : null ,
"weight" : null
} ,
"unitPrice" : {
"value" : 24.99 ,
"currency" : "EUR"
} ,
"totalPrice" : {
"value" : 24.99 ,
"currency" : "EUR"
} ,
"quantity" : 1
}
]
}