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