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