@kiauth/web-sdk 1.0.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,3684 @@
1
+ class KiauthApi {
2
+ constructor(config) {
3
+ this.baseUrl = KiauthApi.resolveBaseUrl(config);
4
+ }
5
+ // Resolve the backend API base. An explicit config.baseUrl always wins so a
6
+ // developer can point the SDK at the live backend (api.kiauth.com (the branded backend host)
7
+ // mapped) or a self-hosted instance; otherwise fall back to env defaults.
8
+ static resolveBaseUrl(config) {
9
+ if (config.baseUrl) {
10
+ const trimmed = config.baseUrl.replace(/\/+$/, '');
11
+ return /\/api\/v\d+$/.test(trimmed) ? trimmed : `${trimmed}/api/v1`;
12
+ }
13
+ if (config.environment === 'production') {
14
+ return 'https://api.kiauth.com/api/v1';
15
+ }
16
+ // Sandbox/dev: talk to a locally running backend.
17
+ return 'http://localhost:3000/api/v1';
18
+ }
19
+ getBaseUrl() {
20
+ return this.baseUrl;
21
+ }
22
+ // Default 8s (not 2s): a backend on a cold start / slow network must not be
23
+ // mistaken for "offline". The on-click re-check passes a longer timeout still.
24
+ async healthCheck(timeoutMs = 8000) {
25
+ try {
26
+ const controller = new AbortController();
27
+ const id = setTimeout(() => controller.abort(), timeoutMs);
28
+ const res = await fetch(`${this.baseUrl}/auth/health`, {
29
+ method: 'GET',
30
+ headers: { 'Accept': 'application/json' },
31
+ signal: controller.signal
32
+ });
33
+ clearTimeout(id);
34
+ return res.ok;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ async initiateSession(clientId, scopes, redirectUri, codeChallenge) {
41
+ const res = await fetch(`${this.baseUrl}/auth/initiate`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ 'Accept': 'application/json'
46
+ },
47
+ body: JSON.stringify({
48
+ clientId,
49
+ scopes,
50
+ redirectUri,
51
+ // PKCE challenge for public clients (omitted for confidential clients).
52
+ ...(codeChallenge ? { codeChallenge, codeChallengeMethod: 'S256' } : {})
53
+ })
54
+ });
55
+ if (!res.ok) {
56
+ const err = await res.json().catch(() => ({}));
57
+ throw { code: err.code || 'INITIATE_FAILED', message: err.message || 'Failed to initiate login session.' };
58
+ }
59
+ return res.json();
60
+ }
61
+ // Poll a QR session's status. On APPROVED the backend also returns the one-time
62
+ // authCode, so the SDK can complete the exchange even if the WebSocket event
63
+ // never arrived (proxy/cold-network). Mirrors the dev-portal's polling flow.
64
+ async getSessionStatus(sessionId) {
65
+ try {
66
+ const res = await fetch(`${this.baseUrl}/auth/session/${sessionId}`, {
67
+ method: 'GET',
68
+ headers: { 'Accept': 'application/json' }
69
+ });
70
+ if (!res.ok)
71
+ return { status: 'UNKNOWN', authCode: null };
72
+ const data = await res.json();
73
+ return { status: data.status || 'UNKNOWN', authCode: data.authCode || null };
74
+ }
75
+ catch {
76
+ return { status: 'UNKNOWN', authCode: null };
77
+ }
78
+ }
79
+ /**
80
+ * Exchange the authorization code for tokens. Public clients pass
81
+ * { codeVerifier } (PKCE); confidential clients pass { clientSecret }.
82
+ */
83
+ async exchangeToken(code, clientId, credentials) {
84
+ const res = await fetch(`${this.baseUrl}/auth/token`, {
85
+ method: 'POST',
86
+ headers: {
87
+ 'Content-Type': 'application/json',
88
+ 'Accept': 'application/json'
89
+ },
90
+ body: JSON.stringify({
91
+ grantType: 'authorization_code',
92
+ code,
93
+ clientId,
94
+ ...(credentials.codeVerifier ? { codeVerifier: credentials.codeVerifier } : {}),
95
+ ...(credentials.clientSecret ? { clientSecret: credentials.clientSecret } : {})
96
+ })
97
+ });
98
+ if (!res.ok) {
99
+ const err = await res.json().catch(() => ({}));
100
+ throw { code: err.code || 'TOKEN_EXCHANGE_FAILED', message: err.message || 'Failed to exchange authorization code.' };
101
+ }
102
+ return res.json();
103
+ }
104
+ async verifyToken(token) {
105
+ const res = await fetch(`${this.baseUrl}/auth/verify`, {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'Accept': 'application/json'
110
+ },
111
+ body: JSON.stringify({ token })
112
+ });
113
+ if (!res.ok) {
114
+ return { valid: false };
115
+ }
116
+ return res.json();
117
+ }
118
+ async reportSession(clientId, clientSecret, options) {
119
+ const creds = btoa(`${clientId}:${clientSecret}`);
120
+ const res = await fetch(`${this.baseUrl}/sessions/bridge`, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ 'Authorization': `Basic ${creds}`,
125
+ 'Accept': 'application/json'
126
+ },
127
+ body: JSON.stringify(options)
128
+ });
129
+ if (!res.ok) {
130
+ const err = await res.json().catch(() => ({}));
131
+ throw { code: err.code || 'REPORT_SESSION_FAILED', message: err.message || 'Failed to report session bridge.' };
132
+ }
133
+ return res.json();
134
+ }
135
+ async reportBatchSessions(clientId, clientSecret, kiauthUserToken, sessions) {
136
+ const creds = btoa(`${clientId}:${clientSecret}`);
137
+ const res = await fetch(`${this.baseUrl}/sessions/bridge/batch`, {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json',
141
+ 'Authorization': `Basic ${creds}`,
142
+ 'Accept': 'application/json'
143
+ },
144
+ body: JSON.stringify({ kiauthUserToken, sessions })
145
+ });
146
+ if (!res.ok) {
147
+ const err = await res.json().catch(() => ({}));
148
+ throw { code: err.code || 'REPORT_BATCH_FAILED', message: err.message || 'Failed to report batch session bridge.' };
149
+ }
150
+ return res.json();
151
+ }
152
+ // Email-keyed login reporting ("claim on first sighting"). Use this when you do NOT
153
+ // have a kiauthUserToken — e.g. a user logged in with their own email/password/Google
154
+ // and you want it to appear in their Kiauth app. If the user is already linked we show
155
+ // it immediately; if not, Kiauth asks them "was this you?" to link or revoke.
156
+ // Always resolves on a 2xx (Kiauth never reveals whether the email is a Kiauth user).
157
+ // Requires a VERIFIED business + clientSecret → must run on your backend, never the browser.
158
+ async reportSessionByEmail(clientId, clientSecret, options) {
159
+ const creds = btoa(`${clientId}:${clientSecret}`);
160
+ const res = await fetch(`${this.baseUrl}/sessions/bridge/by-email`, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ 'Authorization': `Basic ${creds}`,
165
+ 'Accept': 'application/json'
166
+ },
167
+ body: JSON.stringify(options)
168
+ });
169
+ if (!res.ok) {
170
+ const err = await res.json().catch(() => ({}));
171
+ throw { code: err.code || 'REPORT_BY_EMAIL_FAILED', message: err.message || 'Failed to report session by email.' };
172
+ }
173
+ return res.json();
174
+ }
175
+ // RP-initiated logout. Ends a Kiauth session the RP owns when the user logs out
176
+ // on the RP's own site, so the Kiauth app reflects it as revoked. Identify the
177
+ // session by accessToken (native SSO), sessionId, or externalSessionId (bridged).
178
+ // Requires clientSecret → must run on the developer's backend, never the browser.
179
+ async endSession(clientId, clientSecret, opts) {
180
+ const creds = btoa(`${clientId}:${clientSecret}`);
181
+ const res = await fetch(`${this.baseUrl}/sessions/bridge/end`, {
182
+ method: 'POST',
183
+ headers: {
184
+ 'Content-Type': 'application/json',
185
+ 'Authorization': `Basic ${creds}`,
186
+ 'Accept': 'application/json'
187
+ },
188
+ body: JSON.stringify(opts)
189
+ });
190
+ if (!res.ok) {
191
+ const err = await res.json().catch(() => ({}));
192
+ throw { code: err.code || 'END_SESSION_FAILED', message: err.message || 'Failed to end session.' };
193
+ }
194
+ return res.json();
195
+ }
196
+ }
197
+
198
+ class KiauthWebsocket {
199
+ constructor(serverUrl) {
200
+ this.serverUrl = serverUrl;
201
+ this.ws = null;
202
+ this.pingIntervalId = null;
203
+ this.reconnectTimeoutId = null;
204
+ this.reconnectAttempts = 0;
205
+ this.maxReconnectAttempts = 5;
206
+ this.isIntentionalDisconnect = false;
207
+ }
208
+ connect(channelId, onAuthComplete, onError) {
209
+ this.isIntentionalDisconnect = false;
210
+ // Parse HTTP url to WS url using URL object to extract correct host
211
+ const urlObj = new URL(this.serverUrl);
212
+ const wsProtocol = urlObj.protocol.startsWith('https') ? 'wss' : 'ws';
213
+ const wsUrl = `${wsProtocol}://${urlObj.host}/socket.io/?EIO=4&transport=websocket`;
214
+ try {
215
+ this.ws = new WebSocket(wsUrl);
216
+ }
217
+ catch (err) {
218
+ onError({ code: 'WEBSOCKET_ERROR', message: 'Failed to initialize WebSocket client.' });
219
+ return;
220
+ }
221
+ this.ws.onopen = () => {
222
+ this.reconnectAttempts = 0;
223
+ };
224
+ this.ws.onmessage = (event) => {
225
+ const data = event.data;
226
+ if (typeof data !== 'string')
227
+ return;
228
+ // Handle Engine.io packet types:
229
+ // 0: Open (handshake JSON)
230
+ // 2: Ping
231
+ // 3: Pong
232
+ // 4: Message
233
+ // 40: Socket.io Connect
234
+ // 42: Socket.io Event message
235
+ if (data.startsWith('0')) {
236
+ try {
237
+ const handshake = JSON.parse(data.substring(1));
238
+ const pingInterval = handshake.pingInterval || 25000;
239
+ // Start client-initiated ping (Engine.io v4)
240
+ this.startPinging(pingInterval);
241
+ // Join channel in default namespace
242
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
243
+ // First send namespace connect (not strictly required but standard in socket.io client)
244
+ this.ws.send('40');
245
+ // Join our specific QR channel
246
+ this.ws.send(`42["join_channel","${channelId}"]`);
247
+ }
248
+ }
249
+ catch (e) {
250
+ // Handshake parse failed
251
+ }
252
+ }
253
+ else if (data.startsWith('42')) {
254
+ try {
255
+ const payload = JSON.parse(data.substring(2));
256
+ if (Array.isArray(payload)) {
257
+ const [eventName, eventData] = payload;
258
+ if (eventName === 'auth_complete' && eventData) {
259
+ this.disconnect();
260
+ onAuthComplete(eventData);
261
+ }
262
+ }
263
+ }
264
+ catch (e) {
265
+ // Message parsing failed
266
+ }
267
+ }
268
+ };
269
+ this.ws.onclose = (event) => {
270
+ this.stopPinging();
271
+ if (!this.isIntentionalDisconnect) {
272
+ this.attemptReconnect(channelId, onAuthComplete, onError);
273
+ }
274
+ };
275
+ this.ws.onerror = (err) => {
276
+ // Socket error
277
+ };
278
+ }
279
+ startPinging(interval) {
280
+ this.stopPinging();
281
+ this.pingIntervalId = setInterval(() => {
282
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
283
+ this.ws.send('2'); // Ping packet type in Engine.io
284
+ }
285
+ }, interval);
286
+ }
287
+ stopPinging() {
288
+ if (this.pingIntervalId) {
289
+ clearInterval(this.pingIntervalId);
290
+ this.pingIntervalId = null;
291
+ }
292
+ }
293
+ attemptReconnect(channelId, onAuthComplete, onError) {
294
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
295
+ onError({ code: 'WEBSOCKET_DISCONNECTED', message: 'WebSocket connection lost permanently.' });
296
+ return;
297
+ }
298
+ this.reconnectAttempts++;
299
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
300
+ this.reconnectTimeoutId = setTimeout(() => {
301
+ this.connect(channelId, onAuthComplete, onError);
302
+ }, delay);
303
+ }
304
+ disconnect() {
305
+ this.isIntentionalDisconnect = true;
306
+ this.stopPinging();
307
+ if (this.reconnectTimeoutId) {
308
+ clearTimeout(this.reconnectTimeoutId);
309
+ this.reconnectTimeoutId = null;
310
+ }
311
+ if (this.ws) {
312
+ this.ws.close();
313
+ this.ws = null;
314
+ }
315
+ }
316
+ }
317
+
318
+ function getDefaultExportFromCjs (x) {
319
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
320
+ }
321
+
322
+ var qrcode$1 = {exports: {}};
323
+
324
+ (function (module, exports) {
325
+ //---------------------------------------------------------------------
326
+ //
327
+ // QR Code Generator for JavaScript
328
+ //
329
+ // Copyright (c) 2009 Kazuhiko Arase
330
+ //
331
+ // URL: http://www.d-project.com/
332
+ //
333
+ // Licensed under the MIT license:
334
+ // http://www.opensource.org/licenses/mit-license.php
335
+ //
336
+ // The word 'QR Code' is registered trademark of
337
+ // DENSO WAVE INCORPORATED
338
+ // http://www.denso-wave.com/qrcode/faqpatent-e.html
339
+ //
340
+ //---------------------------------------------------------------------
341
+
342
+ var qrcode = function() {
343
+
344
+ //---------------------------------------------------------------------
345
+ // qrcode
346
+ //---------------------------------------------------------------------
347
+
348
+ /**
349
+ * qrcode
350
+ * @param typeNumber 1 to 40
351
+ * @param errorCorrectionLevel 'L','M','Q','H'
352
+ */
353
+ var qrcode = function(typeNumber, errorCorrectionLevel) {
354
+
355
+ var PAD0 = 0xEC;
356
+ var PAD1 = 0x11;
357
+
358
+ var _typeNumber = typeNumber;
359
+ var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
360
+ var _modules = null;
361
+ var _moduleCount = 0;
362
+ var _dataCache = null;
363
+ var _dataList = [];
364
+
365
+ var _this = {};
366
+
367
+ var makeImpl = function(test, maskPattern) {
368
+
369
+ _moduleCount = _typeNumber * 4 + 17;
370
+ _modules = function(moduleCount) {
371
+ var modules = new Array(moduleCount);
372
+ for (var row = 0; row < moduleCount; row += 1) {
373
+ modules[row] = new Array(moduleCount);
374
+ for (var col = 0; col < moduleCount; col += 1) {
375
+ modules[row][col] = null;
376
+ }
377
+ }
378
+ return modules;
379
+ }(_moduleCount);
380
+
381
+ setupPositionProbePattern(0, 0);
382
+ setupPositionProbePattern(_moduleCount - 7, 0);
383
+ setupPositionProbePattern(0, _moduleCount - 7);
384
+ setupPositionAdjustPattern();
385
+ setupTimingPattern();
386
+ setupTypeInfo(test, maskPattern);
387
+
388
+ if (_typeNumber >= 7) {
389
+ setupTypeNumber(test);
390
+ }
391
+
392
+ if (_dataCache == null) {
393
+ _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
394
+ }
395
+
396
+ mapData(_dataCache, maskPattern);
397
+ };
398
+
399
+ var setupPositionProbePattern = function(row, col) {
400
+
401
+ for (var r = -1; r <= 7; r += 1) {
402
+
403
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
404
+
405
+ for (var c = -1; c <= 7; c += 1) {
406
+
407
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
408
+
409
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
410
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
411
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
412
+ _modules[row + r][col + c] = true;
413
+ } else {
414
+ _modules[row + r][col + c] = false;
415
+ }
416
+ }
417
+ }
418
+ };
419
+
420
+ var getBestMaskPattern = function() {
421
+
422
+ var minLostPoint = 0;
423
+ var pattern = 0;
424
+
425
+ for (var i = 0; i < 8; i += 1) {
426
+
427
+ makeImpl(true, i);
428
+
429
+ var lostPoint = QRUtil.getLostPoint(_this);
430
+
431
+ if (i == 0 || minLostPoint > lostPoint) {
432
+ minLostPoint = lostPoint;
433
+ pattern = i;
434
+ }
435
+ }
436
+
437
+ return pattern;
438
+ };
439
+
440
+ var setupTimingPattern = function() {
441
+
442
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
443
+ if (_modules[r][6] != null) {
444
+ continue;
445
+ }
446
+ _modules[r][6] = (r % 2 == 0);
447
+ }
448
+
449
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
450
+ if (_modules[6][c] != null) {
451
+ continue;
452
+ }
453
+ _modules[6][c] = (c % 2 == 0);
454
+ }
455
+ };
456
+
457
+ var setupPositionAdjustPattern = function() {
458
+
459
+ var pos = QRUtil.getPatternPosition(_typeNumber);
460
+
461
+ for (var i = 0; i < pos.length; i += 1) {
462
+
463
+ for (var j = 0; j < pos.length; j += 1) {
464
+
465
+ var row = pos[i];
466
+ var col = pos[j];
467
+
468
+ if (_modules[row][col] != null) {
469
+ continue;
470
+ }
471
+
472
+ for (var r = -2; r <= 2; r += 1) {
473
+
474
+ for (var c = -2; c <= 2; c += 1) {
475
+
476
+ if (r == -2 || r == 2 || c == -2 || c == 2
477
+ || (r == 0 && c == 0) ) {
478
+ _modules[row + r][col + c] = true;
479
+ } else {
480
+ _modules[row + r][col + c] = false;
481
+ }
482
+ }
483
+ }
484
+ }
485
+ }
486
+ };
487
+
488
+ var setupTypeNumber = function(test) {
489
+
490
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
491
+
492
+ for (var i = 0; i < 18; i += 1) {
493
+ var mod = (!test && ( (bits >> i) & 1) == 1);
494
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
495
+ }
496
+
497
+ for (var i = 0; i < 18; i += 1) {
498
+ var mod = (!test && ( (bits >> i) & 1) == 1);
499
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
500
+ }
501
+ };
502
+
503
+ var setupTypeInfo = function(test, maskPattern) {
504
+
505
+ var data = (_errorCorrectionLevel << 3) | maskPattern;
506
+ var bits = QRUtil.getBCHTypeInfo(data);
507
+
508
+ // vertical
509
+ for (var i = 0; i < 15; i += 1) {
510
+
511
+ var mod = (!test && ( (bits >> i) & 1) == 1);
512
+
513
+ if (i < 6) {
514
+ _modules[i][8] = mod;
515
+ } else if (i < 8) {
516
+ _modules[i + 1][8] = mod;
517
+ } else {
518
+ _modules[_moduleCount - 15 + i][8] = mod;
519
+ }
520
+ }
521
+
522
+ // horizontal
523
+ for (var i = 0; i < 15; i += 1) {
524
+
525
+ var mod = (!test && ( (bits >> i) & 1) == 1);
526
+
527
+ if (i < 8) {
528
+ _modules[8][_moduleCount - i - 1] = mod;
529
+ } else if (i < 9) {
530
+ _modules[8][15 - i - 1 + 1] = mod;
531
+ } else {
532
+ _modules[8][15 - i - 1] = mod;
533
+ }
534
+ }
535
+
536
+ // fixed module
537
+ _modules[_moduleCount - 8][8] = (!test);
538
+ };
539
+
540
+ var mapData = function(data, maskPattern) {
541
+
542
+ var inc = -1;
543
+ var row = _moduleCount - 1;
544
+ var bitIndex = 7;
545
+ var byteIndex = 0;
546
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
547
+
548
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
549
+
550
+ if (col == 6) col -= 1;
551
+
552
+ while (true) {
553
+
554
+ for (var c = 0; c < 2; c += 1) {
555
+
556
+ if (_modules[row][col - c] == null) {
557
+
558
+ var dark = false;
559
+
560
+ if (byteIndex < data.length) {
561
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
562
+ }
563
+
564
+ var mask = maskFunc(row, col - c);
565
+
566
+ if (mask) {
567
+ dark = !dark;
568
+ }
569
+
570
+ _modules[row][col - c] = dark;
571
+ bitIndex -= 1;
572
+
573
+ if (bitIndex == -1) {
574
+ byteIndex += 1;
575
+ bitIndex = 7;
576
+ }
577
+ }
578
+ }
579
+
580
+ row += inc;
581
+
582
+ if (row < 0 || _moduleCount <= row) {
583
+ row -= inc;
584
+ inc = -inc;
585
+ break;
586
+ }
587
+ }
588
+ }
589
+ };
590
+
591
+ var createBytes = function(buffer, rsBlocks) {
592
+
593
+ var offset = 0;
594
+
595
+ var maxDcCount = 0;
596
+ var maxEcCount = 0;
597
+
598
+ var dcdata = new Array(rsBlocks.length);
599
+ var ecdata = new Array(rsBlocks.length);
600
+
601
+ for (var r = 0; r < rsBlocks.length; r += 1) {
602
+
603
+ var dcCount = rsBlocks[r].dataCount;
604
+ var ecCount = rsBlocks[r].totalCount - dcCount;
605
+
606
+ maxDcCount = Math.max(maxDcCount, dcCount);
607
+ maxEcCount = Math.max(maxEcCount, ecCount);
608
+
609
+ dcdata[r] = new Array(dcCount);
610
+
611
+ for (var i = 0; i < dcdata[r].length; i += 1) {
612
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
613
+ }
614
+ offset += dcCount;
615
+
616
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
617
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
618
+
619
+ var modPoly = rawPoly.mod(rsPoly);
620
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
621
+ for (var i = 0; i < ecdata[r].length; i += 1) {
622
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
623
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
624
+ }
625
+ }
626
+
627
+ var totalCodeCount = 0;
628
+ for (var i = 0; i < rsBlocks.length; i += 1) {
629
+ totalCodeCount += rsBlocks[i].totalCount;
630
+ }
631
+
632
+ var data = new Array(totalCodeCount);
633
+ var index = 0;
634
+
635
+ for (var i = 0; i < maxDcCount; i += 1) {
636
+ for (var r = 0; r < rsBlocks.length; r += 1) {
637
+ if (i < dcdata[r].length) {
638
+ data[index] = dcdata[r][i];
639
+ index += 1;
640
+ }
641
+ }
642
+ }
643
+
644
+ for (var i = 0; i < maxEcCount; i += 1) {
645
+ for (var r = 0; r < rsBlocks.length; r += 1) {
646
+ if (i < ecdata[r].length) {
647
+ data[index] = ecdata[r][i];
648
+ index += 1;
649
+ }
650
+ }
651
+ }
652
+
653
+ return data;
654
+ };
655
+
656
+ var createData = function(typeNumber, errorCorrectionLevel, dataList) {
657
+
658
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);
659
+
660
+ var buffer = qrBitBuffer();
661
+
662
+ for (var i = 0; i < dataList.length; i += 1) {
663
+ var data = dataList[i];
664
+ buffer.put(data.getMode(), 4);
665
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
666
+ data.write(buffer);
667
+ }
668
+
669
+ // calc num max data.
670
+ var totalDataCount = 0;
671
+ for (var i = 0; i < rsBlocks.length; i += 1) {
672
+ totalDataCount += rsBlocks[i].dataCount;
673
+ }
674
+
675
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
676
+ throw 'code length overflow. ('
677
+ + buffer.getLengthInBits()
678
+ + '>'
679
+ + totalDataCount * 8
680
+ + ')';
681
+ }
682
+
683
+ // end code
684
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
685
+ buffer.put(0, 4);
686
+ }
687
+
688
+ // padding
689
+ while (buffer.getLengthInBits() % 8 != 0) {
690
+ buffer.putBit(false);
691
+ }
692
+
693
+ // padding
694
+ while (true) {
695
+
696
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
697
+ break;
698
+ }
699
+ buffer.put(PAD0, 8);
700
+
701
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
702
+ break;
703
+ }
704
+ buffer.put(PAD1, 8);
705
+ }
706
+
707
+ return createBytes(buffer, rsBlocks);
708
+ };
709
+
710
+ _this.addData = function(data, mode) {
711
+
712
+ mode = mode || 'Byte';
713
+
714
+ var newData = null;
715
+
716
+ switch(mode) {
717
+ case 'Numeric' :
718
+ newData = qrNumber(data);
719
+ break;
720
+ case 'Alphanumeric' :
721
+ newData = qrAlphaNum(data);
722
+ break;
723
+ case 'Byte' :
724
+ newData = qr8BitByte(data);
725
+ break;
726
+ case 'Kanji' :
727
+ newData = qrKanji(data);
728
+ break;
729
+ default :
730
+ throw 'mode:' + mode;
731
+ }
732
+
733
+ _dataList.push(newData);
734
+ _dataCache = null;
735
+ };
736
+
737
+ _this.isDark = function(row, col) {
738
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
739
+ throw row + ',' + col;
740
+ }
741
+ return _modules[row][col];
742
+ };
743
+
744
+ _this.getModuleCount = function() {
745
+ return _moduleCount;
746
+ };
747
+
748
+ _this.make = function() {
749
+ if (_typeNumber < 1) {
750
+ var typeNumber = 1;
751
+
752
+ for (; typeNumber < 40; typeNumber++) {
753
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
754
+ var buffer = qrBitBuffer();
755
+
756
+ for (var i = 0; i < _dataList.length; i++) {
757
+ var data = _dataList[i];
758
+ buffer.put(data.getMode(), 4);
759
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
760
+ data.write(buffer);
761
+ }
762
+
763
+ var totalDataCount = 0;
764
+ for (var i = 0; i < rsBlocks.length; i++) {
765
+ totalDataCount += rsBlocks[i].dataCount;
766
+ }
767
+
768
+ if (buffer.getLengthInBits() <= totalDataCount * 8) {
769
+ break;
770
+ }
771
+ }
772
+
773
+ _typeNumber = typeNumber;
774
+ }
775
+
776
+ makeImpl(false, getBestMaskPattern() );
777
+ };
778
+
779
+ _this.createTableTag = function(cellSize, margin) {
780
+
781
+ cellSize = cellSize || 2;
782
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
783
+
784
+ var qrHtml = '';
785
+
786
+ qrHtml += '<table style="';
787
+ qrHtml += ' border-width: 0px; border-style: none;';
788
+ qrHtml += ' border-collapse: collapse;';
789
+ qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
790
+ qrHtml += '">';
791
+ qrHtml += '<tbody>';
792
+
793
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
794
+
795
+ qrHtml += '<tr>';
796
+
797
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
798
+ qrHtml += '<td style="';
799
+ qrHtml += ' border-width: 0px; border-style: none;';
800
+ qrHtml += ' border-collapse: collapse;';
801
+ qrHtml += ' padding: 0px; margin: 0px;';
802
+ qrHtml += ' width: ' + cellSize + 'px;';
803
+ qrHtml += ' height: ' + cellSize + 'px;';
804
+ qrHtml += ' background-color: ';
805
+ qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff';
806
+ qrHtml += ';';
807
+ qrHtml += '"/>';
808
+ }
809
+
810
+ qrHtml += '</tr>';
811
+ }
812
+
813
+ qrHtml += '</tbody>';
814
+ qrHtml += '</table>';
815
+
816
+ return qrHtml;
817
+ };
818
+
819
+ _this.createSvgTag = function(cellSize, margin, alt, title) {
820
+
821
+ var opts = {};
822
+ if (typeof arguments[0] == 'object') {
823
+ // Called by options.
824
+ opts = arguments[0];
825
+ // overwrite cellSize and margin.
826
+ cellSize = opts.cellSize;
827
+ margin = opts.margin;
828
+ alt = opts.alt;
829
+ title = opts.title;
830
+ }
831
+
832
+ cellSize = cellSize || 2;
833
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
834
+
835
+ // Compose alt property surrogate
836
+ alt = (typeof alt === 'string') ? {text: alt} : alt || {};
837
+ alt.text = alt.text || null;
838
+ alt.id = (alt.text) ? alt.id || 'qrcode-description' : null;
839
+
840
+ // Compose title property surrogate
841
+ title = (typeof title === 'string') ? {text: title} : title || {};
842
+ title.text = title.text || null;
843
+ title.id = (title.text) ? title.id || 'qrcode-title' : null;
844
+
845
+ var size = _this.getModuleCount() * cellSize + margin * 2;
846
+ var c, mc, r, mr, qrSvg='', rect;
847
+
848
+ rect = 'l' + cellSize + ',0 0,' + cellSize +
849
+ ' -' + cellSize + ',0 0,-' + cellSize + 'z ';
850
+
851
+ qrSvg += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg"';
852
+ qrSvg += !opts.scalable ? ' width="' + size + 'px" height="' + size + 'px"' : '';
853
+ qrSvg += ' viewBox="0 0 ' + size + ' ' + size + '" ';
854
+ qrSvg += ' preserveAspectRatio="xMinYMin meet"';
855
+ qrSvg += (title.text || alt.text) ? ' role="img" aria-labelledby="' +
856
+ escapeXml([title.id, alt.id].join(' ').trim() ) + '"' : '';
857
+ qrSvg += '>';
858
+ qrSvg += (title.text) ? '<title id="' + escapeXml(title.id) + '">' +
859
+ escapeXml(title.text) + '</title>' : '';
860
+ qrSvg += (alt.text) ? '<description id="' + escapeXml(alt.id) + '">' +
861
+ escapeXml(alt.text) + '</description>' : '';
862
+ qrSvg += '<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>';
863
+ qrSvg += '<path d="';
864
+
865
+ for (r = 0; r < _this.getModuleCount(); r += 1) {
866
+ mr = r * cellSize + margin;
867
+ for (c = 0; c < _this.getModuleCount(); c += 1) {
868
+ if (_this.isDark(r, c) ) {
869
+ mc = c*cellSize+margin;
870
+ qrSvg += 'M' + mc + ',' + mr + rect;
871
+ }
872
+ }
873
+ }
874
+
875
+ qrSvg += '" stroke="transparent" fill="black"/>';
876
+ qrSvg += '</svg>';
877
+
878
+ return qrSvg;
879
+ };
880
+
881
+ _this.createDataURL = function(cellSize, margin) {
882
+
883
+ cellSize = cellSize || 2;
884
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
885
+
886
+ var size = _this.getModuleCount() * cellSize + margin * 2;
887
+ var min = margin;
888
+ var max = size - margin;
889
+
890
+ return createDataURL(size, size, function(x, y) {
891
+ if (min <= x && x < max && min <= y && y < max) {
892
+ var c = Math.floor( (x - min) / cellSize);
893
+ var r = Math.floor( (y - min) / cellSize);
894
+ return _this.isDark(r, c)? 0 : 1;
895
+ } else {
896
+ return 1;
897
+ }
898
+ } );
899
+ };
900
+
901
+ _this.createImgTag = function(cellSize, margin, alt) {
902
+
903
+ cellSize = cellSize || 2;
904
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
905
+
906
+ var size = _this.getModuleCount() * cellSize + margin * 2;
907
+
908
+ var img = '';
909
+ img += '<img';
910
+ img += '\u0020src="';
911
+ img += _this.createDataURL(cellSize, margin);
912
+ img += '"';
913
+ img += '\u0020width="';
914
+ img += size;
915
+ img += '"';
916
+ img += '\u0020height="';
917
+ img += size;
918
+ img += '"';
919
+ if (alt) {
920
+ img += '\u0020alt="';
921
+ img += escapeXml(alt);
922
+ img += '"';
923
+ }
924
+ img += '/>';
925
+
926
+ return img;
927
+ };
928
+
929
+ var escapeXml = function(s) {
930
+ var escaped = '';
931
+ for (var i = 0; i < s.length; i += 1) {
932
+ var c = s.charAt(i);
933
+ switch(c) {
934
+ case '<': escaped += '&lt;'; break;
935
+ case '>': escaped += '&gt;'; break;
936
+ case '&': escaped += '&amp;'; break;
937
+ case '"': escaped += '&quot;'; break;
938
+ default : escaped += c; break;
939
+ }
940
+ }
941
+ return escaped;
942
+ };
943
+
944
+ var _createHalfASCII = function(margin) {
945
+ var cellSize = 1;
946
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
947
+
948
+ var size = _this.getModuleCount() * cellSize + margin * 2;
949
+ var min = margin;
950
+ var max = size - margin;
951
+
952
+ var y, x, r1, r2, p;
953
+
954
+ var blocks = {
955
+ '██': '█',
956
+ '█ ': '▀',
957
+ ' █': '▄',
958
+ ' ': ' '
959
+ };
960
+
961
+ var blocksLastLineNoMargin = {
962
+ '██': '▀',
963
+ '█ ': '▀',
964
+ ' █': ' ',
965
+ ' ': ' '
966
+ };
967
+
968
+ var ascii = '';
969
+ for (y = 0; y < size; y += 2) {
970
+ r1 = Math.floor((y - min) / cellSize);
971
+ r2 = Math.floor((y + 1 - min) / cellSize);
972
+ for (x = 0; x < size; x += 1) {
973
+ p = '█';
974
+
975
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
976
+ p = ' ';
977
+ }
978
+
979
+ if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
980
+ p += ' ';
981
+ }
982
+ else {
983
+ p += '█';
984
+ }
985
+
986
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
987
+ ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p];
988
+ }
989
+
990
+ ascii += '\n';
991
+ }
992
+
993
+ if (size % 2 && margin > 0) {
994
+ return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀');
995
+ }
996
+
997
+ return ascii.substring(0, ascii.length-1);
998
+ };
999
+
1000
+ _this.createASCII = function(cellSize, margin) {
1001
+ cellSize = cellSize || 1;
1002
+
1003
+ if (cellSize < 2) {
1004
+ return _createHalfASCII(margin);
1005
+ }
1006
+
1007
+ cellSize -= 1;
1008
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
1009
+
1010
+ var size = _this.getModuleCount() * cellSize + margin * 2;
1011
+ var min = margin;
1012
+ var max = size - margin;
1013
+
1014
+ var y, x, r, p;
1015
+
1016
+ var white = Array(cellSize+1).join('██');
1017
+ var black = Array(cellSize+1).join(' ');
1018
+
1019
+ var ascii = '';
1020
+ var line = '';
1021
+ for (y = 0; y < size; y += 1) {
1022
+ r = Math.floor( (y - min) / cellSize);
1023
+ line = '';
1024
+ for (x = 0; x < size; x += 1) {
1025
+ p = 1;
1026
+
1027
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
1028
+ p = 0;
1029
+ }
1030
+
1031
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
1032
+ line += p ? white : black;
1033
+ }
1034
+
1035
+ for (r = 0; r < cellSize; r += 1) {
1036
+ ascii += line + '\n';
1037
+ }
1038
+ }
1039
+
1040
+ return ascii.substring(0, ascii.length-1);
1041
+ };
1042
+
1043
+ _this.renderTo2dContext = function(context, cellSize) {
1044
+ cellSize = cellSize || 2;
1045
+ var length = _this.getModuleCount();
1046
+ for (var row = 0; row < length; row++) {
1047
+ for (var col = 0; col < length; col++) {
1048
+ context.fillStyle = _this.isDark(row, col) ? 'black' : 'white';
1049
+ context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize);
1050
+ }
1051
+ }
1052
+ };
1053
+
1054
+ return _this;
1055
+ };
1056
+
1057
+ //---------------------------------------------------------------------
1058
+ // qrcode.stringToBytes
1059
+ //---------------------------------------------------------------------
1060
+
1061
+ qrcode.stringToBytesFuncs = {
1062
+ 'default' : function(s) {
1063
+ var bytes = [];
1064
+ for (var i = 0; i < s.length; i += 1) {
1065
+ var c = s.charCodeAt(i);
1066
+ bytes.push(c & 0xff);
1067
+ }
1068
+ return bytes;
1069
+ }
1070
+ };
1071
+
1072
+ qrcode.stringToBytes = qrcode.stringToBytesFuncs['default'];
1073
+
1074
+ //---------------------------------------------------------------------
1075
+ // qrcode.createStringToBytes
1076
+ //---------------------------------------------------------------------
1077
+
1078
+ /**
1079
+ * @param unicodeData base64 string of byte array.
1080
+ * [16bit Unicode],[16bit Bytes], ...
1081
+ * @param numChars
1082
+ */
1083
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
1084
+
1085
+ // create conversion map.
1086
+
1087
+ var unicodeMap = function() {
1088
+
1089
+ var bin = base64DecodeInputStream(unicodeData);
1090
+ var read = function() {
1091
+ var b = bin.read();
1092
+ if (b == -1) throw 'eof';
1093
+ return b;
1094
+ };
1095
+
1096
+ var count = 0;
1097
+ var unicodeMap = {};
1098
+ while (true) {
1099
+ var b0 = bin.read();
1100
+ if (b0 == -1) break;
1101
+ var b1 = read();
1102
+ var b2 = read();
1103
+ var b3 = read();
1104
+ var k = String.fromCharCode( (b0 << 8) | b1);
1105
+ var v = (b2 << 8) | b3;
1106
+ unicodeMap[k] = v;
1107
+ count += 1;
1108
+ }
1109
+ if (count != numChars) {
1110
+ throw count + ' != ' + numChars;
1111
+ }
1112
+
1113
+ return unicodeMap;
1114
+ }();
1115
+
1116
+ var unknownChar = '?'.charCodeAt(0);
1117
+
1118
+ return function(s) {
1119
+ var bytes = [];
1120
+ for (var i = 0; i < s.length; i += 1) {
1121
+ var c = s.charCodeAt(i);
1122
+ if (c < 128) {
1123
+ bytes.push(c);
1124
+ } else {
1125
+ var b = unicodeMap[s.charAt(i)];
1126
+ if (typeof b == 'number') {
1127
+ if ( (b & 0xff) == b) {
1128
+ // 1byte
1129
+ bytes.push(b);
1130
+ } else {
1131
+ // 2bytes
1132
+ bytes.push(b >>> 8);
1133
+ bytes.push(b & 0xff);
1134
+ }
1135
+ } else {
1136
+ bytes.push(unknownChar);
1137
+ }
1138
+ }
1139
+ }
1140
+ return bytes;
1141
+ };
1142
+ };
1143
+
1144
+ //---------------------------------------------------------------------
1145
+ // QRMode
1146
+ //---------------------------------------------------------------------
1147
+
1148
+ var QRMode = {
1149
+ MODE_NUMBER : 1 << 0,
1150
+ MODE_ALPHA_NUM : 1 << 1,
1151
+ MODE_8BIT_BYTE : 1 << 2,
1152
+ MODE_KANJI : 1 << 3
1153
+ };
1154
+
1155
+ //---------------------------------------------------------------------
1156
+ // QRErrorCorrectionLevel
1157
+ //---------------------------------------------------------------------
1158
+
1159
+ var QRErrorCorrectionLevel = {
1160
+ L : 1,
1161
+ M : 0,
1162
+ Q : 3,
1163
+ H : 2
1164
+ };
1165
+
1166
+ //---------------------------------------------------------------------
1167
+ // QRMaskPattern
1168
+ //---------------------------------------------------------------------
1169
+
1170
+ var QRMaskPattern = {
1171
+ PATTERN000 : 0,
1172
+ PATTERN001 : 1,
1173
+ PATTERN010 : 2,
1174
+ PATTERN011 : 3,
1175
+ PATTERN100 : 4,
1176
+ PATTERN101 : 5,
1177
+ PATTERN110 : 6,
1178
+ PATTERN111 : 7
1179
+ };
1180
+
1181
+ //---------------------------------------------------------------------
1182
+ // QRUtil
1183
+ //---------------------------------------------------------------------
1184
+
1185
+ var QRUtil = function() {
1186
+
1187
+ var PATTERN_POSITION_TABLE = [
1188
+ [],
1189
+ [6, 18],
1190
+ [6, 22],
1191
+ [6, 26],
1192
+ [6, 30],
1193
+ [6, 34],
1194
+ [6, 22, 38],
1195
+ [6, 24, 42],
1196
+ [6, 26, 46],
1197
+ [6, 28, 50],
1198
+ [6, 30, 54],
1199
+ [6, 32, 58],
1200
+ [6, 34, 62],
1201
+ [6, 26, 46, 66],
1202
+ [6, 26, 48, 70],
1203
+ [6, 26, 50, 74],
1204
+ [6, 30, 54, 78],
1205
+ [6, 30, 56, 82],
1206
+ [6, 30, 58, 86],
1207
+ [6, 34, 62, 90],
1208
+ [6, 28, 50, 72, 94],
1209
+ [6, 26, 50, 74, 98],
1210
+ [6, 30, 54, 78, 102],
1211
+ [6, 28, 54, 80, 106],
1212
+ [6, 32, 58, 84, 110],
1213
+ [6, 30, 58, 86, 114],
1214
+ [6, 34, 62, 90, 118],
1215
+ [6, 26, 50, 74, 98, 122],
1216
+ [6, 30, 54, 78, 102, 126],
1217
+ [6, 26, 52, 78, 104, 130],
1218
+ [6, 30, 56, 82, 108, 134],
1219
+ [6, 34, 60, 86, 112, 138],
1220
+ [6, 30, 58, 86, 114, 142],
1221
+ [6, 34, 62, 90, 118, 146],
1222
+ [6, 30, 54, 78, 102, 126, 150],
1223
+ [6, 24, 50, 76, 102, 128, 154],
1224
+ [6, 28, 54, 80, 106, 132, 158],
1225
+ [6, 32, 58, 84, 110, 136, 162],
1226
+ [6, 26, 54, 82, 110, 138, 166],
1227
+ [6, 30, 58, 86, 114, 142, 170]
1228
+ ];
1229
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
1230
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
1231
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
1232
+
1233
+ var _this = {};
1234
+
1235
+ var getBCHDigit = function(data) {
1236
+ var digit = 0;
1237
+ while (data != 0) {
1238
+ digit += 1;
1239
+ data >>>= 1;
1240
+ }
1241
+ return digit;
1242
+ };
1243
+
1244
+ _this.getBCHTypeInfo = function(data) {
1245
+ var d = data << 10;
1246
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
1247
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
1248
+ }
1249
+ return ( (data << 10) | d) ^ G15_MASK;
1250
+ };
1251
+
1252
+ _this.getBCHTypeNumber = function(data) {
1253
+ var d = data << 12;
1254
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
1255
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
1256
+ }
1257
+ return (data << 12) | d;
1258
+ };
1259
+
1260
+ _this.getPatternPosition = function(typeNumber) {
1261
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
1262
+ };
1263
+
1264
+ _this.getMaskFunction = function(maskPattern) {
1265
+
1266
+ switch (maskPattern) {
1267
+
1268
+ case QRMaskPattern.PATTERN000 :
1269
+ return function(i, j) { return (i + j) % 2 == 0; };
1270
+ case QRMaskPattern.PATTERN001 :
1271
+ return function(i, j) { return i % 2 == 0; };
1272
+ case QRMaskPattern.PATTERN010 :
1273
+ return function(i, j) { return j % 3 == 0; };
1274
+ case QRMaskPattern.PATTERN011 :
1275
+ return function(i, j) { return (i + j) % 3 == 0; };
1276
+ case QRMaskPattern.PATTERN100 :
1277
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
1278
+ case QRMaskPattern.PATTERN101 :
1279
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
1280
+ case QRMaskPattern.PATTERN110 :
1281
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
1282
+ case QRMaskPattern.PATTERN111 :
1283
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
1284
+
1285
+ default :
1286
+ throw 'bad maskPattern:' + maskPattern;
1287
+ }
1288
+ };
1289
+
1290
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
1291
+ var a = qrPolynomial([1], 0);
1292
+ for (var i = 0; i < errorCorrectLength; i += 1) {
1293
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
1294
+ }
1295
+ return a;
1296
+ };
1297
+
1298
+ _this.getLengthInBits = function(mode, type) {
1299
+
1300
+ if (1 <= type && type < 10) {
1301
+
1302
+ // 1 - 9
1303
+
1304
+ switch(mode) {
1305
+ case QRMode.MODE_NUMBER : return 10;
1306
+ case QRMode.MODE_ALPHA_NUM : return 9;
1307
+ case QRMode.MODE_8BIT_BYTE : return 8;
1308
+ case QRMode.MODE_KANJI : return 8;
1309
+ default :
1310
+ throw 'mode:' + mode;
1311
+ }
1312
+
1313
+ } else if (type < 27) {
1314
+
1315
+ // 10 - 26
1316
+
1317
+ switch(mode) {
1318
+ case QRMode.MODE_NUMBER : return 12;
1319
+ case QRMode.MODE_ALPHA_NUM : return 11;
1320
+ case QRMode.MODE_8BIT_BYTE : return 16;
1321
+ case QRMode.MODE_KANJI : return 10;
1322
+ default :
1323
+ throw 'mode:' + mode;
1324
+ }
1325
+
1326
+ } else if (type < 41) {
1327
+
1328
+ // 27 - 40
1329
+
1330
+ switch(mode) {
1331
+ case QRMode.MODE_NUMBER : return 14;
1332
+ case QRMode.MODE_ALPHA_NUM : return 13;
1333
+ case QRMode.MODE_8BIT_BYTE : return 16;
1334
+ case QRMode.MODE_KANJI : return 12;
1335
+ default :
1336
+ throw 'mode:' + mode;
1337
+ }
1338
+
1339
+ } else {
1340
+ throw 'type:' + type;
1341
+ }
1342
+ };
1343
+
1344
+ _this.getLostPoint = function(qrcode) {
1345
+
1346
+ var moduleCount = qrcode.getModuleCount();
1347
+
1348
+ var lostPoint = 0;
1349
+
1350
+ // LEVEL1
1351
+
1352
+ for (var row = 0; row < moduleCount; row += 1) {
1353
+ for (var col = 0; col < moduleCount; col += 1) {
1354
+
1355
+ var sameCount = 0;
1356
+ var dark = qrcode.isDark(row, col);
1357
+
1358
+ for (var r = -1; r <= 1; r += 1) {
1359
+
1360
+ if (row + r < 0 || moduleCount <= row + r) {
1361
+ continue;
1362
+ }
1363
+
1364
+ for (var c = -1; c <= 1; c += 1) {
1365
+
1366
+ if (col + c < 0 || moduleCount <= col + c) {
1367
+ continue;
1368
+ }
1369
+
1370
+ if (r == 0 && c == 0) {
1371
+ continue;
1372
+ }
1373
+
1374
+ if (dark == qrcode.isDark(row + r, col + c) ) {
1375
+ sameCount += 1;
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ if (sameCount > 5) {
1381
+ lostPoint += (3 + sameCount - 5);
1382
+ }
1383
+ }
1384
+ }
1385
+ // LEVEL2
1386
+
1387
+ for (var row = 0; row < moduleCount - 1; row += 1) {
1388
+ for (var col = 0; col < moduleCount - 1; col += 1) {
1389
+ var count = 0;
1390
+ if (qrcode.isDark(row, col) ) count += 1;
1391
+ if (qrcode.isDark(row + 1, col) ) count += 1;
1392
+ if (qrcode.isDark(row, col + 1) ) count += 1;
1393
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
1394
+ if (count == 0 || count == 4) {
1395
+ lostPoint += 3;
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ // LEVEL3
1401
+
1402
+ for (var row = 0; row < moduleCount; row += 1) {
1403
+ for (var col = 0; col < moduleCount - 6; col += 1) {
1404
+ if (qrcode.isDark(row, col)
1405
+ && !qrcode.isDark(row, col + 1)
1406
+ && qrcode.isDark(row, col + 2)
1407
+ && qrcode.isDark(row, col + 3)
1408
+ && qrcode.isDark(row, col + 4)
1409
+ && !qrcode.isDark(row, col + 5)
1410
+ && qrcode.isDark(row, col + 6) ) {
1411
+ lostPoint += 40;
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ for (var col = 0; col < moduleCount; col += 1) {
1417
+ for (var row = 0; row < moduleCount - 6; row += 1) {
1418
+ if (qrcode.isDark(row, col)
1419
+ && !qrcode.isDark(row + 1, col)
1420
+ && qrcode.isDark(row + 2, col)
1421
+ && qrcode.isDark(row + 3, col)
1422
+ && qrcode.isDark(row + 4, col)
1423
+ && !qrcode.isDark(row + 5, col)
1424
+ && qrcode.isDark(row + 6, col) ) {
1425
+ lostPoint += 40;
1426
+ }
1427
+ }
1428
+ }
1429
+
1430
+ // LEVEL4
1431
+
1432
+ var darkCount = 0;
1433
+
1434
+ for (var col = 0; col < moduleCount; col += 1) {
1435
+ for (var row = 0; row < moduleCount; row += 1) {
1436
+ if (qrcode.isDark(row, col) ) {
1437
+ darkCount += 1;
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
1443
+ lostPoint += ratio * 10;
1444
+
1445
+ return lostPoint;
1446
+ };
1447
+
1448
+ return _this;
1449
+ }();
1450
+
1451
+ //---------------------------------------------------------------------
1452
+ // QRMath
1453
+ //---------------------------------------------------------------------
1454
+
1455
+ var QRMath = function() {
1456
+
1457
+ var EXP_TABLE = new Array(256);
1458
+ var LOG_TABLE = new Array(256);
1459
+
1460
+ // initialize tables
1461
+ for (var i = 0; i < 8; i += 1) {
1462
+ EXP_TABLE[i] = 1 << i;
1463
+ }
1464
+ for (var i = 8; i < 256; i += 1) {
1465
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
1466
+ ^ EXP_TABLE[i - 5]
1467
+ ^ EXP_TABLE[i - 6]
1468
+ ^ EXP_TABLE[i - 8];
1469
+ }
1470
+ for (var i = 0; i < 255; i += 1) {
1471
+ LOG_TABLE[EXP_TABLE[i] ] = i;
1472
+ }
1473
+
1474
+ var _this = {};
1475
+
1476
+ _this.glog = function(n) {
1477
+
1478
+ if (n < 1) {
1479
+ throw 'glog(' + n + ')';
1480
+ }
1481
+
1482
+ return LOG_TABLE[n];
1483
+ };
1484
+
1485
+ _this.gexp = function(n) {
1486
+
1487
+ while (n < 0) {
1488
+ n += 255;
1489
+ }
1490
+
1491
+ while (n >= 256) {
1492
+ n -= 255;
1493
+ }
1494
+
1495
+ return EXP_TABLE[n];
1496
+ };
1497
+
1498
+ return _this;
1499
+ }();
1500
+
1501
+ //---------------------------------------------------------------------
1502
+ // qrPolynomial
1503
+ //---------------------------------------------------------------------
1504
+
1505
+ function qrPolynomial(num, shift) {
1506
+
1507
+ if (typeof num.length == 'undefined') {
1508
+ throw num.length + '/' + shift;
1509
+ }
1510
+
1511
+ var _num = function() {
1512
+ var offset = 0;
1513
+ while (offset < num.length && num[offset] == 0) {
1514
+ offset += 1;
1515
+ }
1516
+ var _num = new Array(num.length - offset + shift);
1517
+ for (var i = 0; i < num.length - offset; i += 1) {
1518
+ _num[i] = num[i + offset];
1519
+ }
1520
+ return _num;
1521
+ }();
1522
+
1523
+ var _this = {};
1524
+
1525
+ _this.getAt = function(index) {
1526
+ return _num[index];
1527
+ };
1528
+
1529
+ _this.getLength = function() {
1530
+ return _num.length;
1531
+ };
1532
+
1533
+ _this.multiply = function(e) {
1534
+
1535
+ var num = new Array(_this.getLength() + e.getLength() - 1);
1536
+
1537
+ for (var i = 0; i < _this.getLength(); i += 1) {
1538
+ for (var j = 0; j < e.getLength(); j += 1) {
1539
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
1540
+ }
1541
+ }
1542
+
1543
+ return qrPolynomial(num, 0);
1544
+ };
1545
+
1546
+ _this.mod = function(e) {
1547
+
1548
+ if (_this.getLength() - e.getLength() < 0) {
1549
+ return _this;
1550
+ }
1551
+
1552
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
1553
+
1554
+ var num = new Array(_this.getLength() );
1555
+ for (var i = 0; i < _this.getLength(); i += 1) {
1556
+ num[i] = _this.getAt(i);
1557
+ }
1558
+
1559
+ for (var i = 0; i < e.getLength(); i += 1) {
1560
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
1561
+ }
1562
+
1563
+ // recursive call
1564
+ return qrPolynomial(num, 0).mod(e);
1565
+ };
1566
+
1567
+ return _this;
1568
+ }
1569
+ //---------------------------------------------------------------------
1570
+ // QRRSBlock
1571
+ //---------------------------------------------------------------------
1572
+
1573
+ var QRRSBlock = function() {
1574
+
1575
+ var RS_BLOCK_TABLE = [
1576
+
1577
+ // L
1578
+ // M
1579
+ // Q
1580
+ // H
1581
+
1582
+ // 1
1583
+ [1, 26, 19],
1584
+ [1, 26, 16],
1585
+ [1, 26, 13],
1586
+ [1, 26, 9],
1587
+
1588
+ // 2
1589
+ [1, 44, 34],
1590
+ [1, 44, 28],
1591
+ [1, 44, 22],
1592
+ [1, 44, 16],
1593
+
1594
+ // 3
1595
+ [1, 70, 55],
1596
+ [1, 70, 44],
1597
+ [2, 35, 17],
1598
+ [2, 35, 13],
1599
+
1600
+ // 4
1601
+ [1, 100, 80],
1602
+ [2, 50, 32],
1603
+ [2, 50, 24],
1604
+ [4, 25, 9],
1605
+
1606
+ // 5
1607
+ [1, 134, 108],
1608
+ [2, 67, 43],
1609
+ [2, 33, 15, 2, 34, 16],
1610
+ [2, 33, 11, 2, 34, 12],
1611
+
1612
+ // 6
1613
+ [2, 86, 68],
1614
+ [4, 43, 27],
1615
+ [4, 43, 19],
1616
+ [4, 43, 15],
1617
+
1618
+ // 7
1619
+ [2, 98, 78],
1620
+ [4, 49, 31],
1621
+ [2, 32, 14, 4, 33, 15],
1622
+ [4, 39, 13, 1, 40, 14],
1623
+
1624
+ // 8
1625
+ [2, 121, 97],
1626
+ [2, 60, 38, 2, 61, 39],
1627
+ [4, 40, 18, 2, 41, 19],
1628
+ [4, 40, 14, 2, 41, 15],
1629
+
1630
+ // 9
1631
+ [2, 146, 116],
1632
+ [3, 58, 36, 2, 59, 37],
1633
+ [4, 36, 16, 4, 37, 17],
1634
+ [4, 36, 12, 4, 37, 13],
1635
+
1636
+ // 10
1637
+ [2, 86, 68, 2, 87, 69],
1638
+ [4, 69, 43, 1, 70, 44],
1639
+ [6, 43, 19, 2, 44, 20],
1640
+ [6, 43, 15, 2, 44, 16],
1641
+
1642
+ // 11
1643
+ [4, 101, 81],
1644
+ [1, 80, 50, 4, 81, 51],
1645
+ [4, 50, 22, 4, 51, 23],
1646
+ [3, 36, 12, 8, 37, 13],
1647
+
1648
+ // 12
1649
+ [2, 116, 92, 2, 117, 93],
1650
+ [6, 58, 36, 2, 59, 37],
1651
+ [4, 46, 20, 6, 47, 21],
1652
+ [7, 42, 14, 4, 43, 15],
1653
+
1654
+ // 13
1655
+ [4, 133, 107],
1656
+ [8, 59, 37, 1, 60, 38],
1657
+ [8, 44, 20, 4, 45, 21],
1658
+ [12, 33, 11, 4, 34, 12],
1659
+
1660
+ // 14
1661
+ [3, 145, 115, 1, 146, 116],
1662
+ [4, 64, 40, 5, 65, 41],
1663
+ [11, 36, 16, 5, 37, 17],
1664
+ [11, 36, 12, 5, 37, 13],
1665
+
1666
+ // 15
1667
+ [5, 109, 87, 1, 110, 88],
1668
+ [5, 65, 41, 5, 66, 42],
1669
+ [5, 54, 24, 7, 55, 25],
1670
+ [11, 36, 12, 7, 37, 13],
1671
+
1672
+ // 16
1673
+ [5, 122, 98, 1, 123, 99],
1674
+ [7, 73, 45, 3, 74, 46],
1675
+ [15, 43, 19, 2, 44, 20],
1676
+ [3, 45, 15, 13, 46, 16],
1677
+
1678
+ // 17
1679
+ [1, 135, 107, 5, 136, 108],
1680
+ [10, 74, 46, 1, 75, 47],
1681
+ [1, 50, 22, 15, 51, 23],
1682
+ [2, 42, 14, 17, 43, 15],
1683
+
1684
+ // 18
1685
+ [5, 150, 120, 1, 151, 121],
1686
+ [9, 69, 43, 4, 70, 44],
1687
+ [17, 50, 22, 1, 51, 23],
1688
+ [2, 42, 14, 19, 43, 15],
1689
+
1690
+ // 19
1691
+ [3, 141, 113, 4, 142, 114],
1692
+ [3, 70, 44, 11, 71, 45],
1693
+ [17, 47, 21, 4, 48, 22],
1694
+ [9, 39, 13, 16, 40, 14],
1695
+
1696
+ // 20
1697
+ [3, 135, 107, 5, 136, 108],
1698
+ [3, 67, 41, 13, 68, 42],
1699
+ [15, 54, 24, 5, 55, 25],
1700
+ [15, 43, 15, 10, 44, 16],
1701
+
1702
+ // 21
1703
+ [4, 144, 116, 4, 145, 117],
1704
+ [17, 68, 42],
1705
+ [17, 50, 22, 6, 51, 23],
1706
+ [19, 46, 16, 6, 47, 17],
1707
+
1708
+ // 22
1709
+ [2, 139, 111, 7, 140, 112],
1710
+ [17, 74, 46],
1711
+ [7, 54, 24, 16, 55, 25],
1712
+ [34, 37, 13],
1713
+
1714
+ // 23
1715
+ [4, 151, 121, 5, 152, 122],
1716
+ [4, 75, 47, 14, 76, 48],
1717
+ [11, 54, 24, 14, 55, 25],
1718
+ [16, 45, 15, 14, 46, 16],
1719
+
1720
+ // 24
1721
+ [6, 147, 117, 4, 148, 118],
1722
+ [6, 73, 45, 14, 74, 46],
1723
+ [11, 54, 24, 16, 55, 25],
1724
+ [30, 46, 16, 2, 47, 17],
1725
+
1726
+ // 25
1727
+ [8, 132, 106, 4, 133, 107],
1728
+ [8, 75, 47, 13, 76, 48],
1729
+ [7, 54, 24, 22, 55, 25],
1730
+ [22, 45, 15, 13, 46, 16],
1731
+
1732
+ // 26
1733
+ [10, 142, 114, 2, 143, 115],
1734
+ [19, 74, 46, 4, 75, 47],
1735
+ [28, 50, 22, 6, 51, 23],
1736
+ [33, 46, 16, 4, 47, 17],
1737
+
1738
+ // 27
1739
+ [8, 152, 122, 4, 153, 123],
1740
+ [22, 73, 45, 3, 74, 46],
1741
+ [8, 53, 23, 26, 54, 24],
1742
+ [12, 45, 15, 28, 46, 16],
1743
+
1744
+ // 28
1745
+ [3, 147, 117, 10, 148, 118],
1746
+ [3, 73, 45, 23, 74, 46],
1747
+ [4, 54, 24, 31, 55, 25],
1748
+ [11, 45, 15, 31, 46, 16],
1749
+
1750
+ // 29
1751
+ [7, 146, 116, 7, 147, 117],
1752
+ [21, 73, 45, 7, 74, 46],
1753
+ [1, 53, 23, 37, 54, 24],
1754
+ [19, 45, 15, 26, 46, 16],
1755
+
1756
+ // 30
1757
+ [5, 145, 115, 10, 146, 116],
1758
+ [19, 75, 47, 10, 76, 48],
1759
+ [15, 54, 24, 25, 55, 25],
1760
+ [23, 45, 15, 25, 46, 16],
1761
+
1762
+ // 31
1763
+ [13, 145, 115, 3, 146, 116],
1764
+ [2, 74, 46, 29, 75, 47],
1765
+ [42, 54, 24, 1, 55, 25],
1766
+ [23, 45, 15, 28, 46, 16],
1767
+
1768
+ // 32
1769
+ [17, 145, 115],
1770
+ [10, 74, 46, 23, 75, 47],
1771
+ [10, 54, 24, 35, 55, 25],
1772
+ [19, 45, 15, 35, 46, 16],
1773
+
1774
+ // 33
1775
+ [17, 145, 115, 1, 146, 116],
1776
+ [14, 74, 46, 21, 75, 47],
1777
+ [29, 54, 24, 19, 55, 25],
1778
+ [11, 45, 15, 46, 46, 16],
1779
+
1780
+ // 34
1781
+ [13, 145, 115, 6, 146, 116],
1782
+ [14, 74, 46, 23, 75, 47],
1783
+ [44, 54, 24, 7, 55, 25],
1784
+ [59, 46, 16, 1, 47, 17],
1785
+
1786
+ // 35
1787
+ [12, 151, 121, 7, 152, 122],
1788
+ [12, 75, 47, 26, 76, 48],
1789
+ [39, 54, 24, 14, 55, 25],
1790
+ [22, 45, 15, 41, 46, 16],
1791
+
1792
+ // 36
1793
+ [6, 151, 121, 14, 152, 122],
1794
+ [6, 75, 47, 34, 76, 48],
1795
+ [46, 54, 24, 10, 55, 25],
1796
+ [2, 45, 15, 64, 46, 16],
1797
+
1798
+ // 37
1799
+ [17, 152, 122, 4, 153, 123],
1800
+ [29, 74, 46, 14, 75, 47],
1801
+ [49, 54, 24, 10, 55, 25],
1802
+ [24, 45, 15, 46, 46, 16],
1803
+
1804
+ // 38
1805
+ [4, 152, 122, 18, 153, 123],
1806
+ [13, 74, 46, 32, 75, 47],
1807
+ [48, 54, 24, 14, 55, 25],
1808
+ [42, 45, 15, 32, 46, 16],
1809
+
1810
+ // 39
1811
+ [20, 147, 117, 4, 148, 118],
1812
+ [40, 75, 47, 7, 76, 48],
1813
+ [43, 54, 24, 22, 55, 25],
1814
+ [10, 45, 15, 67, 46, 16],
1815
+
1816
+ // 40
1817
+ [19, 148, 118, 6, 149, 119],
1818
+ [18, 75, 47, 31, 76, 48],
1819
+ [34, 54, 24, 34, 55, 25],
1820
+ [20, 45, 15, 61, 46, 16]
1821
+ ];
1822
+
1823
+ var qrRSBlock = function(totalCount, dataCount) {
1824
+ var _this = {};
1825
+ _this.totalCount = totalCount;
1826
+ _this.dataCount = dataCount;
1827
+ return _this;
1828
+ };
1829
+
1830
+ var _this = {};
1831
+
1832
+ var getRsBlockTable = function(typeNumber, errorCorrectionLevel) {
1833
+
1834
+ switch(errorCorrectionLevel) {
1835
+ case QRErrorCorrectionLevel.L :
1836
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
1837
+ case QRErrorCorrectionLevel.M :
1838
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
1839
+ case QRErrorCorrectionLevel.Q :
1840
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
1841
+ case QRErrorCorrectionLevel.H :
1842
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
1843
+ default :
1844
+ return undefined;
1845
+ }
1846
+ };
1847
+
1848
+ _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {
1849
+
1850
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);
1851
+
1852
+ if (typeof rsBlock == 'undefined') {
1853
+ throw 'bad rs block @ typeNumber:' + typeNumber +
1854
+ '/errorCorrectionLevel:' + errorCorrectionLevel;
1855
+ }
1856
+
1857
+ var length = rsBlock.length / 3;
1858
+
1859
+ var list = [];
1860
+
1861
+ for (var i = 0; i < length; i += 1) {
1862
+
1863
+ var count = rsBlock[i * 3 + 0];
1864
+ var totalCount = rsBlock[i * 3 + 1];
1865
+ var dataCount = rsBlock[i * 3 + 2];
1866
+
1867
+ for (var j = 0; j < count; j += 1) {
1868
+ list.push(qrRSBlock(totalCount, dataCount) );
1869
+ }
1870
+ }
1871
+
1872
+ return list;
1873
+ };
1874
+
1875
+ return _this;
1876
+ }();
1877
+
1878
+ //---------------------------------------------------------------------
1879
+ // qrBitBuffer
1880
+ //---------------------------------------------------------------------
1881
+
1882
+ var qrBitBuffer = function() {
1883
+
1884
+ var _buffer = [];
1885
+ var _length = 0;
1886
+
1887
+ var _this = {};
1888
+
1889
+ _this.getBuffer = function() {
1890
+ return _buffer;
1891
+ };
1892
+
1893
+ _this.getAt = function(index) {
1894
+ var bufIndex = Math.floor(index / 8);
1895
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
1896
+ };
1897
+
1898
+ _this.put = function(num, length) {
1899
+ for (var i = 0; i < length; i += 1) {
1900
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
1901
+ }
1902
+ };
1903
+
1904
+ _this.getLengthInBits = function() {
1905
+ return _length;
1906
+ };
1907
+
1908
+ _this.putBit = function(bit) {
1909
+
1910
+ var bufIndex = Math.floor(_length / 8);
1911
+ if (_buffer.length <= bufIndex) {
1912
+ _buffer.push(0);
1913
+ }
1914
+
1915
+ if (bit) {
1916
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
1917
+ }
1918
+
1919
+ _length += 1;
1920
+ };
1921
+
1922
+ return _this;
1923
+ };
1924
+
1925
+ //---------------------------------------------------------------------
1926
+ // qrNumber
1927
+ //---------------------------------------------------------------------
1928
+
1929
+ var qrNumber = function(data) {
1930
+
1931
+ var _mode = QRMode.MODE_NUMBER;
1932
+ var _data = data;
1933
+
1934
+ var _this = {};
1935
+
1936
+ _this.getMode = function() {
1937
+ return _mode;
1938
+ };
1939
+
1940
+ _this.getLength = function(buffer) {
1941
+ return _data.length;
1942
+ };
1943
+
1944
+ _this.write = function(buffer) {
1945
+
1946
+ var data = _data;
1947
+
1948
+ var i = 0;
1949
+
1950
+ while (i + 2 < data.length) {
1951
+ buffer.put(strToNum(data.substring(i, i + 3) ), 10);
1952
+ i += 3;
1953
+ }
1954
+
1955
+ if (i < data.length) {
1956
+ if (data.length - i == 1) {
1957
+ buffer.put(strToNum(data.substring(i, i + 1) ), 4);
1958
+ } else if (data.length - i == 2) {
1959
+ buffer.put(strToNum(data.substring(i, i + 2) ), 7);
1960
+ }
1961
+ }
1962
+ };
1963
+
1964
+ var strToNum = function(s) {
1965
+ var num = 0;
1966
+ for (var i = 0; i < s.length; i += 1) {
1967
+ num = num * 10 + chatToNum(s.charAt(i) );
1968
+ }
1969
+ return num;
1970
+ };
1971
+
1972
+ var chatToNum = function(c) {
1973
+ if ('0' <= c && c <= '9') {
1974
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
1975
+ }
1976
+ throw 'illegal char :' + c;
1977
+ };
1978
+
1979
+ return _this;
1980
+ };
1981
+
1982
+ //---------------------------------------------------------------------
1983
+ // qrAlphaNum
1984
+ //---------------------------------------------------------------------
1985
+
1986
+ var qrAlphaNum = function(data) {
1987
+
1988
+ var _mode = QRMode.MODE_ALPHA_NUM;
1989
+ var _data = data;
1990
+
1991
+ var _this = {};
1992
+
1993
+ _this.getMode = function() {
1994
+ return _mode;
1995
+ };
1996
+
1997
+ _this.getLength = function(buffer) {
1998
+ return _data.length;
1999
+ };
2000
+
2001
+ _this.write = function(buffer) {
2002
+
2003
+ var s = _data;
2004
+
2005
+ var i = 0;
2006
+
2007
+ while (i + 1 < s.length) {
2008
+ buffer.put(
2009
+ getCode(s.charAt(i) ) * 45 +
2010
+ getCode(s.charAt(i + 1) ), 11);
2011
+ i += 2;
2012
+ }
2013
+
2014
+ if (i < s.length) {
2015
+ buffer.put(getCode(s.charAt(i) ), 6);
2016
+ }
2017
+ };
2018
+
2019
+ var getCode = function(c) {
2020
+
2021
+ if ('0' <= c && c <= '9') {
2022
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
2023
+ } else if ('A' <= c && c <= 'Z') {
2024
+ return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
2025
+ } else {
2026
+ switch (c) {
2027
+ case ' ' : return 36;
2028
+ case '$' : return 37;
2029
+ case '%' : return 38;
2030
+ case '*' : return 39;
2031
+ case '+' : return 40;
2032
+ case '-' : return 41;
2033
+ case '.' : return 42;
2034
+ case '/' : return 43;
2035
+ case ':' : return 44;
2036
+ default :
2037
+ throw 'illegal char :' + c;
2038
+ }
2039
+ }
2040
+ };
2041
+
2042
+ return _this;
2043
+ };
2044
+
2045
+ //---------------------------------------------------------------------
2046
+ // qr8BitByte
2047
+ //---------------------------------------------------------------------
2048
+
2049
+ var qr8BitByte = function(data) {
2050
+
2051
+ var _mode = QRMode.MODE_8BIT_BYTE;
2052
+ var _bytes = qrcode.stringToBytes(data);
2053
+
2054
+ var _this = {};
2055
+
2056
+ _this.getMode = function() {
2057
+ return _mode;
2058
+ };
2059
+
2060
+ _this.getLength = function(buffer) {
2061
+ return _bytes.length;
2062
+ };
2063
+
2064
+ _this.write = function(buffer) {
2065
+ for (var i = 0; i < _bytes.length; i += 1) {
2066
+ buffer.put(_bytes[i], 8);
2067
+ }
2068
+ };
2069
+
2070
+ return _this;
2071
+ };
2072
+
2073
+ //---------------------------------------------------------------------
2074
+ // qrKanji
2075
+ //---------------------------------------------------------------------
2076
+
2077
+ var qrKanji = function(data) {
2078
+
2079
+ var _mode = QRMode.MODE_KANJI;
2080
+
2081
+ var stringToBytes = qrcode.stringToBytesFuncs['SJIS'];
2082
+ if (!stringToBytes) {
2083
+ throw 'sjis not supported.';
2084
+ }
2085
+ !function(c, code) {
2086
+ // self test for sjis support.
2087
+ var test = stringToBytes(c);
2088
+ if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) {
2089
+ throw 'sjis not supported.';
2090
+ }
2091
+ }('\u53cb', 0x9746);
2092
+
2093
+ var _bytes = stringToBytes(data);
2094
+
2095
+ var _this = {};
2096
+
2097
+ _this.getMode = function() {
2098
+ return _mode;
2099
+ };
2100
+
2101
+ _this.getLength = function(buffer) {
2102
+ return ~~(_bytes.length / 2);
2103
+ };
2104
+
2105
+ _this.write = function(buffer) {
2106
+
2107
+ var data = _bytes;
2108
+
2109
+ var i = 0;
2110
+
2111
+ while (i + 1 < data.length) {
2112
+
2113
+ var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);
2114
+
2115
+ if (0x8140 <= c && c <= 0x9FFC) {
2116
+ c -= 0x8140;
2117
+ } else if (0xE040 <= c && c <= 0xEBBF) {
2118
+ c -= 0xC140;
2119
+ } else {
2120
+ throw 'illegal char at ' + (i + 1) + '/' + c;
2121
+ }
2122
+
2123
+ c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff);
2124
+
2125
+ buffer.put(c, 13);
2126
+
2127
+ i += 2;
2128
+ }
2129
+
2130
+ if (i < data.length) {
2131
+ throw 'illegal char at ' + (i + 1);
2132
+ }
2133
+ };
2134
+
2135
+ return _this;
2136
+ };
2137
+
2138
+ //=====================================================================
2139
+ // GIF Support etc.
2140
+ //
2141
+
2142
+ //---------------------------------------------------------------------
2143
+ // byteArrayOutputStream
2144
+ //---------------------------------------------------------------------
2145
+
2146
+ var byteArrayOutputStream = function() {
2147
+
2148
+ var _bytes = [];
2149
+
2150
+ var _this = {};
2151
+
2152
+ _this.writeByte = function(b) {
2153
+ _bytes.push(b & 0xff);
2154
+ };
2155
+
2156
+ _this.writeShort = function(i) {
2157
+ _this.writeByte(i);
2158
+ _this.writeByte(i >>> 8);
2159
+ };
2160
+
2161
+ _this.writeBytes = function(b, off, len) {
2162
+ off = off || 0;
2163
+ len = len || b.length;
2164
+ for (var i = 0; i < len; i += 1) {
2165
+ _this.writeByte(b[i + off]);
2166
+ }
2167
+ };
2168
+
2169
+ _this.writeString = function(s) {
2170
+ for (var i = 0; i < s.length; i += 1) {
2171
+ _this.writeByte(s.charCodeAt(i) );
2172
+ }
2173
+ };
2174
+
2175
+ _this.toByteArray = function() {
2176
+ return _bytes;
2177
+ };
2178
+
2179
+ _this.toString = function() {
2180
+ var s = '';
2181
+ s += '[';
2182
+ for (var i = 0; i < _bytes.length; i += 1) {
2183
+ if (i > 0) {
2184
+ s += ',';
2185
+ }
2186
+ s += _bytes[i];
2187
+ }
2188
+ s += ']';
2189
+ return s;
2190
+ };
2191
+
2192
+ return _this;
2193
+ };
2194
+
2195
+ //---------------------------------------------------------------------
2196
+ // base64EncodeOutputStream
2197
+ //---------------------------------------------------------------------
2198
+
2199
+ var base64EncodeOutputStream = function() {
2200
+
2201
+ var _buffer = 0;
2202
+ var _buflen = 0;
2203
+ var _length = 0;
2204
+ var _base64 = '';
2205
+
2206
+ var _this = {};
2207
+
2208
+ var writeEncoded = function(b) {
2209
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
2210
+ };
2211
+
2212
+ var encode = function(n) {
2213
+ if (n < 0) ; else if (n < 26) {
2214
+ return 0x41 + n;
2215
+ } else if (n < 52) {
2216
+ return 0x61 + (n - 26);
2217
+ } else if (n < 62) {
2218
+ return 0x30 + (n - 52);
2219
+ } else if (n == 62) {
2220
+ return 0x2b;
2221
+ } else if (n == 63) {
2222
+ return 0x2f;
2223
+ }
2224
+ throw 'n:' + n;
2225
+ };
2226
+
2227
+ _this.writeByte = function(n) {
2228
+
2229
+ _buffer = (_buffer << 8) | (n & 0xff);
2230
+ _buflen += 8;
2231
+ _length += 1;
2232
+
2233
+ while (_buflen >= 6) {
2234
+ writeEncoded(_buffer >>> (_buflen - 6) );
2235
+ _buflen -= 6;
2236
+ }
2237
+ };
2238
+
2239
+ _this.flush = function() {
2240
+
2241
+ if (_buflen > 0) {
2242
+ writeEncoded(_buffer << (6 - _buflen) );
2243
+ _buffer = 0;
2244
+ _buflen = 0;
2245
+ }
2246
+
2247
+ if (_length % 3 != 0) {
2248
+ // padding
2249
+ var padlen = 3 - _length % 3;
2250
+ for (var i = 0; i < padlen; i += 1) {
2251
+ _base64 += '=';
2252
+ }
2253
+ }
2254
+ };
2255
+
2256
+ _this.toString = function() {
2257
+ return _base64;
2258
+ };
2259
+
2260
+ return _this;
2261
+ };
2262
+
2263
+ //---------------------------------------------------------------------
2264
+ // base64DecodeInputStream
2265
+ //---------------------------------------------------------------------
2266
+
2267
+ var base64DecodeInputStream = function(str) {
2268
+
2269
+ var _str = str;
2270
+ var _pos = 0;
2271
+ var _buffer = 0;
2272
+ var _buflen = 0;
2273
+
2274
+ var _this = {};
2275
+
2276
+ _this.read = function() {
2277
+
2278
+ while (_buflen < 8) {
2279
+
2280
+ if (_pos >= _str.length) {
2281
+ if (_buflen == 0) {
2282
+ return -1;
2283
+ }
2284
+ throw 'unexpected end of file./' + _buflen;
2285
+ }
2286
+
2287
+ var c = _str.charAt(_pos);
2288
+ _pos += 1;
2289
+
2290
+ if (c == '=') {
2291
+ _buflen = 0;
2292
+ return -1;
2293
+ } else if (c.match(/^\s$/) ) {
2294
+ // ignore if whitespace.
2295
+ continue;
2296
+ }
2297
+
2298
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
2299
+ _buflen += 6;
2300
+ }
2301
+
2302
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
2303
+ _buflen -= 8;
2304
+ return n;
2305
+ };
2306
+
2307
+ var decode = function(c) {
2308
+ if (0x41 <= c && c <= 0x5a) {
2309
+ return c - 0x41;
2310
+ } else if (0x61 <= c && c <= 0x7a) {
2311
+ return c - 0x61 + 26;
2312
+ } else if (0x30 <= c && c <= 0x39) {
2313
+ return c - 0x30 + 52;
2314
+ } else if (c == 0x2b) {
2315
+ return 62;
2316
+ } else if (c == 0x2f) {
2317
+ return 63;
2318
+ } else {
2319
+ throw 'c:' + c;
2320
+ }
2321
+ };
2322
+
2323
+ return _this;
2324
+ };
2325
+
2326
+ //---------------------------------------------------------------------
2327
+ // gifImage (B/W)
2328
+ //---------------------------------------------------------------------
2329
+
2330
+ var gifImage = function(width, height) {
2331
+
2332
+ var _width = width;
2333
+ var _height = height;
2334
+ var _data = new Array(width * height);
2335
+
2336
+ var _this = {};
2337
+
2338
+ _this.setPixel = function(x, y, pixel) {
2339
+ _data[y * _width + x] = pixel;
2340
+ };
2341
+
2342
+ _this.write = function(out) {
2343
+
2344
+ //---------------------------------
2345
+ // GIF Signature
2346
+
2347
+ out.writeString('GIF87a');
2348
+
2349
+ //---------------------------------
2350
+ // Screen Descriptor
2351
+
2352
+ out.writeShort(_width);
2353
+ out.writeShort(_height);
2354
+
2355
+ out.writeByte(0x80); // 2bit
2356
+ out.writeByte(0);
2357
+ out.writeByte(0);
2358
+
2359
+ //---------------------------------
2360
+ // Global Color Map
2361
+
2362
+ // black
2363
+ out.writeByte(0x00);
2364
+ out.writeByte(0x00);
2365
+ out.writeByte(0x00);
2366
+
2367
+ // white
2368
+ out.writeByte(0xff);
2369
+ out.writeByte(0xff);
2370
+ out.writeByte(0xff);
2371
+
2372
+ //---------------------------------
2373
+ // Image Descriptor
2374
+
2375
+ out.writeString(',');
2376
+ out.writeShort(0);
2377
+ out.writeShort(0);
2378
+ out.writeShort(_width);
2379
+ out.writeShort(_height);
2380
+ out.writeByte(0);
2381
+
2382
+ //---------------------------------
2383
+ // Local Color Map
2384
+
2385
+ //---------------------------------
2386
+ // Raster Data
2387
+
2388
+ var lzwMinCodeSize = 2;
2389
+ var raster = getLZWRaster(lzwMinCodeSize);
2390
+
2391
+ out.writeByte(lzwMinCodeSize);
2392
+
2393
+ var offset = 0;
2394
+
2395
+ while (raster.length - offset > 255) {
2396
+ out.writeByte(255);
2397
+ out.writeBytes(raster, offset, 255);
2398
+ offset += 255;
2399
+ }
2400
+
2401
+ out.writeByte(raster.length - offset);
2402
+ out.writeBytes(raster, offset, raster.length - offset);
2403
+ out.writeByte(0x00);
2404
+
2405
+ //---------------------------------
2406
+ // GIF Terminator
2407
+ out.writeString(';');
2408
+ };
2409
+
2410
+ var bitOutputStream = function(out) {
2411
+
2412
+ var _out = out;
2413
+ var _bitLength = 0;
2414
+ var _bitBuffer = 0;
2415
+
2416
+ var _this = {};
2417
+
2418
+ _this.write = function(data, length) {
2419
+
2420
+ if ( (data >>> length) != 0) {
2421
+ throw 'length over';
2422
+ }
2423
+
2424
+ while (_bitLength + length >= 8) {
2425
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
2426
+ length -= (8 - _bitLength);
2427
+ data >>>= (8 - _bitLength);
2428
+ _bitBuffer = 0;
2429
+ _bitLength = 0;
2430
+ }
2431
+
2432
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
2433
+ _bitLength = _bitLength + length;
2434
+ };
2435
+
2436
+ _this.flush = function() {
2437
+ if (_bitLength > 0) {
2438
+ _out.writeByte(_bitBuffer);
2439
+ }
2440
+ };
2441
+
2442
+ return _this;
2443
+ };
2444
+
2445
+ var getLZWRaster = function(lzwMinCodeSize) {
2446
+
2447
+ var clearCode = 1 << lzwMinCodeSize;
2448
+ var endCode = (1 << lzwMinCodeSize) + 1;
2449
+ var bitLength = lzwMinCodeSize + 1;
2450
+
2451
+ // Setup LZWTable
2452
+ var table = lzwTable();
2453
+
2454
+ for (var i = 0; i < clearCode; i += 1) {
2455
+ table.add(String.fromCharCode(i) );
2456
+ }
2457
+ table.add(String.fromCharCode(clearCode) );
2458
+ table.add(String.fromCharCode(endCode) );
2459
+
2460
+ var byteOut = byteArrayOutputStream();
2461
+ var bitOut = bitOutputStream(byteOut);
2462
+
2463
+ // clear code
2464
+ bitOut.write(clearCode, bitLength);
2465
+
2466
+ var dataIndex = 0;
2467
+
2468
+ var s = String.fromCharCode(_data[dataIndex]);
2469
+ dataIndex += 1;
2470
+
2471
+ while (dataIndex < _data.length) {
2472
+
2473
+ var c = String.fromCharCode(_data[dataIndex]);
2474
+ dataIndex += 1;
2475
+
2476
+ if (table.contains(s + c) ) {
2477
+
2478
+ s = s + c;
2479
+
2480
+ } else {
2481
+
2482
+ bitOut.write(table.indexOf(s), bitLength);
2483
+
2484
+ if (table.size() < 0xfff) {
2485
+
2486
+ if (table.size() == (1 << bitLength) ) {
2487
+ bitLength += 1;
2488
+ }
2489
+
2490
+ table.add(s + c);
2491
+ }
2492
+
2493
+ s = c;
2494
+ }
2495
+ }
2496
+
2497
+ bitOut.write(table.indexOf(s), bitLength);
2498
+
2499
+ // end code
2500
+ bitOut.write(endCode, bitLength);
2501
+
2502
+ bitOut.flush();
2503
+
2504
+ return byteOut.toByteArray();
2505
+ };
2506
+
2507
+ var lzwTable = function() {
2508
+
2509
+ var _map = {};
2510
+ var _size = 0;
2511
+
2512
+ var _this = {};
2513
+
2514
+ _this.add = function(key) {
2515
+ if (_this.contains(key) ) {
2516
+ throw 'dup key:' + key;
2517
+ }
2518
+ _map[key] = _size;
2519
+ _size += 1;
2520
+ };
2521
+
2522
+ _this.size = function() {
2523
+ return _size;
2524
+ };
2525
+
2526
+ _this.indexOf = function(key) {
2527
+ return _map[key];
2528
+ };
2529
+
2530
+ _this.contains = function(key) {
2531
+ return typeof _map[key] != 'undefined';
2532
+ };
2533
+
2534
+ return _this;
2535
+ };
2536
+
2537
+ return _this;
2538
+ };
2539
+
2540
+ var createDataURL = function(width, height, getPixel) {
2541
+ var gif = gifImage(width, height);
2542
+ for (var y = 0; y < height; y += 1) {
2543
+ for (var x = 0; x < width; x += 1) {
2544
+ gif.setPixel(x, y, getPixel(x, y) );
2545
+ }
2546
+ }
2547
+
2548
+ var b = byteArrayOutputStream();
2549
+ gif.write(b);
2550
+
2551
+ var base64 = base64EncodeOutputStream();
2552
+ var bytes = b.toByteArray();
2553
+ for (var i = 0; i < bytes.length; i += 1) {
2554
+ base64.writeByte(bytes[i]);
2555
+ }
2556
+ base64.flush();
2557
+
2558
+ return 'data:image/gif;base64,' + base64;
2559
+ };
2560
+
2561
+ //---------------------------------------------------------------------
2562
+ // returns qrcode function.
2563
+
2564
+ return qrcode;
2565
+ }();
2566
+
2567
+ // multibyte support
2568
+ !function() {
2569
+
2570
+ qrcode.stringToBytesFuncs['UTF-8'] = function(s) {
2571
+ // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
2572
+ function toUTF8Array(str) {
2573
+ var utf8 = [];
2574
+ for (var i=0; i < str.length; i++) {
2575
+ var charcode = str.charCodeAt(i);
2576
+ if (charcode < 0x80) utf8.push(charcode);
2577
+ else if (charcode < 0x800) {
2578
+ utf8.push(0xc0 | (charcode >> 6),
2579
+ 0x80 | (charcode & 0x3f));
2580
+ }
2581
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
2582
+ utf8.push(0xe0 | (charcode >> 12),
2583
+ 0x80 | ((charcode>>6) & 0x3f),
2584
+ 0x80 | (charcode & 0x3f));
2585
+ }
2586
+ // surrogate pair
2587
+ else {
2588
+ i++;
2589
+ // UTF-16 encodes 0x10000-0x10FFFF by
2590
+ // subtracting 0x10000 and splitting the
2591
+ // 20 bits of 0x0-0xFFFFF into two halves
2592
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
2593
+ | (str.charCodeAt(i) & 0x3ff));
2594
+ utf8.push(0xf0 | (charcode >>18),
2595
+ 0x80 | ((charcode>>12) & 0x3f),
2596
+ 0x80 | ((charcode>>6) & 0x3f),
2597
+ 0x80 | (charcode & 0x3f));
2598
+ }
2599
+ }
2600
+ return utf8;
2601
+ }
2602
+ return toUTF8Array(s);
2603
+ };
2604
+
2605
+ }();
2606
+
2607
+ (function (factory) {
2608
+ {
2609
+ module.exports = factory();
2610
+ }
2611
+ }(function () {
2612
+ return qrcode;
2613
+ }));
2614
+ } (qrcode$1));
2615
+
2616
+ var qrcodeExports = qrcode$1.exports;
2617
+ var qrcode = /*@__PURE__*/getDefaultExportFromCjs(qrcodeExports);
2618
+
2619
+ class QrModal {
2620
+ constructor() {
2621
+ this.dialog = null;
2622
+ this.timerInterval = null;
2623
+ }
2624
+ show(sessionId, qrData, expiresIn, // in seconds
2625
+ onRefresh, onCancel) {
2626
+ this.close();
2627
+ // 1. Create dialog element
2628
+ this.dialog = document.createElement('dialog');
2629
+ this.dialog.className = 'kiauth-dialog';
2630
+ this.dialog.setAttribute('closedby', 'any');
2631
+ this.dialog.setAttribute('aria-labelledby', 'kiauth-dialog-title');
2632
+ // 2. Generate QR code image source using qrcode-generator
2633
+ let qrImgHtml = '';
2634
+ try {
2635
+ const qr = qrcode(0, 'M');
2636
+ qr.addData(qrData);
2637
+ qr.make();
2638
+ qrImgHtml = qr.createImgTag(5, 0); // cellSize=5, margin=0
2639
+ }
2640
+ catch (e) {
2641
+ qrImgHtml = '<div style="color:red;padding:20px;">Failed to render QR code</div>';
2642
+ }
2643
+ // 3. Setup deep link for mobile devices
2644
+ const deepLink = `https://www.kiauth.com/auth?sid=${sessionId}`;
2645
+ this.dialog.innerHTML = `
2646
+ <div class="kiauth-modal-content">
2647
+ <div class="kiauth-header">
2648
+ <h2 id="kiauth-dialog-title" class="kiauth-title">Login with <span>Kiauth</span></h2>
2649
+ <button class="kiauth-close-btn" type="button" aria-label="Close dialog">&times;</button>
2650
+ </div>
2651
+
2652
+ <div class="kiauth-qr-container">
2653
+ <div class="kiauth-qr-img-wrapper">${qrImgHtml}</div>
2654
+ <div class="kiauth-qr-expired">
2655
+ <span style="font-weight: 600; font-size: 16px; margin-bottom: 6px; color:#ff4f4f;">Session Expired</span>
2656
+ <span style="font-size: 12px; opacity: 0.8; margin-bottom: 12px; line-height: 1.4;">For security, QR codes expire.</span>
2657
+ <button class="kiauth-btn-refresh" type="button">Refresh Code</button>
2658
+ </div>
2659
+ </div>
2660
+
2661
+ <div class="kiauth-instructions">
2662
+ Scan this QR code with the <strong>Kiauth mobile app</strong> to log in securely.
2663
+ </div>
2664
+
2665
+ <div class="kiauth-timer">
2666
+ Code expires in <span class="kiauth-countdown-val">05:00</span>
2667
+ </div>
2668
+
2669
+ <div class="kiauth-divider"></div>
2670
+
2671
+ <div class="kiauth-mobile-direct">
2672
+ <a href="${deepLink}" class="kiauth-btn-direct">Open on mobile instead</a>
2673
+ </div>
2674
+ </div>
2675
+ `;
2676
+ // Apply classes to the generated img element
2677
+ const imgEl = this.dialog.querySelector('img');
2678
+ if (imgEl) {
2679
+ imgEl.className = 'kiauth-qr-img';
2680
+ imgEl.alt = 'Kiauth QR Code';
2681
+ }
2682
+ document.body.appendChild(this.dialog);
2683
+ this.dialog.showModal();
2684
+ // 4. Event Listener bindings
2685
+ const closeBtn = this.dialog.querySelector('.kiauth-close-btn');
2686
+ closeBtn?.addEventListener('click', () => {
2687
+ this.close();
2688
+ onCancel();
2689
+ });
2690
+ const refreshBtn = this.dialog.querySelector('.kiauth-btn-refresh');
2691
+ refreshBtn?.addEventListener('click', () => {
2692
+ this.close();
2693
+ onRefresh();
2694
+ });
2695
+ this.dialog.addEventListener('cancel', () => {
2696
+ this.close();
2697
+ onCancel();
2698
+ });
2699
+ // 5. Light-dismiss fallback for unsupported browsers
2700
+ if (!('closedBy' in HTMLDialogElement.prototype)) {
2701
+ this.dialog.addEventListener('click', (event) => {
2702
+ if (!this.dialog || event.target !== this.dialog)
2703
+ return;
2704
+ const rect = this.dialog.getBoundingClientRect();
2705
+ const isDialogContent = (rect.top <= event.clientY &&
2706
+ event.clientY <= rect.top + rect.height &&
2707
+ rect.left <= event.clientX &&
2708
+ event.clientX <= rect.left + rect.width);
2709
+ if (!isDialogContent) {
2710
+ this.close();
2711
+ onCancel();
2712
+ }
2713
+ });
2714
+ }
2715
+ // 6. Countdown Timer Logic
2716
+ let secondsLeft = expiresIn;
2717
+ const timerValEl = this.dialog.querySelector('.kiauth-countdown-val');
2718
+ const expiredOverlayEl = this.dialog.querySelector('.kiauth-qr-expired');
2719
+ const updateTimer = () => {
2720
+ if (secondsLeft <= 0) {
2721
+ clearInterval(this.timerInterval);
2722
+ if (timerValEl)
2723
+ timerValEl.textContent = 'Expired';
2724
+ if (expiredOverlayEl)
2725
+ expiredOverlayEl.classList.add('active');
2726
+ return;
2727
+ }
2728
+ const mins = Math.floor(secondsLeft / 60);
2729
+ const secs = secondsLeft % 60;
2730
+ if (timerValEl) {
2731
+ timerValEl.textContent = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
2732
+ }
2733
+ secondsLeft--;
2734
+ };
2735
+ updateTimer();
2736
+ this.timerInterval = setInterval(updateTimer, 1000);
2737
+ }
2738
+ close() {
2739
+ if (this.timerInterval) {
2740
+ clearInterval(this.timerInterval);
2741
+ this.timerInterval = null;
2742
+ }
2743
+ if (this.dialog) {
2744
+ try {
2745
+ if (this.dialog.hasAttribute('open')) {
2746
+ this.dialog.close();
2747
+ }
2748
+ }
2749
+ catch (e) {
2750
+ // ignore errors if already closed
2751
+ }
2752
+ this.dialog.remove();
2753
+ this.dialog = null;
2754
+ }
2755
+ }
2756
+ }
2757
+
2758
+ /** Escapes user-supplied text before interpolating into innerHTML (SDK-02 / XSS). */
2759
+ function escapeHtml(value) {
2760
+ return String(value)
2761
+ .replace(/&/g, '&amp;')
2762
+ .replace(/</g, '&lt;')
2763
+ .replace(/>/g, '&gt;')
2764
+ .replace(/"/g, '&quot;')
2765
+ .replace(/'/g, '&#39;');
2766
+ }
2767
+ class OtpFallback {
2768
+ constructor(container, callbacks, apiBaseUrl) {
2769
+ this.callbacks = callbacks;
2770
+ this.apiBaseUrl = apiBaseUrl;
2771
+ this.container = container;
2772
+ }
2773
+ show() {
2774
+ this.renderPhoneForm();
2775
+ }
2776
+ renderPhoneForm() {
2777
+ this.container.innerHTML = `
2778
+ <div class="kiauth-fallback-container">
2779
+ <div class="kiauth-header">
2780
+ <h2 class="kiauth-title">OTP Fallback</h2>
2781
+ <button class="kiauth-close-btn" type="button" aria-label="Close dialog">&times;</button>
2782
+ </div>
2783
+
2784
+ <p style="font-size: 14px; opacity: 0.8; margin-bottom: 20px; text-align: center; line-height: 1.4;">
2785
+ Kiauth QR login is offline. Log in with your verified phone number instead.
2786
+ </p>
2787
+
2788
+ <div class="kiauth-input-group">
2789
+ <label class="kiauth-input-label">Phone Number</label>
2790
+ <input type="tel" class="kiauth-input kiauth-phone-input" placeholder="e.g. +91 99999 99999" value="+91 ">
2791
+ </div>
2792
+
2793
+ <div class="kiauth-error-message" style="color: #ff4f4f; font-size: 13px; margin-bottom: 12px; display: none; text-align: center;"></div>
2794
+
2795
+ <button class="kiauth-btn-direct kiauth-btn-send-otp" type="button" style="width: 100%;">
2796
+ Send OTP
2797
+ </button>
2798
+ </div>
2799
+ `;
2800
+ const closeBtn = this.container.querySelector('.kiauth-close-btn');
2801
+ closeBtn?.addEventListener('click', () => {
2802
+ this.callbacks.onCancel?.();
2803
+ });
2804
+ const phoneInput = this.container.querySelector('.kiauth-phone-input');
2805
+ const sendBtn = this.container.querySelector('.kiauth-btn-send-otp');
2806
+ const errorEl = this.container.querySelector('.kiauth-error-message');
2807
+ // Focus phone input field
2808
+ setTimeout(() => phoneInput.focus(), 100);
2809
+ sendBtn.addEventListener('click', async () => {
2810
+ const phone = phoneInput.value.trim();
2811
+ if (!phone || phone === '+91' || phone.length < 8) {
2812
+ errorEl.textContent = 'Please enter a valid phone number.';
2813
+ errorEl.style.display = 'block';
2814
+ return;
2815
+ }
2816
+ sendBtn.disabled = true;
2817
+ sendBtn.innerHTML = '<span class="kiauth-spinner"></span>';
2818
+ errorEl.style.display = 'none';
2819
+ try {
2820
+ let transactionId = '';
2821
+ if (this.callbacks.onOtpSend) {
2822
+ const res = await this.callbacks.onOtpSend(phone);
2823
+ transactionId = typeof res === 'string' ? res : res?.transactionId || '';
2824
+ }
2825
+ else {
2826
+ // Call backend identity service
2827
+ const res = await fetch(`${this.apiBaseUrl}/identity/register/phone`, {
2828
+ method: 'POST',
2829
+ headers: {
2830
+ 'Content-Type': 'application/json',
2831
+ 'Accept': 'application/json'
2832
+ },
2833
+ body: JSON.stringify({ phoneNumber: phone })
2834
+ });
2835
+ const data = await res.json();
2836
+ if (!res.ok) {
2837
+ throw new Error(data.message || 'Failed to send OTP.');
2838
+ }
2839
+ transactionId = data.userId;
2840
+ }
2841
+ this.renderOtpForm(phone, transactionId);
2842
+ }
2843
+ catch (err) {
2844
+ sendBtn.disabled = false;
2845
+ sendBtn.textContent = 'Send OTP';
2846
+ errorEl.textContent = err.message || 'Error sending OTP.';
2847
+ errorEl.style.display = 'block';
2848
+ }
2849
+ });
2850
+ }
2851
+ renderOtpForm(phone, transactionId) {
2852
+ this.container.innerHTML = `
2853
+ <div class="kiauth-fallback-container">
2854
+ <div class="kiauth-header">
2855
+ <h2 class="kiauth-title">Enter OTP</h2>
2856
+ <button class="kiauth-close-btn" type="button" aria-label="Close dialog">&times;</button>
2857
+ </div>
2858
+
2859
+ <p style="font-size: 14px; opacity: 0.8; margin-bottom: 20px; text-align: center;">
2860
+ An OTP has been sent to <strong>${escapeHtml(phone)}</strong>.
2861
+ </p>
2862
+
2863
+ <div class="kiauth-otp-inputs">
2864
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="0">
2865
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="1" disabled>
2866
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="2" disabled>
2867
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="3" disabled>
2868
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="4" disabled>
2869
+ <input type="text" pattern="[0-9]*" inputmode="numeric" maxlength="1" class="kiauth-otp-box" data-index="5" disabled>
2870
+ </div>
2871
+
2872
+ <div class="kiauth-error-message" style="color: #ff4f4f; font-size: 13px; margin-bottom: 12px; display: none; text-align: center;"></div>
2873
+
2874
+ <button class="kiauth-btn-direct kiauth-btn-verify-otp" type="button" style="width: 100%;" disabled>
2875
+ Verify
2876
+ </button>
2877
+
2878
+ <button class="kiauth-btn-refresh" type="button" style="background: transparent; color: rgba(255, 255, 255, 0.5); font-size: 13px; width: 100%; margin-top: 12px;">
2879
+ ← Back to Phone
2880
+ </button>
2881
+ </div>
2882
+ `;
2883
+ const closeBtn = this.container.querySelector('.kiauth-close-btn');
2884
+ closeBtn?.addEventListener('click', () => {
2885
+ this.callbacks.onCancel?.();
2886
+ });
2887
+ const backBtn = this.container.querySelector('.kiauth-btn-refresh');
2888
+ backBtn?.addEventListener('click', () => {
2889
+ this.renderPhoneForm();
2890
+ });
2891
+ const otpBoxes = this.container.querySelectorAll('.kiauth-otp-box');
2892
+ const verifyBtn = this.container.querySelector('.kiauth-btn-verify-otp');
2893
+ const errorEl = this.container.querySelector('.kiauth-error-message');
2894
+ // Auto-focus first digit
2895
+ setTimeout(() => otpBoxes[0].focus(), 100);
2896
+ const getOtpValue = () => Array.from(otpBoxes).map(b => b.value).join('');
2897
+ const otpContainer = this.container.querySelector('.kiauth-otp-inputs');
2898
+ otpContainer?.addEventListener('paste', (e) => {
2899
+ e.preventDefault();
2900
+ const pastedData = e.clipboardData?.getData('text') || '';
2901
+ const digits = pastedData.replace(/\D/g, '').slice(0, 6);
2902
+ if (digits.length > 0) {
2903
+ otpBoxes.forEach((box, i) => {
2904
+ if (digits[i]) {
2905
+ box.value = digits[i];
2906
+ box.removeAttribute('disabled');
2907
+ if (i < 5) {
2908
+ otpBoxes[i + 1].removeAttribute('disabled');
2909
+ }
2910
+ }
2911
+ });
2912
+ const focusIndex = Math.min(digits.length, 5);
2913
+ otpBoxes[focusIndex].focus();
2914
+ const otp = getOtpValue();
2915
+ if (otp.length === 6) {
2916
+ verifyBtn.removeAttribute('disabled');
2917
+ verifyBtn.click();
2918
+ }
2919
+ }
2920
+ });
2921
+ otpBoxes.forEach((box, idx) => {
2922
+ box.addEventListener('input', () => {
2923
+ const val = box.value;
2924
+ if (!/^[0-9]$/.test(val)) {
2925
+ box.value = '';
2926
+ return;
2927
+ }
2928
+ if (idx < 5) {
2929
+ const nextBox = otpBoxes[idx + 1];
2930
+ nextBox.removeAttribute('disabled');
2931
+ nextBox.focus();
2932
+ }
2933
+ const otp = getOtpValue();
2934
+ if (otp.length === 6) {
2935
+ verifyBtn.removeAttribute('disabled');
2936
+ }
2937
+ });
2938
+ box.addEventListener('keydown', (e) => {
2939
+ if (e.key === 'Backspace' && box.value === '') {
2940
+ if (idx > 0) {
2941
+ const prevBox = otpBoxes[idx - 1];
2942
+ prevBox.focus();
2943
+ prevBox.value = '';
2944
+ verifyBtn.setAttribute('disabled', 'true');
2945
+ }
2946
+ }
2947
+ });
2948
+ });
2949
+ verifyBtn.addEventListener('click', async () => {
2950
+ const otp = getOtpValue();
2951
+ if (otp.length !== 6)
2952
+ return;
2953
+ verifyBtn.disabled = true;
2954
+ verifyBtn.innerHTML = '<span class="kiauth-spinner"></span>';
2955
+ errorEl.style.display = 'none';
2956
+ try {
2957
+ let user = null;
2958
+ if (this.callbacks.onOtpVerify) {
2959
+ user = await this.callbacks.onOtpVerify(phone, otp, transactionId);
2960
+ }
2961
+ else {
2962
+ // Call backend verify OTP
2963
+ const res = await fetch(`${this.apiBaseUrl}/identity/register/verify-otp`, {
2964
+ method: 'POST',
2965
+ headers: {
2966
+ 'Content-Type': 'application/json',
2967
+ 'Accept': 'application/json'
2968
+ },
2969
+ body: JSON.stringify({ userId: transactionId, otp })
2970
+ });
2971
+ const data = await res.json();
2972
+ if (!res.ok) {
2973
+ throw new Error(data.message || 'OTP verification failed.');
2974
+ }
2975
+ // Build a mock user object representing the verified identity session
2976
+ user = {
2977
+ name: 'OTP Verified User',
2978
+ email: null,
2979
+ kiauth_verified: true,
2980
+ userToken: `fallback_token_${transactionId}`,
2981
+ verificationExpiry: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
2982
+ };
2983
+ }
2984
+ this.callbacks.onSuccess(user);
2985
+ }
2986
+ catch (err) {
2987
+ verifyBtn.disabled = false;
2988
+ verifyBtn.textContent = 'Verify';
2989
+ errorEl.textContent = err.message || 'Invalid OTP. Please try again.';
2990
+ errorEl.style.display = 'block';
2991
+ }
2992
+ });
2993
+ }
2994
+ }
2995
+
2996
+ class KiauthButton {
2997
+ render(selector, options) {
2998
+ const container = document.querySelector(selector);
2999
+ if (!container) {
3000
+ console.error(`KiauthSDK: Element with selector "${selector}" not found.`);
3001
+ return;
3002
+ }
3003
+ const button = document.createElement('button');
3004
+ button.type = 'button';
3005
+ const theme = options.theme || 'purple';
3006
+ const size = options.size || 'medium';
3007
+ button.className = `kiauth-login-button kiauth-btn-${theme} kiauth-btn-${size}`;
3008
+ // White-label overrides (applied as inline styles so they win over the preset
3009
+ // theme classes; omitting them keeps the default themed look = backward compatible).
3010
+ if (options.color) {
3011
+ button.style.background = options.color;
3012
+ button.style.borderColor = options.color;
3013
+ }
3014
+ if (options.textColor)
3015
+ button.style.color = options.textColor;
3016
+ if (options.fullWidth)
3017
+ button.style.width = '100%';
3018
+ if (options.shape) {
3019
+ const radius = options.shape === 'pill' ? '999px' : options.shape === 'square' ? '4px' : '10px';
3020
+ button.style.borderRadius = radius;
3021
+ }
3022
+ const text = options.text || 'Login with Kiauth';
3023
+ const showLogo = options.logo !== false; // default true
3024
+ // Premium logo icon (lock / identity shield). Suppressed when logo:false.
3025
+ const logoSvg = showLogo
3026
+ ? `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block; vertical-align:middle;">
3027
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
3028
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
3029
+ </svg>`
3030
+ : '';
3031
+ button.innerHTML = `${logoSvg}<span style="${showLogo ? 'margin-left: 8px;' : ''}">${text}</span>`;
3032
+ button.addEventListener('click', (e) => {
3033
+ e.preventDefault();
3034
+ options.onClick();
3035
+ });
3036
+ container.innerHTML = '';
3037
+ container.appendChild(button);
3038
+ }
3039
+ }
3040
+
3041
+ const CSS_STYLES = `
3042
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
3043
+
3044
+ :root {
3045
+ --kiauth-purple: #534ab7;
3046
+ --kiauth-purple-hover: #423a9c;
3047
+ --kiauth-bg-dark: #0f0c1b;
3048
+ --kiauth-border-dark: rgba(255, 255, 255, 0.1);
3049
+ --kiauth-glass: rgba(15, 12, 27, 0.7);
3050
+ --kiauth-glow: rgba(83, 74, 183, 0.3);
3051
+ }
3052
+
3053
+ .kiauth-dialog {
3054
+ border: none;
3055
+ border-radius: 20px;
3056
+ padding: 0;
3057
+ background: var(--kiauth-bg-dark);
3058
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--kiauth-border-dark), 0 0 30px var(--kiauth-glow);
3059
+ max-width: 420px;
3060
+ width: 90%;
3061
+ color: #ffffff;
3062
+ font-family: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3063
+ overflow: hidden;
3064
+ outline: none;
3065
+ opacity: 0;
3066
+ transform: scale(0.9);
3067
+ transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
3068
+ }
3069
+
3070
+ .kiauth-dialog[open] {
3071
+ opacity: 1;
3072
+ transform: scale(1);
3073
+ }
3074
+
3075
+ .kiauth-dialog::backdrop {
3076
+ background: rgba(10, 8, 20, 0.75);
3077
+ backdrop-filter: blur(12px);
3078
+ opacity: 0;
3079
+ transition: opacity 0.3s ease;
3080
+ }
3081
+
3082
+ .kiauth-dialog[open]::backdrop {
3083
+ opacity: 1;
3084
+ }
3085
+
3086
+ .kiauth-modal-content {
3087
+ padding: 32px 24px;
3088
+ display: flex;
3089
+ flex-direction: column;
3090
+ align-items: center;
3091
+ text-align: center;
3092
+ }
3093
+
3094
+ .kiauth-header {
3095
+ display: flex;
3096
+ justify-content: space-between;
3097
+ align-items: center;
3098
+ width: 100%;
3099
+ margin-bottom: 24px;
3100
+ }
3101
+
3102
+ .kiauth-title {
3103
+ font-size: 22px;
3104
+ font-weight: 600;
3105
+ color: #ffffff;
3106
+ margin: 0;
3107
+ letter-spacing: -0.5px;
3108
+ display: flex;
3109
+ align-items: center;
3110
+ gap: 8px;
3111
+ }
3112
+
3113
+ .kiauth-title span {
3114
+ color: var(--kiauth-purple);
3115
+ font-weight: 700;
3116
+ }
3117
+
3118
+ .kiauth-close-btn {
3119
+ background: transparent;
3120
+ border: none;
3121
+ color: rgba(255, 255, 255, 0.5);
3122
+ font-size: 24px;
3123
+ cursor: pointer;
3124
+ line-height: 1;
3125
+ padding: 4px;
3126
+ transition: color 0.2s;
3127
+ }
3128
+
3129
+ .kiauth-close-btn:hover {
3130
+ color: #ffffff;
3131
+ }
3132
+
3133
+ .kiauth-qr-container {
3134
+ background: #ffffff;
3135
+ padding: 16px;
3136
+ border-radius: 16px;
3137
+ margin-bottom: 20px;
3138
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
3139
+ position: relative;
3140
+ display: flex;
3141
+ justify-content: center;
3142
+ align-items: center;
3143
+ width: 220px;
3144
+ height: 220px;
3145
+ }
3146
+
3147
+ .kiauth-qr-img {
3148
+ width: 200px;
3149
+ height: 200px;
3150
+ display: block;
3151
+ }
3152
+
3153
+ .kiauth-qr-expired {
3154
+ position: absolute;
3155
+ top: 0;
3156
+ left: 0;
3157
+ right: 0;
3158
+ bottom: 0;
3159
+ background: rgba(15, 12, 27, 0.95);
3160
+ backdrop-filter: blur(4px);
3161
+ border-radius: 16px;
3162
+ display: flex;
3163
+ flex-direction: column;
3164
+ justify-content: center;
3165
+ align-items: center;
3166
+ color: #ffffff;
3167
+ opacity: 0;
3168
+ pointer-events: none;
3169
+ transition: opacity 0.3s;
3170
+ padding: 16px;
3171
+ }
3172
+
3173
+ .kiauth-qr-expired.active {
3174
+ opacity: 1;
3175
+ pointer-events: auto;
3176
+ }
3177
+
3178
+ .kiauth-btn-refresh {
3179
+ background: var(--kiauth-purple);
3180
+ color: #ffffff;
3181
+ border: none;
3182
+ padding: 10px 20px;
3183
+ border-radius: 8px;
3184
+ font-weight: 500;
3185
+ cursor: pointer;
3186
+ margin-top: 12px;
3187
+ font-family: inherit;
3188
+ transition: background 0.2s;
3189
+ }
3190
+
3191
+ .kiauth-btn-refresh:hover {
3192
+ background: var(--kiauth-purple-hover);
3193
+ }
3194
+
3195
+ .kiauth-instructions {
3196
+ font-size: 14px;
3197
+ color: rgba(255, 255, 255, 0.7);
3198
+ margin-bottom: 24px;
3199
+ line-height: 1.5;
3200
+ }
3201
+
3202
+ .kiauth-timer {
3203
+ font-size: 13px;
3204
+ color: rgba(255, 255, 255, 0.4);
3205
+ margin-bottom: 20px;
3206
+ }
3207
+
3208
+ .kiauth-timer span {
3209
+ color: var(--kiauth-purple);
3210
+ font-weight: 600;
3211
+ }
3212
+
3213
+ .kiauth-divider {
3214
+ width: 100%;
3215
+ height: 1px;
3216
+ background: var(--kiauth-border-dark);
3217
+ margin: 20px 0;
3218
+ }
3219
+
3220
+ .kiauth-mobile-direct {
3221
+ display: flex;
3222
+ flex-direction: column;
3223
+ gap: 12px;
3224
+ width: 100%;
3225
+ }
3226
+
3227
+ .kiauth-btn-direct {
3228
+ background: var(--kiauth-purple);
3229
+ color: #ffffff;
3230
+ border: none;
3231
+ padding: 14px;
3232
+ border-radius: 12px;
3233
+ font-size: 16px;
3234
+ font-weight: 600;
3235
+ cursor: pointer;
3236
+ font-family: inherit;
3237
+ text-decoration: none;
3238
+ display: inline-block;
3239
+ transition: background 0.2s, transform 0.1s;
3240
+ box-shadow: 0 4px 12px var(--kiauth-glow);
3241
+ }
3242
+
3243
+ .kiauth-btn-direct:hover {
3244
+ background: var(--kiauth-purple-hover);
3245
+ }
3246
+
3247
+ .kiauth-btn-direct:active {
3248
+ transform: scale(0.98);
3249
+ }
3250
+
3251
+ /* Button Renderers */
3252
+ .kiauth-login-button {
3253
+ display: inline-flex;
3254
+ align-items: center;
3255
+ justify-content: center;
3256
+ gap: 10px;
3257
+ font-family: 'Outfit', sans-serif;
3258
+ font-weight: 600;
3259
+ border-radius: 10px;
3260
+ cursor: pointer;
3261
+ transition: all 0.2s ease;
3262
+ border: 1px solid transparent;
3263
+ user-select: none;
3264
+ }
3265
+
3266
+ .kiauth-btn-purple {
3267
+ background-color: var(--kiauth-purple);
3268
+ color: #ffffff;
3269
+ }
3270
+ .kiauth-btn-purple:hover {
3271
+ background-color: var(--kiauth-purple-hover);
3272
+ }
3273
+
3274
+ .kiauth-btn-white {
3275
+ background-color: #ffffff;
3276
+ color: #0f0c1b;
3277
+ border-color: rgba(0, 0, 0, 0.1);
3278
+ }
3279
+ .kiauth-btn-white:hover {
3280
+ background-color: #f5f5f7;
3281
+ }
3282
+
3283
+ .kiauth-btn-dark {
3284
+ background-color: #0f0c1b;
3285
+ color: #ffffff;
3286
+ border-color: var(--kiauth-border-dark);
3287
+ }
3288
+ .kiauth-btn-dark:hover {
3289
+ background-color: #1a1530;
3290
+ }
3291
+
3292
+ .kiauth-btn-small {
3293
+ padding: 8px 16px;
3294
+ font-size: 13px;
3295
+ }
3296
+ .kiauth-btn-medium {
3297
+ padding: 12px 24px;
3298
+ font-size: 15px;
3299
+ }
3300
+ .kiauth-btn-large {
3301
+ padding: 16px 32px;
3302
+ font-size: 17px;
3303
+ }
3304
+
3305
+ /* Fallback Form */
3306
+ .kiauth-fallback-container {
3307
+ width: 100%;
3308
+ display: flex;
3309
+ flex-direction: column;
3310
+ align-items: center;
3311
+ }
3312
+
3313
+ .kiauth-input-group {
3314
+ width: 100%;
3315
+ margin-bottom: 16px;
3316
+ text-align: left;
3317
+ }
3318
+
3319
+ .kiauth-input-label {
3320
+ display: block;
3321
+ font-size: 12px;
3322
+ font-weight: 500;
3323
+ color: rgba(255, 255, 255, 0.5);
3324
+ margin-bottom: 6px;
3325
+ text-transform: uppercase;
3326
+ letter-spacing: 0.5px;
3327
+ }
3328
+
3329
+ .kiauth-input {
3330
+ width: 100%;
3331
+ background: rgba(255, 255, 255, 0.05);
3332
+ border: 1px solid var(--kiauth-border-dark);
3333
+ padding: 12px;
3334
+ border-radius: 8px;
3335
+ color: #ffffff;
3336
+ font-family: inherit;
3337
+ font-size: 15px;
3338
+ box-sizing: border-box;
3339
+ transition: border-color 0.2s, box-shadow 0.2s;
3340
+ }
3341
+
3342
+ .kiauth-input:focus {
3343
+ outline: none;
3344
+ border-color: var(--kiauth-purple);
3345
+ box-shadow: 0 0 10px var(--kiauth-glow);
3346
+ }
3347
+
3348
+ .kiauth-otp-inputs {
3349
+ display: flex;
3350
+ justify-content: space-between;
3351
+ gap: 8px;
3352
+ margin: 16px 0;
3353
+ width: 100%;
3354
+ }
3355
+
3356
+ .kiauth-otp-box {
3357
+ width: 45px;
3358
+ height: 45px;
3359
+ background: rgba(255, 255, 255, 0.05);
3360
+ border: 1px solid var(--kiauth-border-dark);
3361
+ border-radius: 8px;
3362
+ text-align: center;
3363
+ color: #ffffff;
3364
+ font-size: 20px;
3365
+ font-weight: 600;
3366
+ transition: border-color 0.2s;
3367
+ }
3368
+
3369
+ .kiauth-otp-box:focus {
3370
+ outline: none;
3371
+ border-color: var(--kiauth-purple);
3372
+ box-shadow: 0 0 10px var(--kiauth-glow);
3373
+ }
3374
+
3375
+ .kiauth-spinner {
3376
+ border: 2px solid rgba(255, 255, 255, 0.1);
3377
+ width: 18px;
3378
+ height: 18px;
3379
+ border-radius: 50%;
3380
+ border-left-color: #ffffff;
3381
+ animation: spin 0.8s linear infinite;
3382
+ display: inline-block;
3383
+ }
3384
+
3385
+ @keyframes spin {
3386
+ 0% { transform: rotate(0deg); }
3387
+ 100% { transform: rotate(360deg); }
3388
+ }
3389
+ `;
3390
+ function injectStyles() {
3391
+ const id = 'kiauth-sdk-styles';
3392
+ if (document.getElementById(id))
3393
+ return;
3394
+ const style = document.createElement('style');
3395
+ style.id = id;
3396
+ style.textContent = CSS_STYLES;
3397
+ document.head.appendChild(style);
3398
+ }
3399
+
3400
+ /**
3401
+ * PKCE (RFC 7636) helpers for public/browser clients, so the SDK never needs a
3402
+ * confidential clientSecret in the browser. Uses the Web Crypto API.
3403
+ */
3404
+ function base64UrlEncode(bytes) {
3405
+ let str = '';
3406
+ for (let i = 0; i < bytes.length; i++) {
3407
+ str += String.fromCharCode(bytes[i]);
3408
+ }
3409
+ return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
3410
+ }
3411
+ /** Cryptographically-random code_verifier (43 chars, base64url of 32 bytes). */
3412
+ function generateCodeVerifier() {
3413
+ const arr = new Uint8Array(32);
3414
+ crypto.getRandomValues(arr);
3415
+ return base64UrlEncode(arr);
3416
+ }
3417
+ /** code_challenge = BASE64URL(SHA256(code_verifier)). */
3418
+ async function generateCodeChallenge(verifier) {
3419
+ const data = new TextEncoder().encode(verifier);
3420
+ const digest = await crypto.subtle.digest('SHA-256', data);
3421
+ return base64UrlEncode(new Uint8Array(digest));
3422
+ }
3423
+
3424
+ class KiauthSDK {
3425
+ constructor(config) {
3426
+ this.config = config;
3427
+ this.isFallbackMode = false;
3428
+ this.healthCheckIntervalId = null;
3429
+ this.pollIntervalId = null;
3430
+ this.completed = false;
3431
+ if (typeof window !== 'undefined' && config.clientSecret) {
3432
+ console.warn('[Kiauth] CRITICAL SECURITY WARNING: clientSecret is configured in the browser environment. This leaks your clientSecret to all visitors. Omit clientSecret and use the default PKCE flow in the browser.');
3433
+ }
3434
+ this.api = new KiauthApi(config);
3435
+ this.socket = new KiauthWebsocket(this.api.getBaseUrl());
3436
+ this.modal = new QrModal();
3437
+ this.buttonRenderer = new KiauthButton();
3438
+ // Inject the premium styles automatically into the head
3439
+ if (typeof document !== 'undefined') {
3440
+ injectStyles();
3441
+ }
3442
+ // Start background health checking to determine when to fallback silently
3443
+ this.startHealthCheck();
3444
+ }
3445
+ startHealthCheck() {
3446
+ const runCheck = async () => {
3447
+ const isHealthy = await this.api.healthCheck();
3448
+ this.isFallbackMode = !isHealthy;
3449
+ };
3450
+ runCheck();
3451
+ this.healthCheckIntervalId = setInterval(runCheck, 30000); // Check every 30 seconds
3452
+ }
3453
+ renderButton(selector, options) {
3454
+ this.buttonRenderer.render(selector, {
3455
+ text: options.text,
3456
+ theme: options.theme,
3457
+ size: options.size,
3458
+ color: options.color,
3459
+ textColor: options.textColor,
3460
+ shape: options.shape,
3461
+ fullWidth: options.fullWidth,
3462
+ logo: options.logo,
3463
+ onClick: () => {
3464
+ this.startLogin({
3465
+ onSuccess: options.onSuccess,
3466
+ onError: options.onError,
3467
+ onCancel: options.onCancel
3468
+ });
3469
+ }
3470
+ });
3471
+ }
3472
+ async startLogin(options) {
3473
+ // Don't trust a stale background health result (e.g. one slow/cold-start
3474
+ // check). Re-verify with a longer timeout before deciding to fall back, so a
3475
+ // backend that's just waking up still serves the real Kiauth flow.
3476
+ if (this.isFallbackMode) {
3477
+ const healthyNow = await this.api.healthCheck(15000);
3478
+ this.isFallbackMode = !healthyNow;
3479
+ if (!healthyNow) {
3480
+ this.triggerOtpFallback(options);
3481
+ return;
3482
+ }
3483
+ }
3484
+ try {
3485
+ this.completed = false;
3486
+ this.stopPolling();
3487
+ // 0. PKCE for public (browser) clients — the secure default. We only fall
3488
+ // back to a confidential clientSecret if one was explicitly configured
3489
+ // (discouraged in the browser).
3490
+ const usePkce = !this.config.clientSecret;
3491
+ let codeVerifier;
3492
+ let codeChallenge;
3493
+ if (usePkce) {
3494
+ codeVerifier = generateCodeVerifier();
3495
+ codeChallenge = await generateCodeChallenge(codeVerifier);
3496
+ }
3497
+ // 1. Initiate QR session on the backend
3498
+ const session = await this.api.initiateSession(this.config.clientId, this.config.scopes, this.config.redirectUri, codeChallenge);
3499
+ const isMobile = typeof navigator !== 'undefined' &&
3500
+ /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
3501
+ // 2. Open QR code modal
3502
+ this.modal.show(session.sessionId, session.qrData, session.expiresIn,
3503
+ // On Refresh click
3504
+ () => {
3505
+ this.modal.close();
3506
+ this.startLogin(options);
3507
+ },
3508
+ // On Cancel click
3509
+ () => {
3510
+ this.socket.disconnect();
3511
+ this.stopPolling();
3512
+ options.onCancel?.();
3513
+ });
3514
+ // 3. If mobile, attempt auto deep link redirect
3515
+ if (isMobile) {
3516
+ window.location.href = `https://www.kiauth.com/auth?sid=${session.sessionId}`;
3517
+ }
3518
+ // Shared completion: exchange the code and fire onSuccess exactly once,
3519
+ // whether approval arrived via the WebSocket or via HTTP polling.
3520
+ const finish = async (code) => {
3521
+ if (this.completed)
3522
+ return;
3523
+ this.completed = true;
3524
+ this.stopPolling();
3525
+ this.socket.disconnect();
3526
+ this.modal.close();
3527
+ try {
3528
+ if (!usePkce) {
3529
+ console.warn('[Kiauth] Using clientSecret in the browser is insecure. Omit clientSecret to use the default PKCE flow.');
3530
+ }
3531
+ const tokenResponse = await this.api.exchangeToken(code, this.config.clientId, usePkce ? { codeVerifier } : { clientSecret: this.config.clientSecret });
3532
+ options.onSuccess(tokenResponse.user);
3533
+ }
3534
+ catch (exchangeErr) {
3535
+ options.onError({
3536
+ code: exchangeErr.code || 'EXCHANGE_FAILED',
3537
+ message: exchangeErr.message || 'Failed to complete authentication exchange.'
3538
+ });
3539
+ }
3540
+ };
3541
+ // 4a. Real-time path: WebSocket auth_complete (fast when it connects).
3542
+ this.socket.connect(session.channelId, (authData) => finish(authData.code), () => {
3543
+ // Socket loss is non-fatal: the poller below is the reliable path,
3544
+ // so we do NOT drop to OTP here.
3545
+ });
3546
+ // 4b. Reliable path: poll session status until APPROVED. This works through
3547
+ // proxies / cold networks where the WS event may never arrive (it's the same
3548
+ // mechanism the dev portal uses successfully).
3549
+ this.startPolling(session.sessionId, session.expiresIn, finish);
3550
+ }
3551
+ catch (err) {
3552
+ this.modal.close();
3553
+ if (err.code === 'KIAUTH_OFFLINE' || this.isFallbackMode) {
3554
+ this.triggerOtpFallback(options);
3555
+ }
3556
+ else {
3557
+ options.onError({
3558
+ code: err.code || 'LOGIN_INITIATE_FAILED',
3559
+ message: err.message || 'Login initiation failed.'
3560
+ });
3561
+ }
3562
+ }
3563
+ }
3564
+ // Poll the QR session until it's APPROVED, then complete via the shared finish().
3565
+ startPolling(sessionId, expiresInSec, finish) {
3566
+ this.stopPolling();
3567
+ const deadline = Date.now() + (expiresInSec || 300) * 1000;
3568
+ let consecutiveErrors = 0;
3569
+ this.pollIntervalId = setInterval(async () => {
3570
+ if (this.completed || Date.now() > deadline) {
3571
+ this.stopPolling();
3572
+ return;
3573
+ }
3574
+ try {
3575
+ const { status, authCode } = await this.api.getSessionStatus(sessionId);
3576
+ consecutiveErrors = 0;
3577
+ if (status === 'APPROVED' && authCode) {
3578
+ finish(authCode);
3579
+ }
3580
+ else if (status === 'EXPIRED' || status === 'USED') {
3581
+ this.stopPolling();
3582
+ }
3583
+ }
3584
+ catch (err) {
3585
+ consecutiveErrors++;
3586
+ const isExpiredOrNotFound = err && (err.statusCode === 404 || err.statusCode === 410 || err.code === 'SESSION_EXPIRED');
3587
+ if (isExpiredOrNotFound || consecutiveErrors >= 5) {
3588
+ this.stopPolling();
3589
+ }
3590
+ }
3591
+ }, 3000);
3592
+ }
3593
+ stopPolling() {
3594
+ if (this.pollIntervalId) {
3595
+ clearInterval(this.pollIntervalId);
3596
+ this.pollIntervalId = null;
3597
+ }
3598
+ }
3599
+ triggerOtpFallback(options) {
3600
+ // Show empty dialog container to render OTP fallback view inside
3601
+ const dialog = document.createElement('dialog');
3602
+ dialog.className = 'kiauth-dialog';
3603
+ dialog.setAttribute('closedby', 'any');
3604
+ dialog.setAttribute('aria-labelledby', 'kiauth-dialog-title');
3605
+ document.body.appendChild(dialog);
3606
+ dialog.showModal();
3607
+ const cleanUp = () => {
3608
+ dialog.close();
3609
+ dialog.remove();
3610
+ };
3611
+ const fallback = new OtpFallback(dialog, {
3612
+ onSuccess: (user) => {
3613
+ cleanUp();
3614
+ options.onSuccess(user);
3615
+ },
3616
+ onError: (err) => {
3617
+ cleanUp();
3618
+ options.onError(err);
3619
+ },
3620
+ onCancel: () => {
3621
+ cleanUp();
3622
+ options.onCancel?.();
3623
+ }
3624
+ }, this.api.getBaseUrl());
3625
+ fallback.show();
3626
+ }
3627
+ async verifyToken(token) {
3628
+ return this.api.verifyToken(token);
3629
+ }
3630
+ async reportSession(options) {
3631
+ if (typeof window !== 'undefined') {
3632
+ throw new Error('reportSession cannot be called in the browser environment to prevent client-side exposure of clientSecret. Run this method on your backend server only.');
3633
+ }
3634
+ if (!this.config.clientSecret) {
3635
+ throw new Error('clientSecret is required to call reportSession.');
3636
+ }
3637
+ return this.api.reportSession(this.config.clientId, this.config.clientSecret, options);
3638
+ }
3639
+ async reportBatchSessions(kiauthUserToken, sessions) {
3640
+ if (typeof window !== 'undefined') {
3641
+ throw new Error('reportBatchSessions cannot be called in the browser environment to prevent client-side exposure of clientSecret. Run this method on your backend server only.');
3642
+ }
3643
+ if (!this.config.clientSecret) {
3644
+ throw new Error('clientSecret is required to call reportBatchSessions.');
3645
+ }
3646
+ return this.api.reportBatchSessions(this.config.clientId, this.config.clientSecret, kiauthUserToken, sessions);
3647
+ }
3648
+ // Email-keyed login reporting ("claim on first sighting"). Use when you don't have a
3649
+ // kiauthUserToken — e.g. the user signed in with their own email/password/Google and you
3650
+ // want it shown in their Kiauth app. Requires a VERIFIED business + clientSecret — call
3651
+ // this from your backend, never the browser.
3652
+ async reportSessionByEmail(options) {
3653
+ if (typeof window !== 'undefined') {
3654
+ throw new Error('reportSessionByEmail cannot be called in the browser environment to prevent client-side exposure of clientSecret. Run this method on your backend server only.');
3655
+ }
3656
+ if (!this.config.clientSecret) {
3657
+ throw new Error('clientSecret is required to call reportSessionByEmail (run this on your backend).');
3658
+ }
3659
+ return this.api.reportSessionByEmail(this.config.clientId, this.config.clientSecret, options);
3660
+ }
3661
+ // RP-initiated logout. Call this from your backend when the user logs out of
3662
+ // your site so the Kiauth app shows the session as revoked. Requires
3663
+ // clientSecret — never expose this in the browser.
3664
+ async logout(opts) {
3665
+ if (typeof window !== 'undefined') {
3666
+ throw new Error('logout cannot be called in the browser environment to prevent client-side exposure of clientSecret. Run this method on your backend server only.');
3667
+ }
3668
+ if (!this.config.clientSecret) {
3669
+ throw new Error('clientSecret is required to call logout (run this on your backend).');
3670
+ }
3671
+ return this.api.endSession(this.config.clientId, this.config.clientSecret, opts);
3672
+ }
3673
+ destroy() {
3674
+ if (this.healthCheckIntervalId) {
3675
+ clearInterval(this.healthCheckIntervalId);
3676
+ this.healthCheckIntervalId = null;
3677
+ }
3678
+ this.socket.disconnect();
3679
+ this.modal.close();
3680
+ }
3681
+ }
3682
+
3683
+ export { KiauthSDK };
3684
+ //# sourceMappingURL=kiauth-sdk.js.map