@polyguard/sdk 1.4.1 → 1.5.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,157 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for src/ticketService.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../prettify.css" />
9
+ <link rel="stylesheet" href="../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> ticketService.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">100% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>9/9</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>6/6</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/1</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>9/9</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
91
+ <span class="cline-any cline-yes">36x</span>
92
+ <span class="cline-any cline-neutral">&nbsp;</span>
93
+ <span class="cline-any cline-neutral">&nbsp;</span>
94
+ <span class="cline-any cline-neutral">&nbsp;</span>
95
+ <span class="cline-any cline-yes">36x</span>
96
+ <span class="cline-any cline-neutral">&nbsp;</span>
97
+ <span class="cline-any cline-neutral">&nbsp;</span>
98
+ <span class="cline-any cline-neutral">&nbsp;</span>
99
+ <span class="cline-any cline-neutral">&nbsp;</span>
100
+ <span class="cline-any cline-neutral">&nbsp;</span>
101
+ <span class="cline-any cline-yes">35x</span>
102
+ <span class="cline-any cline-yes">1x</span>
103
+ <span class="cline-any cline-neutral">&nbsp;</span>
104
+ <span class="cline-any cline-neutral">&nbsp;</span>
105
+ <span class="cline-any cline-yes">34x</span>
106
+ <span class="cline-any cline-yes">34x</span>
107
+ <span class="cline-any cline-neutral">&nbsp;</span>
108
+ <span class="cline-any cline-yes">34x</span>
109
+ <span class="cline-any cline-yes">1x</span>
110
+ <span class="cline-any cline-neutral">&nbsp;</span>
111
+ <span class="cline-any cline-neutral">&nbsp;</span>
112
+ <span class="cline-any cline-yes">33x</span>
113
+ <span class="cline-any cline-neutral">&nbsp;</span>
114
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export async function fetchTicket({ apiServer, appId, link_uuid, requiredProofs, scanType }) {
115
+ const ticketUrl = link_uuid
116
+ ? `https://${apiServer}/v2/ticket/${appId}/${link_uuid}`
117
+ : `https://${apiServer}/v2/ticket/${appId}`;
118
+ &nbsp;
119
+ const ticketRes = await fetch(ticketUrl, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({ requiredProofs, scanType }),
123
+ });
124
+ &nbsp;
125
+ if (!ticketRes.ok) {
126
+ throw new Error('Failed to get ticket');
127
+ }
128
+ &nbsp;
129
+ const ticketData = await ticketRes.json();
130
+ const ticket = ticketData.ticket;
131
+ &nbsp;
132
+ if (!ticket) {
133
+ throw new Error('No ticket returned from server');
134
+ }
135
+ &nbsp;
136
+ return ticket;
137
+ }
138
+ &nbsp;</pre></td></tr></table></pre>
139
+
140
+ <div class='push'></div><!-- for sticky footer -->
141
+ </div><!-- /wrapper -->
142
+ <div class='footer quiet pad2 space-top1 center small'>
143
+ Code coverage generated by
144
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
145
+ at 2026-05-17T02:18:23.420Z
146
+ </div>
147
+ <script src="../prettify.js"></script>
148
+ <script>
149
+ window.onload = function () {
150
+ prettyPrint();
151
+ };
152
+ </script>
153
+ <script src="../sorter.js"></script>
154
+ <script src="../block-navigation.js"></script>
155
+ </body>
156
+ </html>
157
+
@@ -0,0 +1,349 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for src/ui.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../prettify.css" />
9
+ <link rel="stylesheet" href="../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> ui.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">100% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>34/34</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">60% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>6/10</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>6/6</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>32/32</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
155
+ <span class="cline-any cline-neutral">&nbsp;</span>
156
+ <span class="cline-any cline-neutral">&nbsp;</span>
157
+ <span class="cline-any cline-yes">2x</span>
158
+ <span class="cline-any cline-neutral">&nbsp;</span>
159
+ <span class="cline-any cline-neutral">&nbsp;</span>
160
+ <span class="cline-any cline-neutral">&nbsp;</span>
161
+ <span class="cline-any cline-neutral">&nbsp;</span>
162
+ <span class="cline-any cline-neutral">&nbsp;</span>
163
+ <span class="cline-any cline-neutral">&nbsp;</span>
164
+ <span class="cline-any cline-neutral">&nbsp;</span>
165
+ <span class="cline-any cline-neutral">&nbsp;</span>
166
+ <span class="cline-any cline-neutral">&nbsp;</span>
167
+ <span class="cline-any cline-neutral">&nbsp;</span>
168
+ <span class="cline-any cline-neutral">&nbsp;</span>
169
+ <span class="cline-any cline-neutral">&nbsp;</span>
170
+ <span class="cline-any cline-neutral">&nbsp;</span>
171
+ <span class="cline-any cline-neutral">&nbsp;</span>
172
+ <span class="cline-any cline-neutral">&nbsp;</span>
173
+ <span class="cline-any cline-neutral">&nbsp;</span>
174
+ <span class="cline-any cline-neutral">&nbsp;</span>
175
+ <span class="cline-any cline-yes">31x</span>
176
+ <span class="cline-any cline-yes">31x</span>
177
+ <span class="cline-any cline-yes">31x</span>
178
+ <span class="cline-any cline-yes">31x</span>
179
+ <span class="cline-any cline-yes">31x</span>
180
+ <span class="cline-any cline-yes">31x</span>
181
+ <span class="cline-any cline-yes">31x</span>
182
+ <span class="cline-any cline-yes">31x</span>
183
+ <span class="cline-any cline-yes">31x</span>
184
+ <span class="cline-any cline-yes">31x</span>
185
+ <span class="cline-any cline-yes">31x</span>
186
+ <span class="cline-any cline-yes">31x</span>
187
+ <span class="cline-any cline-yes">31x</span>
188
+ <span class="cline-any cline-yes">31x</span>
189
+ <span class="cline-any cline-neutral">&nbsp;</span>
190
+ <span class="cline-any cline-neutral">&nbsp;</span>
191
+ <span class="cline-any cline-neutral">&nbsp;</span>
192
+ <span class="cline-any cline-neutral">&nbsp;</span>
193
+ <span class="cline-any cline-neutral">&nbsp;</span>
194
+ <span class="cline-any cline-neutral">&nbsp;</span>
195
+ <span class="cline-any cline-neutral">&nbsp;</span>
196
+ <span class="cline-any cline-neutral">&nbsp;</span>
197
+ <span class="cline-any cline-neutral">&nbsp;</span>
198
+ <span class="cline-any cline-neutral">&nbsp;</span>
199
+ <span class="cline-any cline-neutral">&nbsp;</span>
200
+ <span class="cline-any cline-neutral">&nbsp;</span>
201
+ <span class="cline-any cline-neutral">&nbsp;</span>
202
+ <span class="cline-any cline-neutral">&nbsp;</span>
203
+ <span class="cline-any cline-neutral">&nbsp;</span>
204
+ <span class="cline-any cline-neutral">&nbsp;</span>
205
+ <span class="cline-any cline-neutral">&nbsp;</span>
206
+ <span class="cline-any cline-neutral">&nbsp;</span>
207
+ <span class="cline-any cline-neutral">&nbsp;</span>
208
+ <span class="cline-any cline-neutral">&nbsp;</span>
209
+ <span class="cline-any cline-yes">31x</span>
210
+ <span class="cline-any cline-neutral">&nbsp;</span>
211
+ <span class="cline-any cline-neutral">&nbsp;</span>
212
+ <span class="cline-any cline-neutral">&nbsp;</span>
213
+ <span class="cline-any cline-yes">37x</span>
214
+ <span class="cline-any cline-neutral">&nbsp;</span>
215
+ <span class="cline-any cline-neutral">&nbsp;</span>
216
+ <span class="cline-any cline-neutral">&nbsp;</span>
217
+ <span class="cline-any cline-yes">3x</span>
218
+ <span class="cline-any cline-yes">3x</span>
219
+ <span class="cline-any cline-yes">3x</span>
220
+ <span class="cline-any cline-neutral">&nbsp;</span>
221
+ <span class="cline-any cline-neutral">&nbsp;</span>
222
+ <span class="cline-any cline-yes">3x</span>
223
+ <span class="cline-any cline-yes">3x</span>
224
+ <span class="cline-any cline-yes">3x</span>
225
+ <span class="cline-any cline-yes">3x</span>
226
+ <span class="cline-any cline-neutral">&nbsp;</span>
227
+ <span class="cline-any cline-yes">3x</span>
228
+ <span class="cline-any cline-yes">3x</span>
229
+ <span class="cline-any cline-yes">3x</span>
230
+ <span class="cline-any cline-neutral">&nbsp;</span>
231
+ <span class="cline-any cline-neutral">&nbsp;</span>
232
+ <span class="cline-any cline-neutral">&nbsp;</span>
233
+ <span class="cline-any cline-neutral">&nbsp;</span>
234
+ <span class="cline-any cline-neutral">&nbsp;</span>
235
+ <span class="cline-any cline-yes">3x</span>
236
+ <span class="cline-any cline-yes">3x</span>
237
+ <span class="cline-any cline-yes">3x</span>
238
+ <span class="cline-any cline-yes">3x</span>
239
+ <span class="cline-any cline-neutral">&nbsp;</span>
240
+ <span class="cline-any cline-yes">3x</span>
241
+ <span class="cline-any cline-neutral">&nbsp;</span>
242
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import QRCode from 'qrcode';
243
+ &nbsp;
244
+ // Animated spinner SVG
245
+ export const LOADING_SPINNER = `&lt;svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200" fill="none"&gt;
246
+ &lt;style&gt;
247
+ .spinner-circle {
248
+ animation: spin 1s linear infinite;
249
+ transform-origin: 100px 100px;
250
+ }
251
+ @keyframes spin {
252
+ from { transform: rotate(0deg); }
253
+ to { transform: rotate(360deg); }
254
+ }
255
+ &lt;/style&gt;
256
+ &lt;circle cx="100" cy="100" r="80" stroke="#e0e0e0" stroke-width="8" fill="none" /&gt;
257
+ &lt;circle cx="100" cy="100" r="80" stroke="#407796" stroke-width="8" fill="none"
258
+ stroke-dasharray="251.2" stroke-dashoffset="188.4"
259
+ class="spinner-circle" stroke-linecap="round" /&gt;
260
+ &lt;/svg&gt;`;
261
+ &nbsp;
262
+ export function buildModal() {
263
+ const modal = document.createElement('div');
264
+ modal.style.position = 'fixed';
265
+ modal.style.top = '0';
266
+ modal.style.left = '0';
267
+ modal.style.width = '100vw';
268
+ modal.style.height = '100vh';
269
+ modal.style.background = 'rgba(0,0,0,0.45)';
270
+ modal.style.display = 'flex';
271
+ modal.style.alignItems = 'flex-start';
272
+ modal.style.justifyContent = 'center';
273
+ modal.style.overflowY = 'auto';
274
+ modal.style.paddingTop = '24px';
275
+ modal.style.zIndex = '2147483647';
276
+ modal.innerHTML = `
277
+ &lt;div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; z-index: 2147483647; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;"&gt;
278
+ &lt;button id="polyguard-modal-close" style="position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; color: #222; cursor: pointer; z-index: 2;"&gt;&amp;times;&lt;/button&gt;
279
+ &lt;h2 style="margin-top: 0; font-size: 1.04rem; font-weight: 700; color: #222;"&gt;Quick Identity Verification&lt;/h2&gt;
280
+ &lt;div style="font-size: 10px; color: #888; margin-bottom: 10px; font-weight: 500;"&gt;Powered by Polyguard&lt;/div&gt;
281
+ &lt;div id="polyguard-qr" style="width: 200px; height: 200px; margin: 0 auto 13px auto; display: flex; align-items: center; justify-content: center; background: #f4f8fb; border-radius: 10px;"&gt;&lt;/div&gt;
282
+ &lt;div style="margin-bottom: 10px; font-weight: 600; font-size: 13px; color: #222;"&gt;Scan this QR code to verify your identity.&lt;/div&gt;
283
+ &lt;ul style="text-align: left; margin: 0 0 13px 0; padding-left: 16px; font-size: 11px; color: #444;"&gt;
284
+ &lt;li&gt;We use the Polyguard service to verify your identity.&lt;/li&gt;
285
+ &lt;li&gt;If you do not have the Polyguard app, the QR code will redirect you to download it from the App Store or Google Play.&lt;/li&gt;
286
+ &lt;li&gt;Your credentials remain private on your device.&lt;/li&gt;
287
+ &lt;/ul&gt;
288
+ &lt;div style="display: flex; justify-content: center; gap: 10px; font-size: 10px; color: #5a6c7d; margin-bottom: 10px; flex-wrap: wrap;"&gt;
289
+ &lt;span&gt;&amp;#128274; Encrypted&lt;/span&gt;
290
+ &lt;span&gt;&amp;#128241; On-device&lt;/span&gt;
291
+ &lt;span&gt;&amp;#9202; Expires&lt;/span&gt;
292
+ &lt;/div&gt;
293
+ &lt;div id="polyguard-error" style="color: #b31d28; font-size: 11px; margin-bottom: 6px; display: none;"&gt;&lt;/div&gt;
294
+ &lt;button id="polyguard-modal-cancel" style="background: #407796; color: #fff; font-weight: 600; border-radius: 8px; border: none; padding: 8px 26px; font-size: 13px; cursor: pointer; margin-top: 6px; width: 100%; max-width: 192px;"&gt;Cancel&lt;/button&gt;
295
+ &lt;/div&gt;
296
+ `;
297
+ return modal;
298
+ }
299
+ &nbsp;
300
+ export function showSpinner(qrDiv) {
301
+ qrDiv.innerHTML = LOADING_SPINNER;
302
+ }
303
+ &nbsp;
304
+ export function renderMobileButton(qrDiv, qrUrl, isTargetMode) {
305
+ qrDiv.innerHTML = `&lt;button id="polyguard-open-app-button" style="background: #407796; color: #fff; font-weight: 600; border-radius: 8px; border: none; padding: 10px 32px; font-size: 16px; cursor: pointer;"&gt;Open Polyguard App&lt;/button&gt;`;
306
+ qrDiv.style.background = 'transparent';
307
+ qrDiv.querySelector('#polyguard-open-app-button').onclick = () =&gt; window.location.assign(qrUrl);
308
+ &nbsp;
309
+ // Update surrounding text only in modal mode
310
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!isTargetMode) {
311
+ const instructionText = qrDiv.nextElementSibling;
312
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (instructionText) {
313
+ instructionText.textContent = 'Tap the button to verify with the Polyguard app.';
314
+ }
315
+ const instructionList = instructionText.nextElementSibling;
316
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (instructionList &amp;&amp; instructionList.children[1]) {
317
+ instructionList.children[1].textContent = 'If you do not have the Polyguard app, you will be redirected to download it.';
318
+ }
319
+ }
320
+ }
321
+ &nbsp;
322
+ export function renderQRCode(qrDiv, qrUrl) {
323
+ const startTime = Date.now();
324
+ console.log('time before qr code', startTime);
325
+ QRCode.toString(qrUrl, { type: 'svg' }, (err, svg) =&gt; {
326
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!err) qrDiv.innerHTML = svg;
327
+ });
328
+ console.log('time to generate qr code', Date.now() - startTime);
329
+ }
330
+ &nbsp;</pre></td></tr></table></pre>
331
+
332
+ <div class='push'></div><!-- for sticky footer -->
333
+ </div><!-- /wrapper -->
334
+ <div class='footer quiet pad2 space-top1 center small'>
335
+ Code coverage generated by
336
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
337
+ at 2026-05-17T02:18:23.420Z
338
+ </div>
339
+ <script src="../prettify.js"></script>
340
+ <script>
341
+ window.onload = function () {
342
+ prettyPrint();
343
+ };
344
+ </script>
345
+ <script src="../sorter.js"></script>
346
+ <script src="../block-navigation.js"></script>
347
+ </body>
348
+ </html>
349
+
package/dist/sdk.esm.js CHANGED
@@ -10920,8 +10920,9 @@ function renderQRCode(qrDiv, qrUrl) {
10920
10920
  }
10921
10921
 
10922
10922
  // src/ticketService.js
10923
- async function fetchTicket({ apiServer, appId, link_uuid, requiredProofs, scanType }) {
10924
- const ticketUrl = link_uuid ? `https://${apiServer}/v2/ticket/${appId}/${link_uuid}` : `https://${apiServer}/v2/ticket/${appId}`;
10923
+ async function fetchTicket({ apiServer, proxyUrl, appId, link_uuid, requiredProofs, scanType }) {
10924
+ const base = proxyUrl ? proxyUrl.replace(/\/$/, "") : `https://${apiServer}`;
10925
+ const ticketUrl = link_uuid ? `${base}/v2/ticket/${appId}/${link_uuid}` : `${base}/v2/ticket/${appId}`;
10925
10926
  const ticketRes = await fetch(ticketUrl, {
10926
10927
  method: "POST",
10927
10928
  headers: { "Content-Type": "application/json" },
@@ -10984,6 +10985,7 @@ function handleWebSocketMessage(event, ctx) {
10984
10985
  const { sidebarUrl, link_uuid } = ctx;
10985
10986
  if (sidebarUrl && redirectUrl) {
10986
10987
  const hasMsftSession = document.cookie.includes("pg_msft_session");
10988
+ ctx.markClosed();
10987
10989
  cleanup();
10988
10990
  ws.close();
10989
10991
  if (hasMsftSession) {
@@ -11032,15 +11034,24 @@ function handleWebSocketMessage(event, ctx) {
11032
11034
  var PolyguardWebsocketClientImpl = class {
11033
11035
  constructor(params = {}) {
11034
11036
  this.apiClient = new ApiClient_default();
11035
- const { apiKey, baseUrl, appId, apiServer, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid, sidebarUrl } = params;
11037
+ const { apiKey, baseUrl, appId, apiServer, proxyUrl, iKnowThisIsInsecure, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid, sidebarUrl } = params;
11036
11038
  this.sidebarUrl = sidebarUrl;
11037
- if (baseUrl) {
11039
+ if (apiKey && typeof window !== "undefined" && iKnowThisIsInsecure !== true) {
11040
+ throw new Error(
11041
+ "apiKey is forbidden in browser code; use proxyUrl with a server-side proxy (see @polyguard/sdk/server). Set iKnowThisIsInsecure: true to bypass."
11042
+ );
11043
+ }
11044
+ if (proxyUrl) {
11045
+ this.apiClient.basePath = proxyUrl.replace(/\/$/, "");
11046
+ } else if (baseUrl) {
11038
11047
  this.apiClient.basePath = baseUrl;
11039
11048
  }
11040
11049
  if (apiKey) {
11041
11050
  if (this.apiClient.authentications["bearerAuth"]) {
11042
11051
  this.apiClient.authentications["bearerAuth"].apiKey = apiKey;
11043
11052
  }
11053
+ this.apiClient.defaultHeaders = this.apiClient.defaultHeaders || {};
11054
+ this.apiClient.defaultHeaders["x-pg-api-key"] = apiKey;
11044
11055
  }
11045
11056
  for (const key in src_exports) {
11046
11057
  if (key.endsWith("Api")) {
@@ -11051,6 +11062,7 @@ var PolyguardWebsocketClientImpl = class {
11051
11062
  this.integrationType = "websocket";
11052
11063
  this.appId = appId;
11053
11064
  this.apiServer = apiServer;
11065
+ this.proxyUrl = proxyUrl;
11054
11066
  this.requiredProofs = requiredProofs;
11055
11067
  this.scanType = scanType;
11056
11068
  this.redirectUrl = redirectUrl;
@@ -11131,6 +11143,7 @@ var PolyguardWebsocketClientImpl = class {
11131
11143
  const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
11132
11144
  const newTicket = await fetchTicket({
11133
11145
  apiServer: this.apiServer,
11146
+ proxyUrl: this.proxyUrl,
11134
11147
  appId: this.appId,
11135
11148
  link_uuid: this.link_uuid,
11136
11149
  requiredProofs: this.requiredProofs,
@@ -11145,7 +11158,10 @@ var PolyguardWebsocketClientImpl = class {
11145
11158
  reconnectionDelayGrowFactor: 1.5
11146
11159
  };
11147
11160
  ws = new reconnecting_websocket_mjs_default(wsUrl, [], options);
11148
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11161
+ const markClosed = () => {
11162
+ closed = true;
11163
+ };
11164
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, markClosed, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11149
11165
  ws.addEventListener("message", (event) => handleWebSocketMessage(event, ctx));
11150
11166
  ws.addEventListener("error", () => {
11151
11167
  console.error("WebSocket error");