true&&(function polyfill() { const relList = document.createElement("link").relList; if (relList && relList.supports && relList.supports("modulepreload")) { return; } for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { processPreload(link); } new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type !== "childList") { continue; } for (const node of mutation.addedNodes) { if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node); } } }).observe(document, { childList: true, subtree: true }); function getFetchOpts(link) { const fetchOpts = {}; if (link.integrity) fetchOpts.integrity = link.integrity; if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include"; else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; else fetchOpts.credentials = "same-origin"; return fetchOpts; } function processPreload(link) { if (link.ep) return; link.ep = true; const fetchOpts = getFetchOpts(link); fetch(link.href, fetchOpts); } }()); const scriptRel = 'modulepreload';const assetsURL = function(dep) { return "/"+dep };const seen = {};const __vitePreload = function preload(baseModule, deps, importerUrl) { let promise = Promise.resolve(); if (true && deps && deps.length > 0) { document.getElementsByTagName("link"); const cspNonceMeta = document.querySelector( "meta[property=csp-nonce]" ); const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce"); promise = Promise.allSettled( deps.map((dep) => { dep = assetsURL(dep); if (dep in seen) return; seen[dep] = true; const isCss = dep.endsWith(".css"); const cssSelector = isCss ? '[rel="stylesheet"]' : ""; if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) { return; } const link = document.createElement("link"); link.rel = isCss ? "stylesheet" : scriptRel; if (!isCss) { link.as = "script"; } link.crossOrigin = ""; link.href = dep; if (cspNonce) { link.setAttribute("nonce", cspNonce); } document.head.appendChild(link); if (isCss) { return new Promise((res, rej) => { link.addEventListener("load", res); link.addEventListener( "error", () => rej(new Error(`Unable to preload CSS for ${dep}`)) ); }); } }) ); } function handlePreloadError(err) { const e = new Event("vite:preloadError", { cancelable: true }); e.payload = err; window.dispatchEvent(e); if (!e.defaultPrevented) { throw err; } } return promise.then((res) => { for (const item of res || []) { if (item.status !== "rejected") continue; handlePreloadError(item.reason); } return baseModule().catch(handlePreloadError); }); }; const modalHtml = "
\r\n
\r\n
Login
\r\n
\r\n
\r\n Attempting to pair device with address\r\n {{device1}}\r\n with device with address\r\n {{device2}}\r\n
\r\n
Awaiting pairing validation...
\r\n
\r\n
\r\n
\r\n"; const modalScript = "import Routing from '/src/services/routing.service.ts';\r\n\r\nconst router = await Routing.getInstance();\r\nexport async function confirmLogin() {\r\n router.confirmLogin();\r\n}\r\n\r\nexport async function closeLoginModal() {\r\n router.closeLoginModal();\r\n}\r\n\r\nwindow.confirmLogin = confirmLogin;\r\nwindow.closeLoginModal = closeLoginModal;\r\n"; const validationModalStyle = ".validation-modal {\r\n display: block; /* Show the modal for demo purposes */\r\n position: fixed;\r\n z-index: 1;\r\n left: 0;\r\n top: 0;\r\n width: 100%;\r\n height: 100%;\r\n overflow: auto;\r\n background-color: rgb(0, 0, 0);\r\n background-color: rgba(0, 0, 0, 0.4);\r\n padding-top: 60px;\r\n}\r\n.modal-content {\r\n background-color: #fefefe;\r\n margin: 5% auto;\r\n padding: 20px;\r\n border: 1px solid #888;\r\n width: 80%;\r\n height: fit-content;\r\n}\r\n.modal-title {\r\n font-size: 24px;\r\n font-weight: bold;\r\n margin-bottom: 20px;\r\n}\r\n.validation-box {\r\n margin-bottom: 15px;\r\n width: 100%;\r\n}\r\n.expansion-panel-header {\r\n background-color: #e0e0e0;\r\n padding: 10px;\r\n cursor: pointer;\r\n}\r\n.expansion-panel-body {\r\n display: none;\r\n background-color: #fafafa;\r\n padding: 10px;\r\n border-top: 1px solid #ddd;\r\n}\r\n.expansion-panel-body pre {\r\n background-color: #f6f8fa;\r\n padding: 10px;\r\n border-left: 4px solid #d1d5da;\r\n overflow-x: auto;\r\n}\r\n.diff {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 10px;\r\n}\r\n.diff-side {\r\n width: 48%;\r\n padding: 10px;\r\n}\r\n.diff-old {\r\n background-color: #fee;\r\n border: 1px solid #f00;\r\n}\r\n.diff-new {\r\n background-color: #e6ffe6;\r\n border: 1px solid #0f0;\r\n}\r\n.radio-buttons {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 10px;\r\n}\r\n"; let ws; let messageQueue = []; async function initWebsocket(url) { ws = new WebSocket(url); if (ws !== null) { ws.onopen = async (event) => { console.log("WebSocket connection established"); while (messageQueue.length > 0) { const message = messageQueue.shift(); if (message) { ws.send(message); } } }; ws.onmessage = (event) => { const msgData = event.data; (async () => { if (typeof msgData === "string") { try { const parsedMessage = JSON.parse(msgData); const services = await Services.getInstance(); switch (parsedMessage.flag) { case "Handshake": await services.handleHandshakeMsg(url, parsedMessage.content); break; case "NewTx": await services.parseNewTx(parsedMessage.content); break; case "Cipher": await services.parseCipher(parsedMessage.content); break; case "Commit": await services.handleCommitError(parsedMessage.content); break; } } catch (error) { console.error("Received an invalid message:", error); } } else { console.error("Received a non-string message"); } })(); }; ws.onerror = (event) => { console.error("WebSocket error:", event); }; ws.onclose = (event) => { console.log("WebSocket is closed now."); }; } } function sendMessage(flag, message) { if (ws.readyState === WebSocket.OPEN) { const networkMessage = { flag, content: message }; console.log("Sending message of type:", flag); ws.send(JSON.stringify(networkMessage)); } else { console.error("WebSocket is not open. ReadyState:", ws.readyState); messageQueue.push(message); } } function getCorrectDOM(componentTag) { const dom = document?.querySelector(componentTag)?.shadowRoot || document; return dom; } let subscriptions = []; function cleanSubscriptions() { console.log("🚀 ~ cleanSubscriptions ~ sub:", subscriptions); for (const sub of subscriptions) { const el = sub.element; const eventHandler = sub.eventHandler; if (el) { el.removeEventListener(sub.event, eventHandler); } } subscriptions = []; } function addSubscription(element, event, eventHandler) { if (!element) return; subscriptions.push({ element, event, eventHandler }); element.addEventListener(event, eventHandler); } // can-promise has a crash in some versions of react native that dont have // standard global objects // https://github.com/soldair/node-qrcode/issues/157 var canPromise$1 = function () { return typeof Promise === 'function' && Promise.prototype && Promise.prototype.then }; var qrcode = {}; var utils$3 = {}; let toSJISFunction; const CODEWORDS_COUNT = [ 0, // Not used 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 ]; /** * Returns the QR Code size for the specified version * * @param {Number} version QR Code version * @return {Number} size of QR code */ utils$3.getSymbolSize = function getSymbolSize (version) { if (!version) throw new Error('"version" cannot be null or undefined') if (version < 1 || version > 40) throw new Error('"version" should be in range from 1 to 40') return version * 4 + 17 }; /** * Returns the total number of codewords used to store data and EC information. * * @param {Number} version QR Code version * @return {Number} Data length in bits */ utils$3.getSymbolTotalCodewords = function getSymbolTotalCodewords (version) { return CODEWORDS_COUNT[version] }; /** * Encode data with Bose-Chaudhuri-Hocquenghem * * @param {Number} data Value to encode * @return {Number} Encoded value */ utils$3.getBCHDigit = function (data) { let digit = 0; while (data !== 0) { digit++; data >>>= 1; } return digit }; utils$3.setToSJISFunction = function setToSJISFunction (f) { if (typeof f !== 'function') { throw new Error('"toSJISFunc" is not a valid function.') } toSJISFunction = f; }; utils$3.isKanjiModeEnabled = function () { return typeof toSJISFunction !== 'undefined' }; utils$3.toSJIS = function toSJIS (kanji) { return toSJISFunction(kanji) }; var errorCorrectionLevel = {}; (function (exports) { exports.L = { bit: 1 }; exports.M = { bit: 0 }; exports.Q = { bit: 3 }; exports.H = { bit: 2 }; function fromString (string) { if (typeof string !== 'string') { throw new Error('Param is not a string') } const lcStr = string.toLowerCase(); switch (lcStr) { case 'l': case 'low': return exports.L case 'm': case 'medium': return exports.M case 'q': case 'quartile': return exports.Q case 'h': case 'high': return exports.H default: throw new Error('Unknown EC Level: ' + string) } } exports.isValid = function isValid (level) { return level && typeof level.bit !== 'undefined' && level.bit >= 0 && level.bit < 4 }; exports.from = function from (value, defaultValue) { if (exports.isValid(value)) { return value } try { return fromString(value) } catch (e) { return defaultValue } }; } (errorCorrectionLevel)); function BitBuffer$1 () { this.buffer = []; this.length = 0; } BitBuffer$1.prototype = { get: function (index) { const bufIndex = Math.floor(index / 8); return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) === 1 }, put: function (num, length) { for (let i = 0; i < length; i++) { this.putBit(((num >>> (length - i - 1)) & 1) === 1); } }, getLengthInBits: function () { return this.length }, putBit: function (bit) { const bufIndex = Math.floor(this.length / 8); if (this.buffer.length <= bufIndex) { this.buffer.push(0); } if (bit) { this.buffer[bufIndex] |= (0x80 >>> (this.length % 8)); } this.length++; } }; var bitBuffer = BitBuffer$1; /** * Helper class to handle QR Code symbol modules * * @param {Number} size Symbol size */ function BitMatrix$1 (size) { if (!size || size < 1) { throw new Error('BitMatrix size must be defined and greater than 0') } this.size = size; this.data = new Uint8Array(size * size); this.reservedBit = new Uint8Array(size * size); } /** * Set bit value at specified location * If reserved flag is set, this bit will be ignored during masking process * * @param {Number} row * @param {Number} col * @param {Boolean} value * @param {Boolean} reserved */ BitMatrix$1.prototype.set = function (row, col, value, reserved) { const index = row * this.size + col; this.data[index] = value; if (reserved) this.reservedBit[index] = true; }; /** * Returns bit value at specified location * * @param {Number} row * @param {Number} col * @return {Boolean} */ BitMatrix$1.prototype.get = function (row, col) { return this.data[row * this.size + col] }; /** * Applies xor operator at specified location * (used during masking process) * * @param {Number} row * @param {Number} col * @param {Boolean} value */ BitMatrix$1.prototype.xor = function (row, col, value) { this.data[row * this.size + col] ^= value; }; /** * Check if bit at specified location is reserved * * @param {Number} row * @param {Number} col * @return {Boolean} */ BitMatrix$1.prototype.isReserved = function (row, col) { return this.reservedBit[row * this.size + col] }; var bitMatrix = BitMatrix$1; var alignmentPattern = {}; /** * Alignment pattern are fixed reference pattern in defined positions * in a matrix symbology, which enables the decode software to re-synchronise * the coordinate mapping of the image modules in the event of moderate amounts * of distortion of the image. * * Alignment patterns are present only in QR Code symbols of version 2 or larger * and their number depends on the symbol version. */ (function (exports) { const getSymbolSize = utils$3.getSymbolSize; /** * Calculate the row/column coordinates of the center module of each alignment pattern * for the specified QR Code version. * * The alignment patterns are positioned symmetrically on either side of the diagonal * running from the top left corner of the symbol to the bottom right corner. * * Since positions are simmetrical only half of the coordinates are returned. * Each item of the array will represent in turn the x and y coordinate. * @see {@link getPositions} * * @param {Number} version QR Code version * @return {Array} Array of coordinate */ exports.getRowColCoords = function getRowColCoords (version) { if (version === 1) return [] const posCount = Math.floor(version / 7) + 2; const size = getSymbolSize(version); const intervals = size === 145 ? 26 : Math.ceil((size - 13) / (2 * posCount - 2)) * 2; const positions = [size - 7]; // Last coord is always (size - 7) for (let i = 1; i < posCount - 1; i++) { positions[i] = positions[i - 1] - intervals; } positions.push(6); // First coord is always 6 return positions.reverse() }; /** * Returns an array containing the positions of each alignment pattern. * Each array's element represent the center point of the pattern as (x, y) coordinates * * Coordinates are calculated expanding the row/column coordinates returned by {@link getRowColCoords} * and filtering out the items that overlaps with finder pattern * * @example * For a Version 7 symbol {@link getRowColCoords} returns values 6, 22 and 38. * The alignment patterns, therefore, are to be centered on (row, column) * positions (6,22), (22,6), (22,22), (22,38), (38,22), (38,38). * Note that the coordinates (6,6), (6,38), (38,6) are occupied by finder patterns * and are not therefore used for alignment patterns. * * let pos = getPositions(7) * // [[6,22], [22,6], [22,22], [22,38], [38,22], [38,38]] * * @param {Number} version QR Code version * @return {Array} Array of coordinates */ exports.getPositions = function getPositions (version) { const coords = []; const pos = exports.getRowColCoords(version); const posLength = pos.length; for (let i = 0; i < posLength; i++) { for (let j = 0; j < posLength; j++) { // Skip if position is occupied by finder patterns if ((i === 0 && j === 0) || // top-left (i === 0 && j === posLength - 1) || // bottom-left (i === posLength - 1 && j === 0)) { // top-right continue } coords.push([pos[i], pos[j]]); } } return coords }; } (alignmentPattern)); var finderPattern = {}; const getSymbolSize = utils$3.getSymbolSize; const FINDER_PATTERN_SIZE = 7; /** * Returns an array containing the positions of each finder pattern. * Each array's element represent the top-left point of the pattern as (x, y) coordinates * * @param {Number} version QR Code version * @return {Array} Array of coordinates */ finderPattern.getPositions = function getPositions (version) { const size = getSymbolSize(version); return [ // top-left [0, 0], // top-right [size - FINDER_PATTERN_SIZE, 0], // bottom-left [0, size - FINDER_PATTERN_SIZE] ] }; var maskPattern = {}; /** * Data mask pattern reference * @type {Object} */ (function (exports) { exports.Patterns = { PATTERN000: 0, PATTERN001: 1, PATTERN010: 2, PATTERN011: 3, PATTERN100: 4, PATTERN101: 5, PATTERN110: 6, PATTERN111: 7 }; /** * Weighted penalty scores for the undesirable features * @type {Object} */ const PenaltyScores = { N1: 3, N2: 3, N3: 40, N4: 10 }; /** * Check if mask pattern value is valid * * @param {Number} mask Mask pattern * @return {Boolean} true if valid, false otherwise */ exports.isValid = function isValid (mask) { return mask != null && mask !== '' && !isNaN(mask) && mask >= 0 && mask <= 7 }; /** * Returns mask pattern from a value. * If value is not valid, returns undefined * * @param {Number|String} value Mask pattern value * @return {Number} Valid mask pattern or undefined */ exports.from = function from (value) { return exports.isValid(value) ? parseInt(value, 10) : undefined }; /** * Find adjacent modules in row/column with the same color * and assign a penalty value. * * Points: N1 + i * i is the amount by which the number of adjacent modules of the same color exceeds 5 */ exports.getPenaltyN1 = function getPenaltyN1 (data) { const size = data.size; let points = 0; let sameCountCol = 0; let sameCountRow = 0; let lastCol = null; let lastRow = null; for (let row = 0; row < size; row++) { sameCountCol = sameCountRow = 0; lastCol = lastRow = null; for (let col = 0; col < size; col++) { let module = data.get(row, col); if (module === lastCol) { sameCountCol++; } else { if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5); lastCol = module; sameCountCol = 1; } module = data.get(col, row); if (module === lastRow) { sameCountRow++; } else { if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5); lastRow = module; sameCountRow = 1; } } if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5); if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5); } return points }; /** * Find 2x2 blocks with the same color and assign a penalty value * * Points: N2 * (m - 1) * (n - 1) */ exports.getPenaltyN2 = function getPenaltyN2 (data) { const size = data.size; let points = 0; for (let row = 0; row < size - 1; row++) { for (let col = 0; col < size - 1; col++) { const last = data.get(row, col) + data.get(row, col + 1) + data.get(row + 1, col) + data.get(row + 1, col + 1); if (last === 4 || last === 0) points++; } } return points * PenaltyScores.N2 }; /** * Find 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, * preceded or followed by light area 4 modules wide * * Points: N3 * number of pattern found */ exports.getPenaltyN3 = function getPenaltyN3 (data) { const size = data.size; let points = 0; let bitsCol = 0; let bitsRow = 0; for (let row = 0; row < size; row++) { bitsCol = bitsRow = 0; for (let col = 0; col < size; col++) { bitsCol = ((bitsCol << 1) & 0x7FF) | data.get(row, col); if (col >= 10 && (bitsCol === 0x5D0 || bitsCol === 0x05D)) points++; bitsRow = ((bitsRow << 1) & 0x7FF) | data.get(col, row); if (col >= 10 && (bitsRow === 0x5D0 || bitsRow === 0x05D)) points++; } } return points * PenaltyScores.N3 }; /** * Calculate proportion of dark modules in entire symbol * * Points: N4 * k * * k is the rating of the deviation of the proportion of dark modules * in the symbol from 50% in steps of 5% */ exports.getPenaltyN4 = function getPenaltyN4 (data) { let darkCount = 0; const modulesCount = data.data.length; for (let i = 0; i < modulesCount; i++) darkCount += data.data[i]; const k = Math.abs(Math.ceil((darkCount * 100 / modulesCount) / 5) - 10); return k * PenaltyScores.N4 }; /** * Return mask value at given position * * @param {Number} maskPattern Pattern reference value * @param {Number} i Row * @param {Number} j Column * @return {Boolean} Mask value */ function getMaskAt (maskPattern, i, j) { switch (maskPattern) { case exports.Patterns.PATTERN000: return (i + j) % 2 === 0 case exports.Patterns.PATTERN001: return i % 2 === 0 case exports.Patterns.PATTERN010: return j % 3 === 0 case exports.Patterns.PATTERN011: return (i + j) % 3 === 0 case exports.Patterns.PATTERN100: return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0 case exports.Patterns.PATTERN101: return (i * j) % 2 + (i * j) % 3 === 0 case exports.Patterns.PATTERN110: return ((i * j) % 2 + (i * j) % 3) % 2 === 0 case exports.Patterns.PATTERN111: return ((i * j) % 3 + (i + j) % 2) % 2 === 0 default: throw new Error('bad maskPattern:' + maskPattern) } } /** * Apply a mask pattern to a BitMatrix * * @param {Number} pattern Pattern reference number * @param {BitMatrix} data BitMatrix data */ exports.applyMask = function applyMask (pattern, data) { const size = data.size; for (let col = 0; col < size; col++) { for (let row = 0; row < size; row++) { if (data.isReserved(row, col)) continue data.xor(row, col, getMaskAt(pattern, row, col)); } } }; /** * Returns the best mask pattern for data * * @param {BitMatrix} data * @return {Number} Mask pattern reference number */ exports.getBestMask = function getBestMask (data, setupFormatFunc) { const numPatterns = Object.keys(exports.Patterns).length; let bestPattern = 0; let lowerPenalty = Infinity; for (let p = 0; p < numPatterns; p++) { setupFormatFunc(p); exports.applyMask(p, data); // Calculate penalty const penalty = exports.getPenaltyN1(data) + exports.getPenaltyN2(data) + exports.getPenaltyN3(data) + exports.getPenaltyN4(data); // Undo previously applied mask exports.applyMask(p, data); if (penalty < lowerPenalty) { lowerPenalty = penalty; bestPattern = p; } } return bestPattern }; } (maskPattern)); var errorCorrectionCode = {}; const ECLevel$1 = errorCorrectionLevel; const EC_BLOCKS_TABLE = [ // L M Q H 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 4, 1, 2, 4, 4, 2, 4, 4, 4, 2, 4, 6, 5, 2, 4, 6, 6, 2, 5, 8, 8, 4, 5, 8, 8, 4, 5, 8, 11, 4, 8, 10, 11, 4, 9, 12, 16, 4, 9, 16, 16, 6, 10, 12, 18, 6, 10, 17, 16, 6, 11, 16, 19, 6, 13, 18, 21, 7, 14, 21, 25, 8, 16, 20, 25, 8, 17, 23, 25, 9, 17, 23, 34, 9, 18, 25, 30, 10, 20, 27, 32, 12, 21, 29, 35, 12, 23, 34, 37, 12, 25, 34, 40, 13, 26, 35, 42, 14, 28, 38, 45, 15, 29, 40, 48, 16, 31, 43, 51, 17, 33, 45, 54, 18, 35, 48, 57, 19, 37, 51, 60, 19, 38, 53, 63, 20, 40, 56, 66, 21, 43, 59, 70, 22, 45, 62, 74, 24, 47, 65, 77, 25, 49, 68, 81 ]; const EC_CODEWORDS_TABLE = [ // L M Q H 7, 10, 13, 17, 10, 16, 22, 28, 15, 26, 36, 44, 20, 36, 52, 64, 26, 48, 72, 88, 36, 64, 96, 112, 40, 72, 108, 130, 48, 88, 132, 156, 60, 110, 160, 192, 72, 130, 192, 224, 80, 150, 224, 264, 96, 176, 260, 308, 104, 198, 288, 352, 120, 216, 320, 384, 132, 240, 360, 432, 144, 280, 408, 480, 168, 308, 448, 532, 180, 338, 504, 588, 196, 364, 546, 650, 224, 416, 600, 700, 224, 442, 644, 750, 252, 476, 690, 816, 270, 504, 750, 900, 300, 560, 810, 960, 312, 588, 870, 1050, 336, 644, 952, 1110, 360, 700, 1020, 1200, 390, 728, 1050, 1260, 420, 784, 1140, 1350, 450, 812, 1200, 1440, 480, 868, 1290, 1530, 510, 924, 1350, 1620, 540, 980, 1440, 1710, 570, 1036, 1530, 1800, 570, 1064, 1590, 1890, 600, 1120, 1680, 1980, 630, 1204, 1770, 2100, 660, 1260, 1860, 2220, 720, 1316, 1950, 2310, 750, 1372, 2040, 2430 ]; /** * Returns the number of error correction block that the QR Code should contain * for the specified version and error correction level. * * @param {Number} version QR Code version * @param {Number} errorCorrectionLevel Error correction level * @return {Number} Number of error correction blocks */ errorCorrectionCode.getBlocksCount = function getBlocksCount (version, errorCorrectionLevel) { switch (errorCorrectionLevel) { case ECLevel$1.L: return EC_BLOCKS_TABLE[(version - 1) * 4 + 0] case ECLevel$1.M: return EC_BLOCKS_TABLE[(version - 1) * 4 + 1] case ECLevel$1.Q: return EC_BLOCKS_TABLE[(version - 1) * 4 + 2] case ECLevel$1.H: return EC_BLOCKS_TABLE[(version - 1) * 4 + 3] default: return undefined } }; /** * Returns the number of error correction codewords to use for the specified * version and error correction level. * * @param {Number} version QR Code version * @param {Number} errorCorrectionLevel Error correction level * @return {Number} Number of error correction codewords */ errorCorrectionCode.getTotalCodewordsCount = function getTotalCodewordsCount (version, errorCorrectionLevel) { switch (errorCorrectionLevel) { case ECLevel$1.L: return EC_CODEWORDS_TABLE[(version - 1) * 4 + 0] case ECLevel$1.M: return EC_CODEWORDS_TABLE[(version - 1) * 4 + 1] case ECLevel$1.Q: return EC_CODEWORDS_TABLE[(version - 1) * 4 + 2] case ECLevel$1.H: return EC_CODEWORDS_TABLE[(version - 1) * 4 + 3] default: return undefined } }; var polynomial = {}; var galoisField = {}; const EXP_TABLE = new Uint8Array(512); const LOG_TABLE = new Uint8Array(256) /** * Precompute the log and anti-log tables for faster computation later * * For each possible value in the galois field 2^8, we will pre-compute * the logarithm and anti-logarithm (exponential) of this value * * ref {@link https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders#Introduction_to_mathematical_fields} */ ;(function initTables () { let x = 1; for (let i = 0; i < 255; i++) { EXP_TABLE[i] = x; LOG_TABLE[x] = i; x <<= 1; // multiply by 2 // The QR code specification says to use byte-wise modulo 100011101 arithmetic. // This means that when a number is 256 or larger, it should be XORed with 0x11D. if (x & 0x100) { // similar to x >= 256, but a lot faster (because 0x100 == 256) x ^= 0x11D; } } // Optimization: double the size of the anti-log table so that we don't need to mod 255 to // stay inside the bounds (because we will mainly use this table for the multiplication of // two GF numbers, no more). // @see {@link mul} for (let i = 255; i < 512; i++) { EXP_TABLE[i] = EXP_TABLE[i - 255]; } }()); /** * Returns log value of n inside Galois Field * * @param {Number} n * @return {Number} */ galoisField.log = function log (n) { if (n < 1) throw new Error('log(' + n + ')') return LOG_TABLE[n] }; /** * Returns anti-log value of n inside Galois Field * * @param {Number} n * @return {Number} */ galoisField.exp = function exp (n) { return EXP_TABLE[n] }; /** * Multiplies two number inside Galois Field * * @param {Number} x * @param {Number} y * @return {Number} */ galoisField.mul = function mul (x, y) { if (x === 0 || y === 0) return 0 // should be EXP_TABLE[(LOG_TABLE[x] + LOG_TABLE[y]) % 255] if EXP_TABLE wasn't oversized // @see {@link initTables} return EXP_TABLE[LOG_TABLE[x] + LOG_TABLE[y]] }; (function (exports) { const GF = galoisField; /** * Multiplies two polynomials inside Galois Field * * @param {Uint8Array} p1 Polynomial * @param {Uint8Array} p2 Polynomial * @return {Uint8Array} Product of p1 and p2 */ exports.mul = function mul (p1, p2) { const coeff = new Uint8Array(p1.length + p2.length - 1); for (let i = 0; i < p1.length; i++) { for (let j = 0; j < p2.length; j++) { coeff[i + j] ^= GF.mul(p1[i], p2[j]); } } return coeff }; /** * Calculate the remainder of polynomials division * * @param {Uint8Array} divident Polynomial * @param {Uint8Array} divisor Polynomial * @return {Uint8Array} Remainder */ exports.mod = function mod (divident, divisor) { let result = new Uint8Array(divident); while ((result.length - divisor.length) >= 0) { const coeff = result[0]; for (let i = 0; i < divisor.length; i++) { result[i] ^= GF.mul(divisor[i], coeff); } // remove all zeros from buffer head let offset = 0; while (offset < result.length && result[offset] === 0) offset++; result = result.slice(offset); } return result }; /** * Generate an irreducible generator polynomial of specified degree * (used by Reed-Solomon encoder) * * @param {Number} degree Degree of the generator polynomial * @return {Uint8Array} Buffer containing polynomial coefficients */ exports.generateECPolynomial = function generateECPolynomial (degree) { let poly = new Uint8Array([1]); for (let i = 0; i < degree; i++) { poly = exports.mul(poly, new Uint8Array([1, GF.exp(i)])); } return poly }; } (polynomial)); const Polynomial = polynomial; function ReedSolomonEncoder$1 (degree) { this.genPoly = undefined; this.degree = degree; if (this.degree) this.initialize(this.degree); } /** * Initialize the encoder. * The input param should correspond to the number of error correction codewords. * * @param {Number} degree */ ReedSolomonEncoder$1.prototype.initialize = function initialize (degree) { // create an irreducible generator polynomial this.degree = degree; this.genPoly = Polynomial.generateECPolynomial(this.degree); }; /** * Encodes a chunk of data * * @param {Uint8Array} data Buffer containing input data * @return {Uint8Array} Buffer containing encoded data */ ReedSolomonEncoder$1.prototype.encode = function encode (data) { if (!this.genPoly) { throw new Error('Encoder not initialized') } // Calculate EC for this data block // extends data size to data+genPoly size const paddedData = new Uint8Array(data.length + this.degree); paddedData.set(data); // The error correction codewords are the remainder after dividing the data codewords // by a generator polynomial const remainder = Polynomial.mod(paddedData, this.genPoly); // return EC data blocks (last n byte, where n is the degree of genPoly) // If coefficients number in remainder are less than genPoly degree, // pad with 0s to the left to reach the needed number of coefficients const start = this.degree - remainder.length; if (start > 0) { const buff = new Uint8Array(this.degree); buff.set(remainder, start); return buff } return remainder }; var reedSolomonEncoder = ReedSolomonEncoder$1; var version = {}; var mode = {}; var versionCheck = {}; /** * Check if QR Code version is valid * * @param {Number} version QR Code version * @return {Boolean} true if valid version, false otherwise */ versionCheck.isValid = function isValid (version) { return !isNaN(version) && version >= 1 && version <= 40 }; var regex = {}; const numeric = '[0-9]+'; const alphanumeric = '[A-Z $%*+\\-./:]+'; let kanji = '(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|' + '[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|' + '[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|' + '[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+'; kanji = kanji.replace(/u/g, '\\u'); const byte = '(?:(?![A-Z0-9 $%*+\\-./:]|' + kanji + ')(?:.|[\r\n]))+'; regex.KANJI = new RegExp(kanji, 'g'); regex.BYTE_KANJI = new RegExp('[^A-Z0-9 $%*+\\-./:]+', 'g'); regex.BYTE = new RegExp(byte, 'g'); regex.NUMERIC = new RegExp(numeric, 'g'); regex.ALPHANUMERIC = new RegExp(alphanumeric, 'g'); const TEST_KANJI = new RegExp('^' + kanji + '$'); const TEST_NUMERIC = new RegExp('^' + numeric + '$'); const TEST_ALPHANUMERIC = new RegExp('^[A-Z0-9 $%*+\\-./:]+$'); regex.testKanji = function testKanji (str) { return TEST_KANJI.test(str) }; regex.testNumeric = function testNumeric (str) { return TEST_NUMERIC.test(str) }; regex.testAlphanumeric = function testAlphanumeric (str) { return TEST_ALPHANUMERIC.test(str) }; (function (exports) { const VersionCheck = versionCheck; const Regex = regex; /** * Numeric mode encodes data from the decimal digit set (0 - 9) * (byte values 30HEX to 39HEX). * Normally, 3 data characters are represented by 10 bits. * * @type {Object} */ exports.NUMERIC = { id: 'Numeric', bit: 1 << 0, ccBits: [10, 12, 14] }; /** * Alphanumeric mode encodes data from a set of 45 characters, * i.e. 10 numeric digits (0 - 9), * 26 alphabetic characters (A - Z), * and 9 symbols (SP, $, %, *, +, -, ., /, :). * Normally, two input characters are represented by 11 bits. * * @type {Object} */ exports.ALPHANUMERIC = { id: 'Alphanumeric', bit: 1 << 1, ccBits: [9, 11, 13] }; /** * In byte mode, data is encoded at 8 bits per character. * * @type {Object} */ exports.BYTE = { id: 'Byte', bit: 1 << 2, ccBits: [8, 16, 16] }; /** * The Kanji mode efficiently encodes Kanji characters in accordance with * the Shift JIS system based on JIS X 0208. * The Shift JIS values are shifted from the JIS X 0208 values. * JIS X 0208 gives details of the shift coded representation. * Each two-byte character value is compacted to a 13-bit binary codeword. * * @type {Object} */ exports.KANJI = { id: 'Kanji', bit: 1 << 3, ccBits: [8, 10, 12] }; /** * Mixed mode will contain a sequences of data in a combination of any of * the modes described above * * @type {Object} */ exports.MIXED = { bit: -1 }; /** * Returns the number of bits needed to store the data length * according to QR Code specifications. * * @param {Mode} mode Data mode * @param {Number} version QR Code version * @return {Number} Number of bits */ exports.getCharCountIndicator = function getCharCountIndicator (mode, version) { if (!mode.ccBits) throw new Error('Invalid mode: ' + mode) if (!VersionCheck.isValid(version)) { throw new Error('Invalid version: ' + version) } if (version >= 1 && version < 10) return mode.ccBits[0] else if (version < 27) return mode.ccBits[1] return mode.ccBits[2] }; /** * Returns the most efficient mode to store the specified data * * @param {String} dataStr Input data string * @return {Mode} Best mode */ exports.getBestModeForData = function getBestModeForData (dataStr) { if (Regex.testNumeric(dataStr)) return exports.NUMERIC else if (Regex.testAlphanumeric(dataStr)) return exports.ALPHANUMERIC else if (Regex.testKanji(dataStr)) return exports.KANJI else return exports.BYTE }; /** * Return mode name as string * * @param {Mode} mode Mode object * @returns {String} Mode name */ exports.toString = function toString (mode) { if (mode && mode.id) return mode.id throw new Error('Invalid mode') }; /** * Check if input param is a valid mode object * * @param {Mode} mode Mode object * @returns {Boolean} True if valid mode, false otherwise */ exports.isValid = function isValid (mode) { return mode && mode.bit && mode.ccBits }; /** * Get mode object from its name * * @param {String} string Mode name * @returns {Mode} Mode object */ function fromString (string) { if (typeof string !== 'string') { throw new Error('Param is not a string') } const lcStr = string.toLowerCase(); switch (lcStr) { case 'numeric': return exports.NUMERIC case 'alphanumeric': return exports.ALPHANUMERIC case 'kanji': return exports.KANJI case 'byte': return exports.BYTE default: throw new Error('Unknown mode: ' + string) } } /** * Returns mode from a value. * If value is not a valid mode, returns defaultValue * * @param {Mode|String} value Encoding mode * @param {Mode} defaultValue Fallback value * @return {Mode} Encoding mode */ exports.from = function from (value, defaultValue) { if (exports.isValid(value)) { return value } try { return fromString(value) } catch (e) { return defaultValue } }; } (mode)); (function (exports) { const Utils = utils$3; const ECCode = errorCorrectionCode; const ECLevel = errorCorrectionLevel; const Mode = mode; const VersionCheck = versionCheck; // Generator polynomial used to encode version information const G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); const G18_BCH = Utils.getBCHDigit(G18); function getBestVersionForDataLength (mode, length, errorCorrectionLevel) { for (let currentVersion = 1; currentVersion <= 40; currentVersion++) { if (length <= exports.getCapacity(currentVersion, errorCorrectionLevel, mode)) { return currentVersion } } return undefined } function getReservedBitsCount (mode, version) { // Character count indicator + mode indicator bits return Mode.getCharCountIndicator(mode, version) + 4 } function getTotalBitsFromDataArray (segments, version) { let totalBits = 0; segments.forEach(function (data) { const reservedBits = getReservedBitsCount(data.mode, version); totalBits += reservedBits + data.getBitsLength(); }); return totalBits } function getBestVersionForMixedData (segments, errorCorrectionLevel) { for (let currentVersion = 1; currentVersion <= 40; currentVersion++) { const length = getTotalBitsFromDataArray(segments, currentVersion); if (length <= exports.getCapacity(currentVersion, errorCorrectionLevel, Mode.MIXED)) { return currentVersion } } return undefined } /** * Returns version number from a value. * If value is not a valid version, returns defaultValue * * @param {Number|String} value QR Code version * @param {Number} defaultValue Fallback value * @return {Number} QR Code version number */ exports.from = function from (value, defaultValue) { if (VersionCheck.isValid(value)) { return parseInt(value, 10) } return defaultValue }; /** * Returns how much data can be stored with the specified QR code version * and error correction level * * @param {Number} version QR Code version (1-40) * @param {Number} errorCorrectionLevel Error correction level * @param {Mode} mode Data mode * @return {Number} Quantity of storable data */ exports.getCapacity = function getCapacity (version, errorCorrectionLevel, mode) { if (!VersionCheck.isValid(version)) { throw new Error('Invalid QR Code version') } // Use Byte mode as default if (typeof mode === 'undefined') mode = Mode.BYTE; // Total codewords for this QR code version (Data + Error correction) const totalCodewords = Utils.getSymbolTotalCodewords(version); // Total number of error correction codewords const ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel); // Total number of data codewords const dataTotalCodewordsBits = (totalCodewords - ecTotalCodewords) * 8; if (mode === Mode.MIXED) return dataTotalCodewordsBits const usableBits = dataTotalCodewordsBits - getReservedBitsCount(mode, version); // Return max number of storable codewords switch (mode) { case Mode.NUMERIC: return Math.floor((usableBits / 10) * 3) case Mode.ALPHANUMERIC: return Math.floor((usableBits / 11) * 2) case Mode.KANJI: return Math.floor(usableBits / 13) case Mode.BYTE: default: return Math.floor(usableBits / 8) } }; /** * Returns the minimum version needed to contain the amount of data * * @param {Segment} data Segment of data * @param {Number} [errorCorrectionLevel=H] Error correction level * @param {Mode} mode Data mode * @return {Number} QR Code version */ exports.getBestVersionForData = function getBestVersionForData (data, errorCorrectionLevel) { let seg; const ecl = ECLevel.from(errorCorrectionLevel, ECLevel.M); if (Array.isArray(data)) { if (data.length > 1) { return getBestVersionForMixedData(data, ecl) } if (data.length === 0) { return 1 } seg = data[0]; } else { seg = data; } return getBestVersionForDataLength(seg.mode, seg.getLength(), ecl) }; /** * Returns version information with relative error correction bits * * The version information is included in QR Code symbols of version 7 or larger. * It consists of an 18-bit sequence containing 6 data bits, * with 12 error correction bits calculated using the (18, 6) Golay code. * * @param {Number} version QR Code version * @return {Number} Encoded version info bits */ exports.getEncodedBits = function getEncodedBits (version) { if (!VersionCheck.isValid(version) || version < 7) { throw new Error('Invalid QR Code version') } let d = version << 12; while (Utils.getBCHDigit(d) - G18_BCH >= 0) { d ^= (G18 << (Utils.getBCHDigit(d) - G18_BCH)); } return (version << 12) | d }; } (version)); var formatInfo = {}; const Utils$3 = utils$3; const G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); const G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); const G15_BCH = Utils$3.getBCHDigit(G15); /** * Returns format information with relative error correction bits * * The format information is a 15-bit sequence containing 5 data bits, * with 10 error correction bits calculated using the (15, 5) BCH code. * * @param {Number} errorCorrectionLevel Error correction level * @param {Number} mask Mask pattern * @return {Number} Encoded format information bits */ formatInfo.getEncodedBits = function getEncodedBits (errorCorrectionLevel, mask) { const data = ((errorCorrectionLevel.bit << 3) | mask); let d = data << 10; while (Utils$3.getBCHDigit(d) - G15_BCH >= 0) { d ^= (G15 << (Utils$3.getBCHDigit(d) - G15_BCH)); } // xor final data with mask pattern in order to ensure that // no combination of Error Correction Level and data mask pattern // will result in an all-zero data string return ((data << 10) | d) ^ G15_MASK }; var segments = {}; const Mode$4 = mode; function NumericData (data) { this.mode = Mode$4.NUMERIC; this.data = data.toString(); } NumericData.getBitsLength = function getBitsLength (length) { return 10 * Math.floor(length / 3) + ((length % 3) ? ((length % 3) * 3 + 1) : 0) }; NumericData.prototype.getLength = function getLength () { return this.data.length }; NumericData.prototype.getBitsLength = function getBitsLength () { return NumericData.getBitsLength(this.data.length) }; NumericData.prototype.write = function write (bitBuffer) { let i, group, value; // The input data string is divided into groups of three digits, // and each group is converted to its 10-bit binary equivalent. for (i = 0; i + 3 <= this.data.length; i += 3) { group = this.data.substr(i, 3); value = parseInt(group, 10); bitBuffer.put(value, 10); } // If the number of input digits is not an exact multiple of three, // the final one or two digits are converted to 4 or 7 bits respectively. const remainingNum = this.data.length - i; if (remainingNum > 0) { group = this.data.substr(i); value = parseInt(group, 10); bitBuffer.put(value, remainingNum * 3 + 1); } }; var numericData = NumericData; const Mode$3 = mode; /** * Array of characters available in alphanumeric mode * * As per QR Code specification, to each character * is assigned a value from 0 to 44 which in this case coincides * with the array index * * @type {Array} */ const ALPHA_NUM_CHARS = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':' ]; function AlphanumericData (data) { this.mode = Mode$3.ALPHANUMERIC; this.data = data; } AlphanumericData.getBitsLength = function getBitsLength (length) { return 11 * Math.floor(length / 2) + 6 * (length % 2) }; AlphanumericData.prototype.getLength = function getLength () { return this.data.length }; AlphanumericData.prototype.getBitsLength = function getBitsLength () { return AlphanumericData.getBitsLength(this.data.length) }; AlphanumericData.prototype.write = function write (bitBuffer) { let i; // Input data characters are divided into groups of two characters // and encoded as 11-bit binary codes. for (i = 0; i + 2 <= this.data.length; i += 2) { // The character value of the first character is multiplied by 45 let value = ALPHA_NUM_CHARS.indexOf(this.data[i]) * 45; // The character value of the second digit is added to the product value += ALPHA_NUM_CHARS.indexOf(this.data[i + 1]); // The sum is then stored as 11-bit binary number bitBuffer.put(value, 11); } // If the number of input data characters is not a multiple of two, // the character value of the final character is encoded as a 6-bit binary number. if (this.data.length % 2) { bitBuffer.put(ALPHA_NUM_CHARS.indexOf(this.data[i]), 6); } }; var alphanumericData = AlphanumericData; const Mode$2 = mode; function ByteData (data) { this.mode = Mode$2.BYTE; if (typeof (data) === 'string') { this.data = new TextEncoder().encode(data); } else { this.data = new Uint8Array(data); } } ByteData.getBitsLength = function getBitsLength (length) { return length * 8 }; ByteData.prototype.getLength = function getLength () { return this.data.length }; ByteData.prototype.getBitsLength = function getBitsLength () { return ByteData.getBitsLength(this.data.length) }; ByteData.prototype.write = function (bitBuffer) { for (let i = 0, l = this.data.length; i < l; i++) { bitBuffer.put(this.data[i], 8); } }; var byteData = ByteData; const Mode$1 = mode; const Utils$2 = utils$3; function KanjiData (data) { this.mode = Mode$1.KANJI; this.data = data; } KanjiData.getBitsLength = function getBitsLength (length) { return length * 13 }; KanjiData.prototype.getLength = function getLength () { return this.data.length }; KanjiData.prototype.getBitsLength = function getBitsLength () { return KanjiData.getBitsLength(this.data.length) }; KanjiData.prototype.write = function (bitBuffer) { let i; // In the Shift JIS system, Kanji characters are represented by a two byte combination. // These byte values are shifted from the JIS X 0208 values. // JIS X 0208 gives details of the shift coded representation. for (i = 0; i < this.data.length; i++) { let value = Utils$2.toSJIS(this.data[i]); // For characters with Shift JIS values from 0x8140 to 0x9FFC: if (value >= 0x8140 && value <= 0x9FFC) { // Subtract 0x8140 from Shift JIS value value -= 0x8140; // For characters with Shift JIS values from 0xE040 to 0xEBBF } else if (value >= 0xE040 && value <= 0xEBBF) { // Subtract 0xC140 from Shift JIS value value -= 0xC140; } else { throw new Error( 'Invalid SJIS character: ' + this.data[i] + '\n' + 'Make sure your charset is UTF-8') } // Multiply most significant byte of result by 0xC0 // and add least significant byte to product value = (((value >>> 8) & 0xff) * 0xC0) + (value & 0xff); // Convert result to a 13-bit binary string bitBuffer.put(value, 13); } }; var kanjiData = KanjiData; var dijkstra = {exports: {}}; (function (module) { /****************************************************************************** * Created 2008-08-19. * * Dijkstra path-finding functions. Adapted from the Dijkstar Python project. * * Copyright (C) 2008 * Wyatt Baldwin * All rights reserved * * Licensed under the MIT license. * * http://www.opensource.org/licenses/mit-license.php * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. *****************************************************************************/ var dijkstra = { single_source_shortest_paths: function(graph, s, d) { // Predecessor map for each node that has been encountered. // node ID => predecessor node ID var predecessors = {}; // Costs of shortest paths from s to all nodes encountered. // node ID => cost var costs = {}; costs[s] = 0; // Costs of shortest paths from s to all nodes encountered; differs from // `costs` in that it provides easy access to the node that currently has // the known shortest path from s. // XXX: Do we actually need both `costs` and `open`? var open = dijkstra.PriorityQueue.make(); open.push(s, 0); var closest, u, v, cost_of_s_to_u, adjacent_nodes, cost_of_e, cost_of_s_to_u_plus_cost_of_e, cost_of_s_to_v, first_visit; while (!open.empty()) { // In the nodes remaining in graph that have a known cost from s, // find the node, u, that currently has the shortest path from s. closest = open.pop(); u = closest.value; cost_of_s_to_u = closest.cost; // Get nodes adjacent to u... adjacent_nodes = graph[u] || {}; // ...and explore the edges that connect u to those nodes, updating // the cost of the shortest paths to any or all of those nodes as // necessary. v is the node across the current edge from u. for (v in adjacent_nodes) { if (adjacent_nodes.hasOwnProperty(v)) { // Get the cost of the edge running from u to v. cost_of_e = adjacent_nodes[v]; // Cost of s to u plus the cost of u to v across e--this is *a* // cost from s to v that may or may not be less than the current // known cost to v. cost_of_s_to_u_plus_cost_of_e = cost_of_s_to_u + cost_of_e; // If we haven't visited v yet OR if the current known cost from s to // v is greater than the new cost we just found (cost of s to u plus // cost of u to v across e), update v's cost in the cost list and // update v's predecessor in the predecessor list (it's now u). cost_of_s_to_v = costs[v]; first_visit = (typeof costs[v] === 'undefined'); if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) { costs[v] = cost_of_s_to_u_plus_cost_of_e; open.push(v, cost_of_s_to_u_plus_cost_of_e); predecessors[v] = u; } } } } if (typeof d !== 'undefined' && typeof costs[d] === 'undefined') { var msg = ['Could not find a path from ', s, ' to ', d, '.'].join(''); throw new Error(msg); } return predecessors; }, extract_shortest_path_from_predecessor_list: function(predecessors, d) { var nodes = []; var u = d; while (u) { nodes.push(u); predecessors[u]; u = predecessors[u]; } nodes.reverse(); return nodes; }, find_path: function(graph, s, d) { var predecessors = dijkstra.single_source_shortest_paths(graph, s, d); return dijkstra.extract_shortest_path_from_predecessor_list( predecessors, d); }, /** * A very naive priority queue implementation. */ PriorityQueue: { make: function (opts) { var T = dijkstra.PriorityQueue, t = {}, key; opts = opts || {}; for (key in T) { if (T.hasOwnProperty(key)) { t[key] = T[key]; } } t.queue = []; t.sorter = opts.sorter || T.default_sorter; return t; }, default_sorter: function (a, b) { return a.cost - b.cost; }, /** * Add a new item to the queue and ensure the highest priority element * is at the front of the queue. */ push: function (value, cost) { var item = {value: value, cost: cost}; this.queue.push(item); this.queue.sort(this.sorter); }, /** * Return the highest priority element in the queue. */ pop: function () { return this.queue.shift(); }, empty: function () { return this.queue.length === 0; } } }; // node.js module exports { module.exports = dijkstra; } } (dijkstra)); var dijkstraExports = dijkstra.exports; (function (exports) { const Mode = mode; const NumericData = numericData; const AlphanumericData = alphanumericData; const ByteData = byteData; const KanjiData = kanjiData; const Regex = regex; const Utils = utils$3; const dijkstra = dijkstraExports; /** * Returns UTF8 byte length * * @param {String} str Input string * @return {Number} Number of byte */ function getStringByteLength (str) { return unescape(encodeURIComponent(str)).length } /** * Get a list of segments of the specified mode * from a string * * @param {Mode} mode Segment mode * @param {String} str String to process * @return {Array} Array of object with segments data */ function getSegments (regex, mode, str) { const segments = []; let result; while ((result = regex.exec(str)) !== null) { segments.push({ data: result[0], index: result.index, mode: mode, length: result[0].length }); } return segments } /** * Extracts a series of segments with the appropriate * modes from a string * * @param {String} dataStr Input string * @return {Array} Array of object with segments data */ function getSegmentsFromString (dataStr) { const numSegs = getSegments(Regex.NUMERIC, Mode.NUMERIC, dataStr); const alphaNumSegs = getSegments(Regex.ALPHANUMERIC, Mode.ALPHANUMERIC, dataStr); let byteSegs; let kanjiSegs; if (Utils.isKanjiModeEnabled()) { byteSegs = getSegments(Regex.BYTE, Mode.BYTE, dataStr); kanjiSegs = getSegments(Regex.KANJI, Mode.KANJI, dataStr); } else { byteSegs = getSegments(Regex.BYTE_KANJI, Mode.BYTE, dataStr); kanjiSegs = []; } const segs = numSegs.concat(alphaNumSegs, byteSegs, kanjiSegs); return segs .sort(function (s1, s2) { return s1.index - s2.index }) .map(function (obj) { return { data: obj.data, mode: obj.mode, length: obj.length } }) } /** * Returns how many bits are needed to encode a string of * specified length with the specified mode * * @param {Number} length String length * @param {Mode} mode Segment mode * @return {Number} Bit length */ function getSegmentBitsLength (length, mode) { switch (mode) { case Mode.NUMERIC: return NumericData.getBitsLength(length) case Mode.ALPHANUMERIC: return AlphanumericData.getBitsLength(length) case Mode.KANJI: return KanjiData.getBitsLength(length) case Mode.BYTE: return ByteData.getBitsLength(length) } } /** * Merges adjacent segments which have the same mode * * @param {Array} segs Array of object with segments data * @return {Array} Array of object with segments data */ function mergeSegments (segs) { return segs.reduce(function (acc, curr) { const prevSeg = acc.length - 1 >= 0 ? acc[acc.length - 1] : null; if (prevSeg && prevSeg.mode === curr.mode) { acc[acc.length - 1].data += curr.data; return acc } acc.push(curr); return acc }, []) } /** * Generates a list of all possible nodes combination which * will be used to build a segments graph. * * Nodes are divided by groups. Each group will contain a list of all the modes * in which is possible to encode the given text. * * For example the text '12345' can be encoded as Numeric, Alphanumeric or Byte. * The group for '12345' will contain then 3 objects, one for each * possible encoding mode. * * Each node represents a possible segment. * * @param {Array} segs Array of object with segments data * @return {Array} Array of object with segments data */ function buildNodes (segs) { const nodes = []; for (let i = 0; i < segs.length; i++) { const seg = segs[i]; switch (seg.mode) { case Mode.NUMERIC: nodes.push([seg, { data: seg.data, mode: Mode.ALPHANUMERIC, length: seg.length }, { data: seg.data, mode: Mode.BYTE, length: seg.length } ]); break case Mode.ALPHANUMERIC: nodes.push([seg, { data: seg.data, mode: Mode.BYTE, length: seg.length } ]); break case Mode.KANJI: nodes.push([seg, { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) } ]); break case Mode.BYTE: nodes.push([ { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) } ]); } } return nodes } /** * Builds a graph from a list of nodes. * All segments in each node group will be connected with all the segments of * the next group and so on. * * At each connection will be assigned a weight depending on the * segment's byte length. * * @param {Array} nodes Array of object with segments data * @param {Number} version QR Code version * @return {Object} Graph of all possible segments */ function buildGraph (nodes, version) { const table = {}; const graph = { start: {} }; let prevNodeIds = ['start']; for (let i = 0; i < nodes.length; i++) { const nodeGroup = nodes[i]; const currentNodeIds = []; for (let j = 0; j < nodeGroup.length; j++) { const node = nodeGroup[j]; const key = '' + i + j; currentNodeIds.push(key); table[key] = { node: node, lastCount: 0 }; graph[key] = {}; for (let n = 0; n < prevNodeIds.length; n++) { const prevNodeId = prevNodeIds[n]; if (table[prevNodeId] && table[prevNodeId].node.mode === node.mode) { graph[prevNodeId][key] = getSegmentBitsLength(table[prevNodeId].lastCount + node.length, node.mode) - getSegmentBitsLength(table[prevNodeId].lastCount, node.mode); table[prevNodeId].lastCount += node.length; } else { if (table[prevNodeId]) table[prevNodeId].lastCount = node.length; graph[prevNodeId][key] = getSegmentBitsLength(node.length, node.mode) + 4 + Mode.getCharCountIndicator(node.mode, version); // switch cost } } } prevNodeIds = currentNodeIds; } for (let n = 0; n < prevNodeIds.length; n++) { graph[prevNodeIds[n]].end = 0; } return { map: graph, table: table } } /** * Builds a segment from a specified data and mode. * If a mode is not specified, the more suitable will be used. * * @param {String} data Input data * @param {Mode | String} modesHint Data mode * @return {Segment} Segment */ function buildSingleSegment (data, modesHint) { let mode; const bestMode = Mode.getBestModeForData(data); mode = Mode.from(modesHint, bestMode); // Make sure data can be encoded if (mode !== Mode.BYTE && mode.bit < bestMode.bit) { throw new Error('"' + data + '"' + ' cannot be encoded with mode ' + Mode.toString(mode) + '.\n Suggested mode is: ' + Mode.toString(bestMode)) } // Use Mode.BYTE if Kanji support is disabled if (mode === Mode.KANJI && !Utils.isKanjiModeEnabled()) { mode = Mode.BYTE; } switch (mode) { case Mode.NUMERIC: return new NumericData(data) case Mode.ALPHANUMERIC: return new AlphanumericData(data) case Mode.KANJI: return new KanjiData(data) case Mode.BYTE: return new ByteData(data) } } /** * Builds a list of segments from an array. * Array can contain Strings or Objects with segment's info. * * For each item which is a string, will be generated a segment with the given * string and the more appropriate encoding mode. * * For each item which is an object, will be generated a segment with the given * data and mode. * Objects must contain at least the property "data". * If property "mode" is not present, the more suitable mode will be used. * * @param {Array} array Array of objects with segments data * @return {Array} Array of Segments */ exports.fromArray = function fromArray (array) { return array.reduce(function (acc, seg) { if (typeof seg === 'string') { acc.push(buildSingleSegment(seg, null)); } else if (seg.data) { acc.push(buildSingleSegment(seg.data, seg.mode)); } return acc }, []) }; /** * Builds an optimized sequence of segments from a string, * which will produce the shortest possible bitstream. * * @param {String} data Input string * @param {Number} version QR Code version * @return {Array} Array of segments */ exports.fromString = function fromString (data, version) { const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled()); const nodes = buildNodes(segs); const graph = buildGraph(nodes, version); const path = dijkstra.find_path(graph.map, 'start', 'end'); const optimizedSegs = []; for (let i = 1; i < path.length - 1; i++) { optimizedSegs.push(graph.table[path[i]].node); } return exports.fromArray(mergeSegments(optimizedSegs)) }; /** * Splits a string in various segments with the modes which * best represent their content. * The produced segments are far from being optimized. * The output of this function is only used to estimate a QR Code version * which may contain the data. * * @param {string} data Input string * @return {Array} Array of segments */ exports.rawSplit = function rawSplit (data) { return exports.fromArray( getSegmentsFromString(data, Utils.isKanjiModeEnabled()) ) }; } (segments)); const Utils$1 = utils$3; const ECLevel = errorCorrectionLevel; const BitBuffer = bitBuffer; const BitMatrix = bitMatrix; const AlignmentPattern = alignmentPattern; const FinderPattern = finderPattern; const MaskPattern = maskPattern; const ECCode = errorCorrectionCode; const ReedSolomonEncoder = reedSolomonEncoder; const Version = version; const FormatInfo = formatInfo; const Mode = mode; const Segments = segments; /** * QRCode for JavaScript * * modified by Ryan Day for nodejs support * Copyright (c) 2011 Ryan Day * * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * //--------------------------------------------------------------------- // QRCode for JavaScript // // Copyright (c) 2009 Kazuhiko Arase // // URL: http://www.d-project.com/ // // Licensed under the MIT license: // http://www.opensource.org/licenses/mit-license.php // // The word "QR Code" is registered trademark of // DENSO WAVE INCORPORATED // http://www.denso-wave.com/qrcode/faqpatent-e.html // //--------------------------------------------------------------------- */ /** * Add finder patterns bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupFinderPattern (matrix, version) { const size = matrix.size; const pos = FinderPattern.getPositions(version); for (let i = 0; i < pos.length; i++) { const row = pos[i][0]; const col = pos[i][1]; for (let r = -1; r <= 7; r++) { if (row + r <= -1 || size <= row + r) continue for (let c = -1; c <= 7; c++) { if (col + c <= -1 || size <= col + c) continue if ((r >= 0 && r <= 6 && (c === 0 || c === 6)) || (c >= 0 && c <= 6 && (r === 0 || r === 6)) || (r >= 2 && r <= 4 && c >= 2 && c <= 4)) { matrix.set(row + r, col + c, true, true); } else { matrix.set(row + r, col + c, false, true); } } } } } /** * Add timing pattern bits to matrix * * Note: this function must be called before {@link setupAlignmentPattern} * * @param {BitMatrix} matrix Modules matrix */ function setupTimingPattern (matrix) { const size = matrix.size; for (let r = 8; r < size - 8; r++) { const value = r % 2 === 0; matrix.set(r, 6, value, true); matrix.set(6, r, value, true); } } /** * Add alignment patterns bits to matrix * * Note: this function must be called after {@link setupTimingPattern} * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupAlignmentPattern (matrix, version) { const pos = AlignmentPattern.getPositions(version); for (let i = 0; i < pos.length; i++) { const row = pos[i][0]; const col = pos[i][1]; for (let r = -2; r <= 2; r++) { for (let c = -2; c <= 2; c++) { if (r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0)) { matrix.set(row + r, col + c, true, true); } else { matrix.set(row + r, col + c, false, true); } } } } } /** * Add version info bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupVersionInfo (matrix, version) { const size = matrix.size; const bits = Version.getEncodedBits(version); let row, col, mod; for (let i = 0; i < 18; i++) { row = Math.floor(i / 3); col = i % 3 + size - 8 - 3; mod = ((bits >> i) & 1) === 1; matrix.set(row, col, mod, true); matrix.set(col, row, mod, true); } } /** * Add format info bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @param {Number} maskPattern Mask pattern reference value */ function setupFormatInfo (matrix, errorCorrectionLevel, maskPattern) { const size = matrix.size; const bits = FormatInfo.getEncodedBits(errorCorrectionLevel, maskPattern); let i, mod; for (i = 0; i < 15; i++) { mod = ((bits >> i) & 1) === 1; // vertical if (i < 6) { matrix.set(i, 8, mod, true); } else if (i < 8) { matrix.set(i + 1, 8, mod, true); } else { matrix.set(size - 15 + i, 8, mod, true); } // horizontal if (i < 8) { matrix.set(8, size - i - 1, mod, true); } else if (i < 9) { matrix.set(8, 15 - i - 1 + 1, mod, true); } else { matrix.set(8, 15 - i - 1, mod, true); } } // fixed module matrix.set(size - 8, 8, 1, true); } /** * Add encoded data bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Uint8Array} data Data codewords */ function setupData (matrix, data) { const size = matrix.size; let inc = -1; let row = size - 1; let bitIndex = 7; let byteIndex = 0; for (let col = size - 1; col > 0; col -= 2) { if (col === 6) col--; while (true) { for (let c = 0; c < 2; c++) { if (!matrix.isReserved(row, col - c)) { let dark = false; if (byteIndex < data.length) { dark = (((data[byteIndex] >>> bitIndex) & 1) === 1); } matrix.set(row, col - c, dark); bitIndex--; if (bitIndex === -1) { byteIndex++; bitIndex = 7; } } } row += inc; if (row < 0 || size <= row) { row -= inc; inc = -inc; break } } } } /** * Create encoded codewords from data input * * @param {Number} version QR Code version * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @param {ByteData} data Data input * @return {Uint8Array} Buffer containing encoded codewords */ function createData (version, errorCorrectionLevel, segments) { // Prepare data buffer const buffer = new BitBuffer(); segments.forEach(function (data) { // prefix data with mode indicator (4 bits) buffer.put(data.mode.bit, 4); // Prefix data with character count indicator. // The character count indicator is a string of bits that represents the // number of characters that are being encoded. // The character count indicator must be placed after the mode indicator // and must be a certain number of bits long, depending on the QR version // and data mode // @see {@link Mode.getCharCountIndicator}. buffer.put(data.getLength(), Mode.getCharCountIndicator(data.mode, version)); // add binary data sequence to buffer data.write(buffer); }); // Calculate required number of bits const totalCodewords = Utils$1.getSymbolTotalCodewords(version); const ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel); const dataTotalCodewordsBits = (totalCodewords - ecTotalCodewords) * 8; // Add a terminator. // If the bit string is shorter than the total number of required bits, // a terminator of up to four 0s must be added to the right side of the string. // If the bit string is more than four bits shorter than the required number of bits, // add four 0s to the end. if (buffer.getLengthInBits() + 4 <= dataTotalCodewordsBits) { buffer.put(0, 4); } // If the bit string is fewer than four bits shorter, add only the number of 0s that // are needed to reach the required number of bits. // After adding the terminator, if the number of bits in the string is not a multiple of 8, // pad the string on the right with 0s to make the string's length a multiple of 8. while (buffer.getLengthInBits() % 8 !== 0) { buffer.putBit(0); } // Add pad bytes if the string is still shorter than the total number of required bits. // Extend the buffer to fill the data capacity of the symbol corresponding to // the Version and Error Correction Level by adding the Pad Codewords 11101100 (0xEC) // and 00010001 (0x11) alternately. const remainingByte = (dataTotalCodewordsBits - buffer.getLengthInBits()) / 8; for (let i = 0; i < remainingByte; i++) { buffer.put(i % 2 ? 0x11 : 0xEC, 8); } return createCodewords(buffer, version, errorCorrectionLevel) } /** * Encode input data with Reed-Solomon and return codewords with * relative error correction bits * * @param {BitBuffer} bitBuffer Data to encode * @param {Number} version QR Code version * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @return {Uint8Array} Buffer containing encoded codewords */ function createCodewords (bitBuffer, version, errorCorrectionLevel) { // Total codewords for this QR code version (Data + Error correction) const totalCodewords = Utils$1.getSymbolTotalCodewords(version); // Total number of error correction codewords const ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel); // Total number of data codewords const dataTotalCodewords = totalCodewords - ecTotalCodewords; // Total number of blocks const ecTotalBlocks = ECCode.getBlocksCount(version, errorCorrectionLevel); // Calculate how many blocks each group should contain const blocksInGroup2 = totalCodewords % ecTotalBlocks; const blocksInGroup1 = ecTotalBlocks - blocksInGroup2; const totalCodewordsInGroup1 = Math.floor(totalCodewords / ecTotalBlocks); const dataCodewordsInGroup1 = Math.floor(dataTotalCodewords / ecTotalBlocks); const dataCodewordsInGroup2 = dataCodewordsInGroup1 + 1; // Number of EC codewords is the same for both groups const ecCount = totalCodewordsInGroup1 - dataCodewordsInGroup1; // Initialize a Reed-Solomon encoder with a generator polynomial of degree ecCount const rs = new ReedSolomonEncoder(ecCount); let offset = 0; const dcData = new Array(ecTotalBlocks); const ecData = new Array(ecTotalBlocks); let maxDataSize = 0; const buffer = new Uint8Array(bitBuffer.buffer); // Divide the buffer into the required number of blocks for (let b = 0; b < ecTotalBlocks; b++) { const dataSize = b < blocksInGroup1 ? dataCodewordsInGroup1 : dataCodewordsInGroup2; // extract a block of data from buffer dcData[b] = buffer.slice(offset, offset + dataSize); // Calculate EC codewords for this data block ecData[b] = rs.encode(dcData[b]); offset += dataSize; maxDataSize = Math.max(maxDataSize, dataSize); } // Create final data // Interleave the data and error correction codewords from each block const data = new Uint8Array(totalCodewords); let index = 0; let i, r; // Add data codewords for (i = 0; i < maxDataSize; i++) { for (r = 0; r < ecTotalBlocks; r++) { if (i < dcData[r].length) { data[index++] = dcData[r][i]; } } } // Apped EC codewords for (i = 0; i < ecCount; i++) { for (r = 0; r < ecTotalBlocks; r++) { data[index++] = ecData[r][i]; } } return data } /** * Build QR Code symbol * * @param {String} data Input string * @param {Number} version QR Code version * @param {ErrorCorretionLevel} errorCorrectionLevel Error level * @param {MaskPattern} maskPattern Mask pattern * @return {Object} Object containing symbol data */ function createSymbol (data, version, errorCorrectionLevel, maskPattern) { let segments; if (Array.isArray(data)) { segments = Segments.fromArray(data); } else if (typeof data === 'string') { let estimatedVersion = version; if (!estimatedVersion) { const rawSegments = Segments.rawSplit(data); // Estimate best version that can contain raw splitted segments estimatedVersion = Version.getBestVersionForData(rawSegments, errorCorrectionLevel); } // Build optimized segments // If estimated version is undefined, try with the highest version segments = Segments.fromString(data, estimatedVersion || 40); } else { throw new Error('Invalid data') } // Get the min version that can contain data const bestVersion = Version.getBestVersionForData(segments, errorCorrectionLevel); // If no version is found, data cannot be stored if (!bestVersion) { throw new Error('The amount of data is too big to be stored in a QR Code') } // If not specified, use min version as default if (!version) { version = bestVersion; // Check if the specified version can contain the data } else if (version < bestVersion) { throw new Error('\n' + 'The chosen QR Code version cannot contain this amount of data.\n' + 'Minimum version required to store current data is: ' + bestVersion + '.\n' ) } const dataBits = createData(version, errorCorrectionLevel, segments); // Allocate matrix buffer const moduleCount = Utils$1.getSymbolSize(version); const modules = new BitMatrix(moduleCount); // Add function modules setupFinderPattern(modules, version); setupTimingPattern(modules); setupAlignmentPattern(modules, version); // Add temporary dummy bits for format info just to set them as reserved. // This is needed to prevent these bits from being masked by {@link MaskPattern.applyMask} // since the masking operation must be performed only on the encoding region. // These blocks will be replaced with correct values later in code. setupFormatInfo(modules, errorCorrectionLevel, 0); if (version >= 7) { setupVersionInfo(modules, version); } // Add data codewords setupData(modules, dataBits); if (isNaN(maskPattern)) { // Find best mask pattern maskPattern = MaskPattern.getBestMask(modules, setupFormatInfo.bind(null, modules, errorCorrectionLevel)); } // Apply mask pattern MaskPattern.applyMask(maskPattern, modules); // Replace format info bits with correct values setupFormatInfo(modules, errorCorrectionLevel, maskPattern); return { modules: modules, version: version, errorCorrectionLevel: errorCorrectionLevel, maskPattern: maskPattern, segments: segments } } /** * QR Code * * @param {String | Array} data Input data * @param {Object} options Optional configurations * @param {Number} options.version QR Code version * @param {String} options.errorCorrectionLevel Error correction level * @param {Function} options.toSJISFunc Helper func to convert utf8 to sjis */ qrcode.create = function create (data, options) { if (typeof data === 'undefined' || data === '') { throw new Error('No input text') } let errorCorrectionLevel = ECLevel.M; let version; let mask; if (typeof options !== 'undefined') { // Use higher error correction level as default errorCorrectionLevel = ECLevel.from(options.errorCorrectionLevel, ECLevel.M); version = Version.from(options.version); mask = MaskPattern.from(options.maskPattern); if (options.toSJISFunc) { Utils$1.setToSJISFunction(options.toSJISFunc); } } return createSymbol(data, version, errorCorrectionLevel, mask) }; var canvas = {}; var utils$2 = {}; (function (exports) { function hex2rgba (hex) { if (typeof hex === 'number') { hex = hex.toString(); } if (typeof hex !== 'string') { throw new Error('Color should be defined as hex string') } let hexCode = hex.slice().replace('#', '').split(''); if (hexCode.length < 3 || hexCode.length === 5 || hexCode.length > 8) { throw new Error('Invalid hex color: ' + hex) } // Convert from short to long form (fff -> ffffff) if (hexCode.length === 3 || hexCode.length === 4) { hexCode = Array.prototype.concat.apply([], hexCode.map(function (c) { return [c, c] })); } // Add default alpha value if (hexCode.length === 6) hexCode.push('F', 'F'); const hexValue = parseInt(hexCode.join(''), 16); return { r: (hexValue >> 24) & 255, g: (hexValue >> 16) & 255, b: (hexValue >> 8) & 255, a: hexValue & 255, hex: '#' + hexCode.slice(0, 6).join('') } } exports.getOptions = function getOptions (options) { if (!options) options = {}; if (!options.color) options.color = {}; const margin = typeof options.margin === 'undefined' || options.margin === null || options.margin < 0 ? 4 : options.margin; const width = options.width && options.width >= 21 ? options.width : undefined; const scale = options.scale || 4; return { width: width, scale: width ? 4 : scale, margin: margin, color: { dark: hex2rgba(options.color.dark || '#000000ff'), light: hex2rgba(options.color.light || '#ffffffff') }, type: options.type, rendererOpts: options.rendererOpts || {} } }; exports.getScale = function getScale (qrSize, opts) { return opts.width && opts.width >= qrSize + opts.margin * 2 ? opts.width / (qrSize + opts.margin * 2) : opts.scale }; exports.getImageWidth = function getImageWidth (qrSize, opts) { const scale = exports.getScale(qrSize, opts); return Math.floor((qrSize + opts.margin * 2) * scale) }; exports.qrToImageData = function qrToImageData (imgData, qr, opts) { const size = qr.modules.size; const data = qr.modules.data; const scale = exports.getScale(size, opts); const symbolSize = Math.floor((size + opts.margin * 2) * scale); const scaledMargin = opts.margin * scale; const palette = [opts.color.light, opts.color.dark]; for (let i = 0; i < symbolSize; i++) { for (let j = 0; j < symbolSize; j++) { let posDst = (i * symbolSize + j) * 4; let pxColor = opts.color.light; if (i >= scaledMargin && j >= scaledMargin && i < symbolSize - scaledMargin && j < symbolSize - scaledMargin) { const iSrc = Math.floor((i - scaledMargin) / scale); const jSrc = Math.floor((j - scaledMargin) / scale); pxColor = palette[data[iSrc * size + jSrc] ? 1 : 0]; } imgData[posDst++] = pxColor.r; imgData[posDst++] = pxColor.g; imgData[posDst++] = pxColor.b; imgData[posDst] = pxColor.a; } } }; } (utils$2)); (function (exports) { const Utils = utils$2; function clearCanvas (ctx, canvas, size) { ctx.clearRect(0, 0, canvas.width, canvas.height); if (!canvas.style) canvas.style = {}; canvas.height = size; canvas.width = size; canvas.style.height = size + 'px'; canvas.style.width = size + 'px'; } function getCanvasElement () { try { return document.createElement('canvas') } catch (e) { throw new Error('You need to specify a canvas element') } } exports.render = function render (qrData, canvas, options) { let opts = options; let canvasEl = canvas; if (typeof opts === 'undefined' && (!canvas || !canvas.getContext)) { opts = canvas; canvas = undefined; } if (!canvas) { canvasEl = getCanvasElement(); } opts = Utils.getOptions(opts); const size = Utils.getImageWidth(qrData.modules.size, opts); const ctx = canvasEl.getContext('2d'); const image = ctx.createImageData(size, size); Utils.qrToImageData(image.data, qrData, opts); clearCanvas(ctx, canvasEl, size); ctx.putImageData(image, 0, 0); return canvasEl }; exports.renderToDataURL = function renderToDataURL (qrData, canvas, options) { let opts = options; if (typeof opts === 'undefined' && (!canvas || !canvas.getContext)) { opts = canvas; canvas = undefined; } if (!opts) opts = {}; const canvasEl = exports.render(qrData, canvas, opts); const type = opts.type || 'image/png'; const rendererOpts = opts.rendererOpts || {}; return canvasEl.toDataURL(type, rendererOpts.quality) }; } (canvas)); var svgTag = {}; const Utils = utils$2; function getColorAttrib (color, attrib) { const alpha = color.a / 255; const str = attrib + '="' + color.hex + '"'; return alpha < 1 ? str + ' ' + attrib + '-opacity="' + alpha.toFixed(2).slice(1) + '"' : str } function svgCmd (cmd, x, y) { let str = cmd + x; if (typeof y !== 'undefined') str += ' ' + y; return str } function qrToPath (data, size, margin) { let path = ''; let moveBy = 0; let newRow = false; let lineLength = 0; for (let i = 0; i < data.length; i++) { const col = Math.floor(i % size); const row = Math.floor(i / size); if (!col && !newRow) newRow = true; if (data[i]) { lineLength++; if (!(i > 0 && col > 0 && data[i - 1])) { path += newRow ? svgCmd('M', col + margin, 0.5 + row + margin) : svgCmd('m', moveBy, 0); moveBy = 0; newRow = false; } if (!(col + 1 < size && data[i + 1])) { path += svgCmd('h', lineLength); lineLength = 0; } } else { moveBy++; } } return path } svgTag.render = function render (qrData, options, cb) { const opts = Utils.getOptions(options); const size = qrData.modules.size; const data = qrData.modules.data; const qrcodesize = size + opts.margin * 2; const bg = !opts.color.light.a ? '' : ''; const path = ''; const viewBox = 'viewBox="' + '0 0 ' + qrcodesize + ' ' + qrcodesize + '"'; const width = !opts.width ? '' : 'width="' + opts.width + '" height="' + opts.width + '" '; const svgTag = '' + bg + path + '\n'; if (typeof cb === 'function') { cb(null, svgTag); } return svgTag }; const canPromise = canPromise$1; const QRCode = qrcode; const CanvasRenderer = canvas; const SvgRenderer = svgTag; function renderCanvas (renderFunc, canvas, text, opts, cb) { const args = [].slice.call(arguments, 1); const argsNum = args.length; const isLastArgCb = typeof args[argsNum - 1] === 'function'; if (!isLastArgCb && !canPromise()) { throw new Error('Callback required as last argument') } if (isLastArgCb) { if (argsNum < 2) { throw new Error('Too few arguments provided') } if (argsNum === 2) { cb = text; text = canvas; canvas = opts = undefined; } else if (argsNum === 3) { if (canvas.getContext && typeof cb === 'undefined') { cb = opts; opts = undefined; } else { cb = opts; opts = text; text = canvas; canvas = undefined; } } } else { if (argsNum < 1) { throw new Error('Too few arguments provided') } if (argsNum === 1) { text = canvas; canvas = opts = undefined; } else if (argsNum === 2 && !canvas.getContext) { opts = text; text = canvas; canvas = undefined; } return new Promise(function (resolve, reject) { try { const data = QRCode.create(text, opts); resolve(renderFunc(data, canvas, opts)); } catch (e) { reject(e); } }) } try { const data = QRCode.create(text, opts); cb(null, renderFunc(data, canvas, opts)); } catch (e) { cb(e); } } QRCode.create; renderCanvas.bind(null, CanvasRenderer.render); renderCanvas.bind(null, CanvasRenderer.renderToDataURL); // only svg for now. renderCanvas.bind(null, function (data, _, opts) { return SvgRenderer.render(data, opts) }); function generateEmojiList() { const emojiRanges = [ [128512, 128591], [127744, 128511], [128640, 128767], [128768, 128895] ]; const emojiList = []; for (const range of emojiRanges) { const [start, end] = range; for (let i = start; i <= end && emojiList.length < 256; i++) { emojiList.push(String.fromCodePoint(i)); } if (emojiList.length >= 256) { break; } } return emojiList.slice(0, 256); } async function addressToEmoji(text) { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest("SHA-256", data); const hash = new Uint8Array(hashBuffer); const bytes = hash.slice(-4); const emojiList = generateEmojiList(); const emojis = Array.from(bytes).map((byte) => emojiList[byte]).join(""); return emojis; } async function emojisPairingRequest() { try { const container = getCorrectDOM("login-4nk-component"); const urlParams = new URLSearchParams(window.location.search); const sp_adress = urlParams.get("sp_address"); if (!sp_adress) { return; } const emojis = await addressToEmoji(sp_adress); const emojiDisplay = container?.querySelector(".pairing-request"); if (emojiDisplay) { emojiDisplay.textContent = "(Request from: " + emojis + ")"; } } catch (err) { console.error(err); } } async function displayEmojis(text) { console.log("🚀 ~ Services ~ adressToEmoji"); try { const container = getCorrectDOM("login-4nk-component"); const emojis = await addressToEmoji(text); const emojiDisplay = container?.querySelector(".emoji-display"); if (emojiDisplay) { emojiDisplay.textContent = emojis; } emojisPairingRequest(); initAddressInput(); } catch (err) { console.error(err); } } function initAddressInput() { const container = getCorrectDOM("login-4nk-component"); const addressInput = container.querySelector("#addressInput"); const emojiDisplay = container.querySelector("#emoji-display-2"); const okButton = container.querySelector("#okButton"); const createButton = container.querySelector("#createButton"); container.querySelector("#actionButton"); addSubscription(addressInput, "input", async () => { let address = addressInput.value; try { const url = new URL(address); const urlParams = new URLSearchParams(url.search); const extractedAddress = urlParams.get("sp_address") || ""; if (extractedAddress) { address = extractedAddress; addressInput.value = address; } } catch (e) { console.log("Ce n'est pas une URL valide, on garde l'adresse originale."); } if (address) { const emojis = await addressToEmoji(address); if (emojiDisplay) { emojiDisplay.innerHTML = emojis; } if (okButton) { okButton.style.display = "inline-block"; } } else { if (emojiDisplay) { emojiDisplay.innerHTML = ""; } if (okButton) { okButton.style.display = "none"; } } }); if (createButton) { addSubscription(createButton, "click", () => { onCreateButtonClick(); }); } } async function onCreateButtonClick() { try { await prepareAndSendPairingTx$1(); const service = await Services.getInstance(); await service.confirmPairing(); } catch (e) { console.error(`onCreateButtonClick error: ${e}`); } } async function prepareAndSendPairingTx$1() { const service = await Services.getInstance(); try { await service.checkConnections([]); } catch (e) { throw e; } try { const relayAddress = service.getAllRelays(); const createPairingProcessReturn = await service.createPairingProcess("", []); if (!createPairingProcessReturn.updated_process) { throw new Error("createPairingProcess returned an empty new process"); } service.setProcessId(createPairingProcessReturn.updated_process.process_id); service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id); await service.handleApiReturn(createPairingProcessReturn); } catch (err) { console.error(err); } } async function generateCreateBtn() { try { const container = getCorrectDOM("login-4nk-component"); const createBtn = container?.querySelector(".create-btn"); if (createBtn) { createBtn.textContent = "CREATE"; } } catch (err) { console.error(err); } } async function validate() { console.log("==> VALIDATE"); const modalservice = await ModalService.getInstance(); modalservice.closeValidationModal(); } async function initValidationModal(processDiffs) { console.log("🚀 ~ initValidationModal ~ processDiffs:", processDiffs); for (const diff of processDiffs.diffs) { let diffs = ""; for (const value of diff) { diffs += `
-${value.previous_value}
+${value.new_value}
`; } const state = `
State ${diff[0].new_state_merkle_root}
${diffs}
`; const box = document.querySelector(".validation-box"); if (box) box.innerHTML += state; } document.querySelectorAll(".expansion-panel-header").forEach((header) => { header.addEventListener("click", function(event) { const target = event.target; const body = target.nextElementSibling; if (body?.style) body.style.display = body.style.display === "block" ? "none" : "block"; }); }); } window.validate = validate; function interpolate(template, data) { return template.replace(/{{(.*?)}}/g, (_, key) => data[key.trim()]); } let ModalService$1 = class ModalService { static instance; stateId = null; processId = null; constructor() { } paired_addresses = []; modal = null; // Method to access the singleton instance of Services static async getInstance() { if (!ModalService.instance) { ModalService.instance = new ModalService(); } return ModalService.instance; } openLoginModal(myAddress, receiverAddress) { const container = document.querySelector(".page-container"); let html = modalHtml; html = html.replace("{{device1}}", myAddress); html = html.replace("{{device2}}", receiverAddress); if (container) container.innerHTML += html; const modal = document.getElementById("login-modal"); if (modal) modal.style.display = "flex"; const newScript = document.createElement("script"); newScript.setAttribute("type", "module"); newScript.textContent = modalScript; document.head.appendChild(newScript).parentNode?.removeChild(newScript); } async injectModal(members) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/confirmation-modal.html").then((res) => res.text()); html = html.replace("{{device1}}", await addressToEmoji(members[0]["sp_addresses"][0])); html = html.replace("{{device2}}", await addressToEmoji(members[0]["sp_addresses"][1])); container.innerHTML += html; const script = document.createElement("script"); script.src = "/src/components/modal/confirmation-modal.ts"; script.type = "module"; document.head.appendChild(script); } } async injectCreationModal(members) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/creation-modal.html").then((res) => res.text()); html = html.replace("{{device1}}", await addressToEmoji(members[0]["sp_addresses"][0])); container.innerHTML += html; const script = document.createElement("script"); script.src = "/src/components/modal/confirmation-modal.ts"; script.type = "module"; document.head.appendChild(script); } } // Device 1 wait Device 2 async injectWaitingModal() { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/waiting-modal.html").then((res) => res.text()); container.innerHTML += html; } } async injectValidationModal(processDiff) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/validation-modal/validation-modal.html").then((res) => res.text()); html = interpolate(html, { processId: processDiff.processId }); container.innerHTML += html; const script = document.createElement("script"); script.id = "validation-modal-script"; script.src = "/src/components/validation-modal/validation-modal.ts"; script.type = "module"; document.head.appendChild(script); const css = document.createElement("style"); css.id = "validation-modal-css"; css.innerText = validationModalStyle; document.head.appendChild(css); initValidationModal(processDiff); } } async closeValidationModal() { const script = document.querySelector("#validation-modal-script"); const css = document.querySelector("#validation-modal-css"); const component = document.querySelector("#validation-modal"); script?.remove(); css?.remove(); component?.remove(); } async openPairingConfirmationModal(roleDefinition, processId, stateId) { let memberOutPoints; if (roleDefinition["pairing"]) { const owner = roleDefinition["pairing"]; memberOutPoints = owner.members; } else { throw new Error('No "pairing" role'); } if (memberOutPoints.length != 1) { throw new Error("Must have exactly 1 member"); } console.log("MEMBER OUTPOINTS:", memberOutPoints); const service = await Services.getInstance(); const localAddress = service.getDeviceAddress(); const members = []; for (const outPoint of memberOutPoints) { const member = { sp_addresses: [] }; members.push(member); } for (const member of members) { if (member.sp_addresses) { for (const address of member.sp_addresses) { if (address !== localAddress) { this.paired_addresses.push(address); } } } } this.processId = processId; this.stateId = stateId; if (members[0].sp_addresses.length === 1) { await this.injectCreationModal(members); this.modal = document.getElementById("creation-modal"); console.log("LENGTH:", members[0].sp_addresses.length); } else { await this.injectModal(members); this.modal = document.getElementById("modal"); console.log("LENGTH:", members[0].sp_addresses.length); } if (this.modal) this.modal.style.display = "flex"; window.onclick = (event) => { if (event.target === this.modal) { this.closeConfirmationModal(); } }; } confirmLogin() { console.log("=============> Confirm Login"); } async closeLoginModal() { if (this.modal) this.modal.style.display = "none"; } async showConfirmationModal(options, fullscreen = false) { const modalElement = document.createElement("div"); modalElement.id = "confirmation-modal"; modalElement.innerHTML = ` `; document.body.appendChild(modalElement); return new Promise((resolve) => { const confirmButton = modalElement.querySelector("#confirm-button"); const cancelButton = modalElement.querySelector("#cancel-button"); const modalOverlay = modalElement.querySelector(".modal-overlay"); const cleanup = () => { modalElement.remove(); }; confirmButton?.addEventListener("click", () => { cleanup(); resolve(true); }); cancelButton?.addEventListener("click", () => { cleanup(); resolve(false); }); modalOverlay?.addEventListener("click", (e) => { if (e.target === modalOverlay) { cleanup(); resolve(false); } }); }); } async closeConfirmationModal() { const service = await Services.getInstance(); await service.unpairDevice(); if (this.modal) this.modal.style.display = "none"; } }; class Database { static instance; db = null; dbName = "4nk"; dbVersion = 1; serviceWorkerRegistration = null; messageChannel = null; messageChannelForGet = null; serviceWorkerCheckIntervalId = null; storeDefinitions = { AnkLabels: { name: "labels", options: { keyPath: "emoji" }, indices: [] }, AnkWallet: { name: "wallet", options: { keyPath: "pre_id" }, indices: [] }, AnkProcess: { name: "processes", options: {}, indices: [] }, AnkSharedSecrets: { name: "shared_secrets", options: {}, indices: [] }, AnkUnconfirmedSecrets: { name: "unconfirmed_secrets", options: { autoIncrement: true }, indices: [] }, AnkPendingDiffs: { name: "diffs", options: { keyPath: "value_commitment" }, indices: [ { name: "byStateId", keyPath: "state_id", options: { unique: false } }, { name: "byNeedValidation", keyPath: "need_validation", options: { unique: false } }, { name: "byStatus", keyPath: "validation_status", options: { unique: false } } ] }, AnkData: { name: "data", options: {}, indices: [] } }; // Private constructor to prevent direct instantiation from outside constructor() { } // Method to access the singleton instance of Database static async getInstance() { if (!Database.instance) { Database.instance = new Database(); await Database.instance.init(); } return Database.instance; } // Initialize the database async init() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, this.dbVersion); request.onupgradeneeded = () => { const db = request.result; Object.values(this.storeDefinitions).forEach(({ name, options, indices }) => { if (!db.objectStoreNames.contains(name)) { let store = db.createObjectStore(name, options); indices.forEach(({ name: name2, keyPath, options: options2 }) => { store.createIndex(name2, keyPath, options2); }); } }); }; request.onsuccess = async () => { this.db = request.result; resolve(); }; request.onerror = () => { console.error("Database error:", request.error); reject(request.error); }; }); } async getDb() { if (!this.db) { await this.init(); } return this.db; } getStoreList() { const objectList = {}; Object.keys(this.storeDefinitions).forEach((key) => { objectList[key] = this.storeDefinitions[key].name; }); return objectList; } async registerServiceWorker(path) { if (!("serviceWorker" in navigator)) return; console.log("registering worker at", path); try { const registrations = await navigator.serviceWorker.getRegistrations(); if (registrations.length === 0) { this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, { type: "module" }); console.log("Service Worker registered with scope:", this.serviceWorkerRegistration.scope); } else if (registrations.length === 1) { this.serviceWorkerRegistration = registrations[0]; await this.serviceWorkerRegistration.update(); console.log("Service Worker updated"); } else { console.log("Multiple Service Worker(s) detected. Unregistering all..."); await Promise.all(registrations.map((reg) => reg.unregister())); console.log("All previous Service Workers unregistered."); this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, { type: "module" }); console.log("Service Worker registered with scope:", this.serviceWorkerRegistration.scope); } await this.checkForUpdates(); navigator.serviceWorker.addEventListener("message", async (event) => { console.log("Received message from service worker:", event.data); await this.handleServiceWorkerMessage(event.data); }); this.serviceWorkerCheckIntervalId = window.setInterval(async () => { const activeWorker = this.serviceWorkerRegistration?.active || await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration); const service = await Services.getInstance(); const payload = await service.getMyProcesses(); if (payload && payload.length != 0) { activeWorker?.postMessage({ type: "SCAN", payload }); } }, 5e3); } catch (error) { console.error("Service Worker registration failed:", error); } } // Helper function to wait for service worker activation async waitForServiceWorkerActivation(registration) { return new Promise((resolve) => { if (registration.active) { resolve(registration.active); } else { const listener = () => { if (registration.active) { navigator.serviceWorker.removeEventListener("controllerchange", listener); resolve(registration.active); } }; navigator.serviceWorker.addEventListener("controllerchange", listener); } }); } async checkForUpdates() { if (this.serviceWorkerRegistration) { try { await this.serviceWorkerRegistration.update(); if (this.serviceWorkerRegistration.waiting) { this.serviceWorkerRegistration.waiting.postMessage({ type: "SKIP_WAITING" }); } } catch (error) { console.error("Error checking for service worker updates:", error); } } } async handleServiceWorkerMessage(message) { switch (message.type) { case "TO_DOWNLOAD": await this.handleDownloadList(message.data); break; default: console.warn("Unknown message type received from service worker:", message); } } async handleDownloadList(downloadList) { let requestedStateId = []; const service = await Services.getInstance(); for (const hash of downloadList) { const diff = await service.getDiffByValue(hash); if (!diff) { console.warn(`Missing a diff for hash ${hash}`); continue; } const processId = diff.process_id; const stateId = diff.state_id; const roles = diff.roles; try { const valueBytes = await service.fetchValueFromStorage(hash); if (valueBytes) { const blob = new Blob([valueBytes], { type: "application/octet-stream" }); await service.saveBlobToDb(hash, blob); document.dispatchEvent(new CustomEvent("newDataReceived", { detail: { processId, stateId, hash } })); } else { console.log("Request data from managers of the process"); if (!requestedStateId.includes(stateId)) { await service.requestDataFromPeers(processId, [stateId], [roles]); requestedStateId.push(stateId); } } } catch (e) { console.error(e); } } } handleAddObjectResponse = async (event) => { const data = event.data; console.log("Received response from service worker (ADD_OBJECT):", data); const service = await Services.getInstance(); if (data.type === "NOTIFICATIONS") { service.setNotifications(data.data); } else if (data.type === "TO_DOWNLOAD") { console.log(`Received missing data ${data}`); let requestedStateId = []; for (const hash of data.data) { try { const valueBytes = await service.fetchValueFromStorage(hash); if (valueBytes) { const blob = new Blob([valueBytes], { type: "application/octet-stream" }); await service.saveBlobToDb(hash, blob); } else { console.log("Request data from managers of the process"); const diff = await service.getDiffByValue(hash); if (diff === null) { continue; } const processId = diff.process_id; const stateId = diff.state_id; const roles = diff.roles; if (!requestedStateId.includes(stateId)) { await service.requestDataFromPeers(processId, [stateId], [roles]); requestedStateId.push(stateId); } } } catch (e) { console.error(e); } } } }; handleGetObjectResponse = (event) => { console.log("Received response from service worker (GET_OBJECT):", event.data); }; addObject(payload) { return new Promise(async (resolve, reject) => { if (!this.serviceWorkerRegistration) { this.serviceWorkerRegistration = await navigator.serviceWorker.ready; } const activeWorker = await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration); const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { if (event.data.status === "success") { resolve(); } else { const error = event.data.message; reject(new Error(error || "Unknown error occurred while adding object")); } }; try { activeWorker?.postMessage({ type: "ADD_OBJECT", payload }, [messageChannel.port2]); } catch (error) { reject(new Error(`Failed to send message to service worker: ${error}`)); } }); } batchWriting(payload) { return new Promise(async (resolve, reject) => { if (!this.serviceWorkerRegistration) { this.serviceWorkerRegistration = await navigator.serviceWorker.ready; } const activeWorker = await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration); const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { if (event.data.status === "success") { resolve(); } else { const error = event.data.message; reject(new Error(error || "Unknown error occurred while adding objects")); } }; try { activeWorker?.postMessage({ type: "BATCH_WRITING", payload }, [messageChannel.port2]); } catch (error) { reject(new Error(`Failed to send message to service worker: ${error}`)); } }); } async getObject(storeName, key) { const db = await this.getDb(); const tx = db.transaction(storeName, "readonly"); const store = tx.objectStore(storeName); const result = await new Promise((resolve, reject) => { const getRequest = store.get(key); getRequest.onsuccess = () => resolve(getRequest.result); getRequest.onerror = () => reject(getRequest.error); }); return result; } async dumpStore(storeName) { const db = await this.getDb(); const tx = db.transaction(storeName, "readonly"); const store = tx.objectStore(storeName); try { return new Promise((resolve, reject) => { const result = {}; const cursor = store.openCursor(); cursor.onsuccess = (event) => { const request = event.target; const cursor2 = request.result; if (cursor2) { result[cursor2.key] = cursor2.value; cursor2.continue(); } else { resolve(result); } }; cursor.onerror = () => { reject(cursor.error); }; }); } catch (error) { console.error("Error fetching data from IndexedDB:", error); throw error; } } async deleteObject(storeName, key) { const db = await this.getDb(); const tx = db.transaction(storeName, "readwrite"); const store = tx.objectStore(storeName); try { await new Promise((resolve, reject) => { const getRequest = store.delete(key); getRequest.onsuccess = () => resolve(getRequest.result); getRequest.onerror = () => reject(getRequest.error); }); } catch (e) { throw e; } } async clearStore(storeName) { const db = await this.getDb(); const tx = db.transaction(storeName, "readwrite"); const store = tx.objectStore(storeName); try { await new Promise((resolve, reject) => { const clearRequest = store.clear(); clearRequest.onsuccess = () => resolve(clearRequest.result); clearRequest.onerror = () => reject(clearRequest.error); }); } catch (e) { throw e; } } // Request a store by index async requestStoreByIndex(storeName, indexName, request) { const db = await this.getDb(); const tx = db.transaction(storeName, "readonly"); const store = tx.objectStore(storeName); const index = store.index(indexName); try { return new Promise((resolve, reject) => { const getAllRequest = index.getAll(request); getAllRequest.onsuccess = () => { const allItems = getAllRequest.result; const filtered = allItems.filter((item) => item.state_id === request); resolve(filtered); }; getAllRequest.onerror = () => reject(getAllRequest.error); }); } catch (e) { throw e; } } } function bind(fn, thisArg) { return function wrap() { return fn.apply(thisArg, arguments); }; } // utils is a library of generic helper functions non-specific to axios const {toString} = Object.prototype; const {getPrototypeOf} = Object; const {iterator, toStringTag} = Symbol; const kindOf = (cache => thing => { const str = toString.call(thing); return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); })(Object.create(null)); const kindOfTest = (type) => { type = type.toLowerCase(); return (thing) => kindOf(thing) === type }; const typeOfTest = type => thing => typeof thing === type; /** * Determine if a value is an Array * * @param {Object} val The value to test * * @returns {boolean} True if value is an Array, otherwise false */ const {isArray} = Array; /** * Determine if a value is undefined * * @param {*} val The value to test * * @returns {boolean} True if the value is undefined, otherwise false */ const isUndefined = typeOfTest('undefined'); /** * Determine if a value is a Buffer * * @param {*} val The value to test * * @returns {boolean} True if value is a Buffer, otherwise false */ function isBuffer(val) { return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) && isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val); } /** * Determine if a value is an ArrayBuffer * * @param {*} val The value to test * * @returns {boolean} True if value is an ArrayBuffer, otherwise false */ const isArrayBuffer = kindOfTest('ArrayBuffer'); /** * Determine if a value is a view on an ArrayBuffer * * @param {*} val The value to test * * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false */ function isArrayBufferView(val) { let result; if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { result = ArrayBuffer.isView(val); } else { result = (val) && (val.buffer) && (isArrayBuffer(val.buffer)); } return result; } /** * Determine if a value is a String * * @param {*} val The value to test * * @returns {boolean} True if value is a String, otherwise false */ const isString = typeOfTest('string'); /** * Determine if a value is a Function * * @param {*} val The value to test * @returns {boolean} True if value is a Function, otherwise false */ const isFunction = typeOfTest('function'); /** * Determine if a value is a Number * * @param {*} val The value to test * * @returns {boolean} True if value is a Number, otherwise false */ const isNumber = typeOfTest('number'); /** * Determine if a value is an Object * * @param {*} thing The value to test * * @returns {boolean} True if value is an Object, otherwise false */ const isObject$1 = (thing) => thing !== null && typeof thing === 'object'; /** * Determine if a value is a Boolean * * @param {*} thing The value to test * @returns {boolean} True if value is a Boolean, otherwise false */ const isBoolean = thing => thing === true || thing === false; /** * Determine if a value is a plain Object * * @param {*} val The value to test * * @returns {boolean} True if value is a plain Object, otherwise false */ const isPlainObject = (val) => { if (kindOf(val) !== 'object') { return false; } const prototype = getPrototypeOf(val); return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(toStringTag in val) && !(iterator in val); }; /** * Determine if a value is an empty object (safely handles Buffers) * * @param {*} val The value to test * * @returns {boolean} True if value is an empty object, otherwise false */ const isEmptyObject = (val) => { // Early return for non-objects or Buffers to prevent RangeError if (!isObject$1(val) || isBuffer(val)) { return false; } try { return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype; } catch (e) { // Fallback for any other objects that might cause RangeError with Object.keys() return false; } }; /** * Determine if a value is a Date * * @param {*} val The value to test * * @returns {boolean} True if value is a Date, otherwise false */ const isDate = kindOfTest('Date'); /** * Determine if a value is a File * * @param {*} val The value to test * * @returns {boolean} True if value is a File, otherwise false */ const isFile = kindOfTest('File'); /** * Determine if a value is a Blob * * @param {*} val The value to test * * @returns {boolean} True if value is a Blob, otherwise false */ const isBlob = kindOfTest('Blob'); /** * Determine if a value is a FileList * * @param {*} val The value to test * * @returns {boolean} True if value is a File, otherwise false */ const isFileList = kindOfTest('FileList'); /** * Determine if a value is a Stream * * @param {*} val The value to test * * @returns {boolean} True if value is a Stream, otherwise false */ const isStream = (val) => isObject$1(val) && isFunction(val.pipe); /** * Determine if a value is a FormData * * @param {*} thing The value to test * * @returns {boolean} True if value is an FormData, otherwise false */ const isFormData = (thing) => { let kind; return thing && ( (typeof FormData === 'function' && thing instanceof FormData) || ( isFunction(thing.append) && ( (kind = kindOf(thing)) === 'formdata' || // detect form-data instance (kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]') ) ) ) }; /** * Determine if a value is a URLSearchParams object * * @param {*} val The value to test * * @returns {boolean} True if value is a URLSearchParams object, otherwise false */ const isURLSearchParams = kindOfTest('URLSearchParams'); const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream', 'Request', 'Response', 'Headers'].map(kindOfTest); /** * Trim excess whitespace off the beginning and end of a string * * @param {String} str The String to trim * * @returns {String} The String freed of excess whitespace */ const trim = (str) => str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); /** * Iterate over an Array or an Object invoking a function for each item. * * If `obj` is an Array callback will be called passing * the value, index, and complete array for each item. * * If 'obj' is an Object callback will be called passing * the value, key, and complete object for each property. * * @param {Object|Array} obj The object to iterate * @param {Function} fn The callback to invoke for each item * * @param {Boolean} [allOwnKeys = false] * @returns {any} */ function forEach(obj, fn, {allOwnKeys = false} = {}) { // Don't bother if no value provided if (obj === null || typeof obj === 'undefined') { return; } let i; let l; // Force an array if not already something iterable if (typeof obj !== 'object') { /*eslint no-param-reassign:0*/ obj = [obj]; } if (isArray(obj)) { // Iterate over array values for (i = 0, l = obj.length; i < l; i++) { fn.call(null, obj[i], i, obj); } } else { // Buffer check if (isBuffer(obj)) { return; } // Iterate over object keys const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj); const len = keys.length; let key; for (i = 0; i < len; i++) { key = keys[i]; fn.call(null, obj[key], key, obj); } } } function findKey(obj, key) { if (isBuffer(obj)){ return null; } key = key.toLowerCase(); const keys = Object.keys(obj); let i = keys.length; let _key; while (i-- > 0) { _key = keys[i]; if (key === _key.toLowerCase()) { return _key; } } return null; } const _global = (() => { /*eslint no-undef:0*/ if (typeof globalThis !== "undefined") return globalThis; return typeof self !== "undefined" ? self : (typeof window !== 'undefined' ? window : global) })(); const isContextDefined = (context) => !isUndefined(context) && context !== _global; /** * Accepts varargs expecting each argument to be an object, then * immutably merges the properties of each object and returns result. * * When multiple objects contain the same key the later object in * the arguments list will take precedence. * * Example: * * ```js * var result = merge({foo: 123}, {foo: 456}); * console.log(result.foo); // outputs 456 * ``` * * @param {Object} obj1 Object to merge * * @returns {Object} Result of all merge properties */ function merge(/* obj1, obj2, obj3, ... */) { const {caseless} = isContextDefined(this) && this || {}; const result = {}; const assignValue = (val, key) => { const targetKey = caseless && findKey(result, key) || key; if (isPlainObject(result[targetKey]) && isPlainObject(val)) { result[targetKey] = merge(result[targetKey], val); } else if (isPlainObject(val)) { result[targetKey] = merge({}, val); } else if (isArray(val)) { result[targetKey] = val.slice(); } else { result[targetKey] = val; } }; for (let i = 0, l = arguments.length; i < l; i++) { arguments[i] && forEach(arguments[i], assignValue); } return result; } /** * Extends object a by mutably adding to it the properties of object b. * * @param {Object} a The object to be extended * @param {Object} b The object to copy properties from * @param {Object} thisArg The object to bind function to * * @param {Boolean} [allOwnKeys] * @returns {Object} The resulting value of object a */ const extend = (a, b, thisArg, {allOwnKeys}= {}) => { forEach(b, (val, key) => { if (thisArg && isFunction(val)) { a[key] = bind(val, thisArg); } else { a[key] = val; } }, {allOwnKeys}); return a; }; /** * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) * * @param {string} content with BOM * * @returns {string} content value without BOM */ const stripBOM = (content) => { if (content.charCodeAt(0) === 0xFEFF) { content = content.slice(1); } return content; }; /** * Inherit the prototype methods from one constructor into another * @param {function} constructor * @param {function} superConstructor * @param {object} [props] * @param {object} [descriptors] * * @returns {void} */ const inherits = (constructor, superConstructor, props, descriptors) => { constructor.prototype = Object.create(superConstructor.prototype, descriptors); constructor.prototype.constructor = constructor; Object.defineProperty(constructor, 'super', { value: superConstructor.prototype }); props && Object.assign(constructor.prototype, props); }; /** * Resolve object with deep prototype chain to a flat object * @param {Object} sourceObj source object * @param {Object} [destObj] * @param {Function|Boolean} [filter] * @param {Function} [propFilter] * * @returns {Object} */ const toFlatObject = (sourceObj, destObj, filter, propFilter) => { let props; let i; let prop; const merged = {}; destObj = destObj || {}; // eslint-disable-next-line no-eq-null,eqeqeq if (sourceObj == null) return destObj; do { props = Object.getOwnPropertyNames(sourceObj); i = props.length; while (i-- > 0) { prop = props[i]; if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) { destObj[prop] = sourceObj[prop]; merged[prop] = true; } } sourceObj = filter !== false && getPrototypeOf(sourceObj); } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype); return destObj; }; /** * Determines whether a string ends with the characters of a specified string * * @param {String} str * @param {String} searchString * @param {Number} [position= 0] * * @returns {boolean} */ const endsWith = (str, searchString, position) => { str = String(str); if (position === undefined || position > str.length) { position = str.length; } position -= searchString.length; const lastIndex = str.indexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; }; /** * Returns new array from array like object or null if failed * * @param {*} [thing] * * @returns {?Array} */ const toArray = (thing) => { if (!thing) return null; if (isArray(thing)) return thing; let i = thing.length; if (!isNumber(i)) return null; const arr = new Array(i); while (i-- > 0) { arr[i] = thing[i]; } return arr; }; /** * Checking if the Uint8Array exists and if it does, it returns a function that checks if the * thing passed in is an instance of Uint8Array * * @param {TypedArray} * * @returns {Array} */ // eslint-disable-next-line func-names const isTypedArray = (TypedArray => { // eslint-disable-next-line func-names return thing => { return TypedArray && thing instanceof TypedArray; }; })(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array)); /** * For each entry in the object, call the function with the key and value. * * @param {Object} obj - The object to iterate over. * @param {Function} fn - The function to call for each entry. * * @returns {void} */ const forEachEntry = (obj, fn) => { const generator = obj && obj[iterator]; const _iterator = generator.call(obj); let result; while ((result = _iterator.next()) && !result.done) { const pair = result.value; fn.call(obj, pair[0], pair[1]); } }; /** * It takes a regular expression and a string, and returns an array of all the matches * * @param {string} regExp - The regular expression to match against. * @param {string} str - The string to search. * * @returns {Array} */ const matchAll = (regExp, str) => { let matches; const arr = []; while ((matches = regExp.exec(str)) !== null) { arr.push(matches); } return arr; }; /* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */ const isHTMLForm = kindOfTest('HTMLFormElement'); const toCamelCase = str => { return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) { return p1.toUpperCase() + p2; } ); }; /* Creating a function that will check if an object has a property. */ const hasOwnProperty = (({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call(obj, prop))(Object.prototype); /** * Determine if a value is a RegExp object * * @param {*} val The value to test * * @returns {boolean} True if value is a RegExp object, otherwise false */ const isRegExp = kindOfTest('RegExp'); const reduceDescriptors = (obj, reducer) => { const descriptors = Object.getOwnPropertyDescriptors(obj); const reducedDescriptors = {}; forEach(descriptors, (descriptor, name) => { let ret; if ((ret = reducer(descriptor, name, obj)) !== false) { reducedDescriptors[name] = ret || descriptor; } }); Object.defineProperties(obj, reducedDescriptors); }; /** * Makes all methods read-only * @param {Object} obj */ const freezeMethods = (obj) => { reduceDescriptors(obj, (descriptor, name) => { // skip restricted props in strict mode if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { return false; } const value = obj[name]; if (!isFunction(value)) return; descriptor.enumerable = false; if ('writable' in descriptor) { descriptor.writable = false; return; } if (!descriptor.set) { descriptor.set = () => { throw Error('Can not rewrite read-only method \'' + name + '\''); }; } }); }; const toObjectSet = (arrayOrString, delimiter) => { const obj = {}; const define = (arr) => { arr.forEach(value => { obj[value] = true; }); }; isArray(arrayOrString) ? define(arrayOrString) : define(String(arrayOrString).split(delimiter)); return obj; }; const noop = () => {}; const toFiniteNumber = (value, defaultValue) => { return value != null && Number.isFinite(value = +value) ? value : defaultValue; }; /** * If the thing is a FormData object, return true, otherwise return false. * * @param {unknown} thing - The thing to check. * * @returns {boolean} */ function isSpecCompliantForm(thing) { return !!(thing && isFunction(thing.append) && thing[toStringTag] === 'FormData' && thing[iterator]); } const toJSONObject = (obj) => { const stack = new Array(10); const visit = (source, i) => { if (isObject$1(source)) { if (stack.indexOf(source) >= 0) { return; } //Buffer check if (isBuffer(source)) { return source; } if(!('toJSON' in source)) { stack[i] = source; const target = isArray(source) ? [] : {}; forEach(source, (value, key) => { const reducedValue = visit(value, i + 1); !isUndefined(reducedValue) && (target[key] = reducedValue); }); stack[i] = undefined; return target; } } return source; }; return visit(obj, 0); }; const isAsyncFn = kindOfTest('AsyncFunction'); const isThenable = (thing) => thing && (isObject$1(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); // original code // https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34 const _setImmediate = ((setImmediateSupported, postMessageSupported) => { if (setImmediateSupported) { return setImmediate; } return postMessageSupported ? ((token, callbacks) => { _global.addEventListener("message", ({source, data}) => { if (source === _global && data === token) { callbacks.length && callbacks.shift()(); } }, false); return (cb) => { callbacks.push(cb); _global.postMessage(token, "*"); } })(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb); })( typeof setImmediate === 'function', isFunction(_global.postMessage) ); const asap = typeof queueMicrotask !== 'undefined' ? queueMicrotask.bind(_global) : ( typeof process !== 'undefined' && process.nextTick || _setImmediate); // ********************* const isIterable = (thing) => thing != null && isFunction(thing[iterator]); const utils$1 = { isArray, isArrayBuffer, isBuffer, isFormData, isArrayBufferView, isString, isNumber, isBoolean, isObject: isObject$1, isPlainObject, isEmptyObject, isReadableStream, isRequest, isResponse, isHeaders, isUndefined, isDate, isFile, isBlob, isRegExp, isFunction, isStream, isURLSearchParams, isTypedArray, isFileList, forEach, merge, extend, trim, stripBOM, inherits, toFlatObject, kindOf, kindOfTest, endsWith, toArray, forEachEntry, matchAll, isHTMLForm, hasOwnProperty, hasOwnProp: hasOwnProperty, // an alias to avoid ESLint no-prototype-builtins detection reduceDescriptors, freezeMethods, toObjectSet, toCamelCase, noop, toFiniteNumber, findKey, global: _global, isContextDefined, isSpecCompliantForm, toJSONObject, isAsyncFn, isThenable, setImmediate: _setImmediate, asap, isIterable }; /** * Create an Error with the specified message, config, error code, request and response. * * @param {string} message The error message. * @param {string} [code] The error code (for example, 'ECONNABORTED'). * @param {Object} [config] The config. * @param {Object} [request] The request. * @param {Object} [response] The response. * * @returns {Error} The created error. */ function AxiosError(message, code, config, request, response) { Error.call(this); if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } else { this.stack = (new Error()).stack; } this.message = message; this.name = 'AxiosError'; code && (this.code = code); config && (this.config = config); request && (this.request = request); if (response) { this.response = response; this.status = response.status ? response.status : null; } } utils$1.inherits(AxiosError, Error, { toJSON: function toJSON() { return { // Standard message: this.message, name: this.name, // Microsoft description: this.description, number: this.number, // Mozilla fileName: this.fileName, lineNumber: this.lineNumber, columnNumber: this.columnNumber, stack: this.stack, // Axios config: utils$1.toJSONObject(this.config), code: this.code, status: this.status }; } }); const prototype$1 = AxiosError.prototype; const descriptors = {}; [ 'ERR_BAD_OPTION_VALUE', 'ERR_BAD_OPTION', 'ECONNABORTED', 'ETIMEDOUT', 'ERR_NETWORK', 'ERR_FR_TOO_MANY_REDIRECTS', 'ERR_DEPRECATED', 'ERR_BAD_RESPONSE', 'ERR_BAD_REQUEST', 'ERR_CANCELED', 'ERR_NOT_SUPPORT', 'ERR_INVALID_URL' // eslint-disable-next-line func-names ].forEach(code => { descriptors[code] = {value: code}; }); Object.defineProperties(AxiosError, descriptors); Object.defineProperty(prototype$1, 'isAxiosError', {value: true}); // eslint-disable-next-line func-names AxiosError.from = (error, code, config, request, response, customProps) => { const axiosError = Object.create(prototype$1); utils$1.toFlatObject(error, axiosError, function filter(obj) { return obj !== Error.prototype; }, prop => { return prop !== 'isAxiosError'; }); AxiosError.call(axiosError, error.message, code, config, request, response); axiosError.cause = error; axiosError.name = error.name; customProps && Object.assign(axiosError, customProps); return axiosError; }; // eslint-disable-next-line strict const httpAdapter = null; /** * Determines if the given thing is a array or js object. * * @param {string} thing - The object or array to be visited. * * @returns {boolean} */ function isVisitable(thing) { return utils$1.isPlainObject(thing) || utils$1.isArray(thing); } /** * It removes the brackets from the end of a string * * @param {string} key - The key of the parameter. * * @returns {string} the key without the brackets. */ function removeBrackets(key) { return utils$1.endsWith(key, '[]') ? key.slice(0, -2) : key; } /** * It takes a path, a key, and a boolean, and returns a string * * @param {string} path - The path to the current key. * @param {string} key - The key of the current object being iterated over. * @param {string} dots - If true, the key will be rendered with dots instead of brackets. * * @returns {string} The path to the current key. */ function renderKey(path, key, dots) { if (!path) return key; return path.concat(key).map(function each(token, i) { // eslint-disable-next-line no-param-reassign token = removeBrackets(token); return !dots && i ? '[' + token + ']' : token; }).join(dots ? '.' : ''); } /** * If the array is an array and none of its elements are visitable, then it's a flat array. * * @param {Array} arr - The array to check * * @returns {boolean} */ function isFlatArray(arr) { return utils$1.isArray(arr) && !arr.some(isVisitable); } const predicates = utils$1.toFlatObject(utils$1, {}, null, function filter(prop) { return /^is[A-Z]/.test(prop); }); /** * Convert a data object to FormData * * @param {Object} obj * @param {?Object} [formData] * @param {?Object} [options] * @param {Function} [options.visitor] * @param {Boolean} [options.metaTokens = true] * @param {Boolean} [options.dots = false] * @param {?Boolean} [options.indexes = false] * * @returns {Object} **/ /** * It converts an object into a FormData object * * @param {Object} obj - The object to convert to form data. * @param {string} formData - The FormData object to append to. * @param {Object} options * * @returns */ function toFormData(obj, formData, options) { if (!utils$1.isObject(obj)) { throw new TypeError('target must be an object'); } // eslint-disable-next-line no-param-reassign formData = formData || new (FormData)(); // eslint-disable-next-line no-param-reassign options = utils$1.toFlatObject(options, { metaTokens: true, dots: false, indexes: false }, false, function defined(option, source) { // eslint-disable-next-line no-eq-null,eqeqeq return !utils$1.isUndefined(source[option]); }); const metaTokens = options.metaTokens; // eslint-disable-next-line no-use-before-define const visitor = options.visitor || defaultVisitor; const dots = options.dots; const indexes = options.indexes; const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; const useBlob = _Blob && utils$1.isSpecCompliantForm(formData); if (!utils$1.isFunction(visitor)) { throw new TypeError('visitor must be a function'); } function convertValue(value) { if (value === null) return ''; if (utils$1.isDate(value)) { return value.toISOString(); } if (utils$1.isBoolean(value)) { return value.toString(); } if (!useBlob && utils$1.isBlob(value)) { throw new AxiosError('Blob is not supported. Use a Buffer instead.'); } if (utils$1.isArrayBuffer(value) || utils$1.isTypedArray(value)) { return useBlob && typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); } return value; } /** * Default visitor. * * @param {*} value * @param {String|Number} key * @param {Array} path * @this {FormData} * * @returns {boolean} return true to visit the each prop of the value recursively */ function defaultVisitor(value, key, path) { let arr = value; if (value && !path && typeof value === 'object') { if (utils$1.endsWith(key, '{}')) { // eslint-disable-next-line no-param-reassign key = metaTokens ? key : key.slice(0, -2); // eslint-disable-next-line no-param-reassign value = JSON.stringify(value); } else if ( (utils$1.isArray(value) && isFlatArray(value)) || ((utils$1.isFileList(value) || utils$1.endsWith(key, '[]')) && (arr = utils$1.toArray(value)) )) { // eslint-disable-next-line no-param-reassign key = removeBrackets(key); arr.forEach(function each(el, index) { !(utils$1.isUndefined(el) || el === null) && formData.append( // eslint-disable-next-line no-nested-ternary indexes === true ? renderKey([key], index, dots) : (indexes === null ? key : key + '[]'), convertValue(el) ); }); return false; } } if (isVisitable(value)) { return true; } formData.append(renderKey(path, key, dots), convertValue(value)); return false; } const stack = []; const exposedHelpers = Object.assign(predicates, { defaultVisitor, convertValue, isVisitable }); function build(value, path) { if (utils$1.isUndefined(value)) return; if (stack.indexOf(value) !== -1) { throw Error('Circular reference detected in ' + path.join('.')); } stack.push(value); utils$1.forEach(value, function each(el, key) { const result = !(utils$1.isUndefined(el) || el === null) && visitor.call( formData, el, utils$1.isString(key) ? key.trim() : key, path, exposedHelpers ); if (result === true) { build(el, path ? path.concat(key) : [key]); } }); stack.pop(); } if (!utils$1.isObject(obj)) { throw new TypeError('data must be an object'); } build(obj); return formData; } /** * It encodes a string by replacing all characters that are not in the unreserved set with * their percent-encoded equivalents * * @param {string} str - The string to encode. * * @returns {string} The encoded string. */ function encode$2(str) { const charMap = { '!': '%21', "'": '%27', '(': '%28', ')': '%29', '~': '%7E', '%20': '+', '%00': '\x00' }; return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) { return charMap[match]; }); } /** * It takes a params object and converts it to a FormData object * * @param {Object} params - The parameters to be converted to a FormData object. * @param {Object} options - The options object passed to the Axios constructor. * * @returns {void} */ function AxiosURLSearchParams(params, options) { this._pairs = []; params && toFormData(params, this, options); } const prototype = AxiosURLSearchParams.prototype; prototype.append = function append(name, value) { this._pairs.push([name, value]); }; prototype.toString = function toString(encoder) { const _encode = encoder ? function(value) { return encoder.call(this, value, encode$2); } : encode$2; return this._pairs.map(function each(pair) { return _encode(pair[0]) + '=' + _encode(pair[1]); }, '').join('&'); }; /** * It replaces all instances of the characters `:`, `$`, `,`, `+`, `[`, and `]` with their * URI encoded counterparts * * @param {string} val The value to be encoded. * * @returns {string} The encoded value. */ function encode$1(val) { return encodeURIComponent(val). replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). replace(/%20/g, '+'). replace(/%5B/gi, '['). replace(/%5D/gi, ']'); } /** * Build a URL by appending params to the end * * @param {string} url The base of the url (e.g., http://www.google.com) * @param {object} [params] The params to be appended * @param {?(object|Function)} options * * @returns {string} The formatted url */ function buildURL(url, params, options) { /*eslint no-param-reassign:0*/ if (!params) { return url; } const _encode = options && options.encode || encode$1; if (utils$1.isFunction(options)) { options = { serialize: options }; } const serializeFn = options && options.serialize; let serializedParams; if (serializeFn) { serializedParams = serializeFn(params, options); } else { serializedParams = utils$1.isURLSearchParams(params) ? params.toString() : new AxiosURLSearchParams(params, options).toString(_encode); } if (serializedParams) { const hashmarkIndex = url.indexOf("#"); if (hashmarkIndex !== -1) { url = url.slice(0, hashmarkIndex); } url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; } return url; } class InterceptorManager { constructor() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ use(fulfilled, rejected, options) { this.handlers.push({ fulfilled, rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; } /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` * * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise */ eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } } /** * Clear all interceptors from the stack * * @returns {void} */ clear() { if (this.handlers) { this.handlers = []; } } /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor * * @returns {void} */ forEach(fn) { utils$1.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); } } const transitionalDefaults = { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }; const URLSearchParams$1 = typeof URLSearchParams !== 'undefined' ? URLSearchParams : AxiosURLSearchParams; const FormData$1 = typeof FormData !== 'undefined' ? FormData : null; const Blob$1 = typeof Blob !== 'undefined' ? Blob : null; const platform$1 = { isBrowser: true, classes: { URLSearchParams: URLSearchParams$1, FormData: FormData$1, Blob: Blob$1 }, protocols: ['http', 'https', 'file', 'blob', 'url', 'data'] }; const hasBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined'; const _navigator = typeof navigator === 'object' && navigator || undefined; /** * Determine if we're running in a standard browser environment * * This allows axios to run in a web worker, and react-native. * Both environments support XMLHttpRequest, but not fully standard globals. * * web workers: * typeof window -> undefined * typeof document -> undefined * * react-native: * navigator.product -> 'ReactNative' * nativescript * navigator.product -> 'NativeScript' or 'NS' * * @returns {boolean} */ const hasStandardBrowserEnv = hasBrowserEnv && (!_navigator || ['ReactNative', 'NativeScript', 'NS'].indexOf(_navigator.product) < 0); /** * Determine if we're running in a standard browser webWorker environment * * Although the `isStandardBrowserEnv` method indicates that * `allows axios to run in a web worker`, the WebWorker will still be * filtered out due to its judgment standard * `typeof window !== 'undefined' && typeof document !== 'undefined'`. * This leads to a problem when axios post `FormData` in webWorker */ const hasStandardBrowserWebWorkerEnv = (() => { return ( typeof WorkerGlobalScope !== 'undefined' && // eslint-disable-next-line no-undef self instanceof WorkerGlobalScope && typeof self.importScripts === 'function' ); })(); const origin = hasBrowserEnv && window.location.href || 'http://localhost'; const utils = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, hasBrowserEnv, hasStandardBrowserEnv, hasStandardBrowserWebWorkerEnv, navigator: _navigator, origin }, Symbol.toStringTag, { value: 'Module' })); const platform = { ...utils, ...platform$1 }; function toURLEncodedForm(data, options) { return toFormData(data, new platform.classes.URLSearchParams(), { visitor: function(value, key, path, helpers) { if (platform.isNode && utils$1.isBuffer(value)) { this.append(key, value.toString('base64')); return false; } return helpers.defaultVisitor.apply(this, arguments); }, ...options }); } /** * It takes a string like `foo[x][y][z]` and returns an array like `['foo', 'x', 'y', 'z'] * * @param {string} name - The name of the property to get. * * @returns An array of strings. */ function parsePropPath(name) { // foo[x][y][z] // foo.x.y.z // foo-x-y-z // foo x y z return utils$1.matchAll(/\w+|\[(\w*)]/g, name).map(match => { return match[0] === '[]' ? '' : match[1] || match[0]; }); } /** * Convert an array to an object. * * @param {Array} arr - The array to convert to an object. * * @returns An object with the same keys and values as the array. */ function arrayToObject(arr) { const obj = {}; const keys = Object.keys(arr); let i; const len = keys.length; let key; for (i = 0; i < len; i++) { key = keys[i]; obj[key] = arr[key]; } return obj; } /** * It takes a FormData object and returns a JavaScript object * * @param {string} formData The FormData object to convert to JSON. * * @returns {Object | null} The converted object. */ function formDataToJSON(formData) { function buildPath(path, value, target, index) { let name = path[index++]; if (name === '__proto__') return true; const isNumericKey = Number.isFinite(+name); const isLast = index >= path.length; name = !name && utils$1.isArray(target) ? target.length : name; if (isLast) { if (utils$1.hasOwnProp(target, name)) { target[name] = [target[name], value]; } else { target[name] = value; } return !isNumericKey; } if (!target[name] || !utils$1.isObject(target[name])) { target[name] = []; } const result = buildPath(path, value, target[name], index); if (result && utils$1.isArray(target[name])) { target[name] = arrayToObject(target[name]); } return !isNumericKey; } if (utils$1.isFormData(formData) && utils$1.isFunction(formData.entries)) { const obj = {}; utils$1.forEachEntry(formData, (name, value) => { buildPath(parsePropPath(name), value, obj, 0); }); return obj; } return null; } /** * It takes a string, tries to parse it, and if it fails, it returns the stringified version * of the input * * @param {any} rawValue - The value to be stringified. * @param {Function} parser - A function that parses a string into a JavaScript object. * @param {Function} encoder - A function that takes a value and returns a string. * * @returns {string} A stringified version of the rawValue. */ function stringifySafely(rawValue, parser, encoder) { if (utils$1.isString(rawValue)) { try { (parser || JSON.parse)(rawValue); return utils$1.trim(rawValue); } catch (e) { if (e.name !== 'SyntaxError') { throw e; } } } return (0, JSON.stringify)(rawValue); } const defaults = { transitional: transitionalDefaults, adapter: ['xhr', 'http', 'fetch'], transformRequest: [function transformRequest(data, headers) { const contentType = headers.getContentType() || ''; const hasJSONContentType = contentType.indexOf('application/json') > -1; const isObjectPayload = utils$1.isObject(data); if (isObjectPayload && utils$1.isHTMLForm(data)) { data = new FormData(data); } const isFormData = utils$1.isFormData(data); if (isFormData) { return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data; } if (utils$1.isArrayBuffer(data) || utils$1.isBuffer(data) || utils$1.isStream(data) || utils$1.isFile(data) || utils$1.isBlob(data) || utils$1.isReadableStream(data) ) { return data; } if (utils$1.isArrayBufferView(data)) { return data.buffer; } if (utils$1.isURLSearchParams(data)) { headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false); return data.toString(); } let isFileList; if (isObjectPayload) { if (contentType.indexOf('application/x-www-form-urlencoded') > -1) { return toURLEncodedForm(data, this.formSerializer).toString(); } if ((isFileList = utils$1.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) { const _FormData = this.env && this.env.FormData; return toFormData( isFileList ? {'files[]': data} : data, _FormData && new _FormData(), this.formSerializer ); } } if (isObjectPayload || hasJSONContentType ) { headers.setContentType('application/json', false); return stringifySafely(data); } return data; }], transformResponse: [function transformResponse(data) { const transitional = this.transitional || defaults.transitional; const forcedJSONParsing = transitional && transitional.forcedJSONParsing; const JSONRequested = this.responseType === 'json'; if (utils$1.isResponse(data) || utils$1.isReadableStream(data)) { return data; } if (data && utils$1.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) { const silentJSONParsing = transitional && transitional.silentJSONParsing; const strictJSONParsing = !silentJSONParsing && JSONRequested; try { return JSON.parse(data); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response); } throw e; } } } return data; }], /** * A timeout in milliseconds to abort a request. If set to 0 (default) a * timeout is not created. */ timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, env: { FormData: platform.classes.FormData, Blob: platform.classes.Blob }, validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }, headers: { common: { 'Accept': 'application/json, text/plain, */*', 'Content-Type': undefined } } }; utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], (method) => { defaults.headers[method] = {}; }); // RawAxiosHeaders whose duplicates are ignored by node // c.f. https://nodejs.org/api/http.html#http_message_headers const ignoreDuplicateOf = utils$1.toObjectSet([ 'age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent' ]); /** * Parse headers into an object * * ``` * Date: Wed, 27 Aug 2014 08:58:49 GMT * Content-Type: application/json * Connection: keep-alive * Transfer-Encoding: chunked * ``` * * @param {String} rawHeaders Headers needing to be parsed * * @returns {Object} Headers parsed into an object */ const parseHeaders = rawHeaders => { const parsed = {}; let key; let val; let i; rawHeaders && rawHeaders.split('\n').forEach(function parser(line) { i = line.indexOf(':'); key = line.substring(0, i).trim().toLowerCase(); val = line.substring(i + 1).trim(); if (!key || (parsed[key] && ignoreDuplicateOf[key])) { return; } if (key === 'set-cookie') { if (parsed[key]) { parsed[key].push(val); } else { parsed[key] = [val]; } } else { parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; } }); return parsed; }; const $internals = Symbol('internals'); function normalizeHeader(header) { return header && String(header).trim().toLowerCase(); } function normalizeValue(value) { if (value === false || value == null) { return value; } return utils$1.isArray(value) ? value.map(normalizeValue) : String(value); } function parseTokens(str) { const tokens = Object.create(null); const tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g; let match; while ((match = tokensRE.exec(str))) { tokens[match[1]] = match[2]; } return tokens; } const isValidHeaderName = (str) => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim()); function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { if (utils$1.isFunction(filter)) { return filter.call(this, value, header); } if (isHeaderNameFilter) { value = header; } if (!utils$1.isString(value)) return; if (utils$1.isString(filter)) { return value.indexOf(filter) !== -1; } if (utils$1.isRegExp(filter)) { return filter.test(value); } } function formatHeader(header) { return header.trim() .toLowerCase().replace(/([a-z\d])(\w*)/g, (w, char, str) => { return char.toUpperCase() + str; }); } function buildAccessors(obj, header) { const accessorName = utils$1.toCamelCase(' ' + header); ['get', 'set', 'has'].forEach(methodName => { Object.defineProperty(obj, methodName + accessorName, { value: function(arg1, arg2, arg3) { return this[methodName].call(this, header, arg1, arg2, arg3); }, configurable: true }); }); } class AxiosHeaders { constructor(headers) { headers && this.set(headers); } set(header, valueOrRewrite, rewrite) { const self = this; function setHeader(_value, _header, _rewrite) { const lHeader = normalizeHeader(_header); if (!lHeader) { throw new Error('header name must be a non-empty string'); } const key = utils$1.findKey(self, lHeader); if(!key || self[key] === undefined || _rewrite === true || (_rewrite === undefined && self[key] !== false)) { self[key || _header] = normalizeValue(_value); } } const setHeaders = (headers, _rewrite) => utils$1.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite)); if (utils$1.isPlainObject(header) || header instanceof this.constructor) { setHeaders(header, valueOrRewrite); } else if(utils$1.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { setHeaders(parseHeaders(header), valueOrRewrite); } else if (utils$1.isObject(header) && utils$1.isIterable(header)) { let obj = {}, dest, key; for (const entry of header) { if (!utils$1.isArray(entry)) { throw TypeError('Object iterator must return a key-value pair'); } obj[key = entry[0]] = (dest = obj[key]) ? (utils$1.isArray(dest) ? [...dest, entry[1]] : [dest, entry[1]]) : entry[1]; } setHeaders(obj, valueOrRewrite); } else { header != null && setHeader(valueOrRewrite, header, rewrite); } return this; } get(header, parser) { header = normalizeHeader(header); if (header) { const key = utils$1.findKey(this, header); if (key) { const value = this[key]; if (!parser) { return value; } if (parser === true) { return parseTokens(value); } if (utils$1.isFunction(parser)) { return parser.call(this, value, key); } if (utils$1.isRegExp(parser)) { return parser.exec(value); } throw new TypeError('parser must be boolean|regexp|function'); } } } has(header, matcher) { header = normalizeHeader(header); if (header) { const key = utils$1.findKey(this, header); return !!(key && this[key] !== undefined && (!matcher || matchHeaderValue(this, this[key], key, matcher))); } return false; } delete(header, matcher) { const self = this; let deleted = false; function deleteHeader(_header) { _header = normalizeHeader(_header); if (_header) { const key = utils$1.findKey(self, _header); if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) { delete self[key]; deleted = true; } } } if (utils$1.isArray(header)) { header.forEach(deleteHeader); } else { deleteHeader(header); } return deleted; } clear(matcher) { const keys = Object.keys(this); let i = keys.length; let deleted = false; while (i--) { const key = keys[i]; if(!matcher || matchHeaderValue(this, this[key], key, matcher, true)) { delete this[key]; deleted = true; } } return deleted; } normalize(format) { const self = this; const headers = {}; utils$1.forEach(this, (value, header) => { const key = utils$1.findKey(headers, header); if (key) { self[key] = normalizeValue(value); delete self[header]; return; } const normalized = format ? formatHeader(header) : String(header).trim(); if (normalized !== header) { delete self[header]; } self[normalized] = normalizeValue(value); headers[normalized] = true; }); return this; } concat(...targets) { return this.constructor.concat(this, ...targets); } toJSON(asStrings) { const obj = Object.create(null); utils$1.forEach(this, (value, header) => { value != null && value !== false && (obj[header] = asStrings && utils$1.isArray(value) ? value.join(', ') : value); }); return obj; } [Symbol.iterator]() { return Object.entries(this.toJSON())[Symbol.iterator](); } toString() { return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); } getSetCookie() { return this.get("set-cookie") || []; } get [Symbol.toStringTag]() { return 'AxiosHeaders'; } static from(thing) { return thing instanceof this ? thing : new this(thing); } static concat(first, ...targets) { const computed = new this(first); targets.forEach((target) => computed.set(target)); return computed; } static accessor(header) { const internals = this[$internals] = (this[$internals] = { accessors: {} }); const accessors = internals.accessors; const prototype = this.prototype; function defineAccessor(_header) { const lHeader = normalizeHeader(_header); if (!accessors[lHeader]) { buildAccessors(prototype, _header); accessors[lHeader] = true; } } utils$1.isArray(header) ? header.forEach(defineAccessor) : defineAccessor(header); return this; } } AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent', 'Authorization']); // reserved names hotfix utils$1.reduceDescriptors(AxiosHeaders.prototype, ({value}, key) => { let mapped = key[0].toUpperCase() + key.slice(1); // map `set` => `Set` return { get: () => value, set(headerValue) { this[mapped] = headerValue; } } }); utils$1.freezeMethods(AxiosHeaders); /** * Transform the data for a request or a response * * @param {Array|Function} fns A single function or Array of functions * @param {?Object} response The response object * * @returns {*} The resulting transformed data */ function transformData(fns, response) { const config = this || defaults; const context = response || config; const headers = AxiosHeaders.from(context.headers); let data = context.data; utils$1.forEach(fns, function transform(fn) { data = fn.call(config, data, headers.normalize(), response ? response.status : undefined); }); headers.normalize(); return data; } function isCancel(value) { return !!(value && value.__CANCEL__); } /** * A `CanceledError` is an object that is thrown when an operation is canceled. * * @param {string=} message The message. * @param {Object=} config The config. * @param {Object=} request The request. * * @returns {CanceledError} The created error. */ function CanceledError(message, config, request) { // eslint-disable-next-line no-eq-null,eqeqeq AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED, config, request); this.name = 'CanceledError'; } utils$1.inherits(CanceledError, AxiosError, { __CANCEL__: true }); /** * Resolve or reject a Promise based on response status. * * @param {Function} resolve A function that resolves the promise. * @param {Function} reject A function that rejects the promise. * @param {object} response The response. * * @returns {object} The response. */ function settle(resolve, reject, response) { const validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject(new AxiosError( 'Request failed with status code ' + response.status, [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response )); } } function parseProtocol(url) { const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); return match && match[1] || ''; } /** * Calculate data maxRate * @param {Number} [samplesCount= 10] * @param {Number} [min= 1000] * @returns {Function} */ function speedometer(samplesCount, min) { samplesCount = samplesCount || 10; const bytes = new Array(samplesCount); const timestamps = new Array(samplesCount); let head = 0; let tail = 0; let firstSampleTS; min = min !== undefined ? min : 1000; return function push(chunkLength) { const now = Date.now(); const startedAt = timestamps[tail]; if (!firstSampleTS) { firstSampleTS = now; } bytes[head] = chunkLength; timestamps[head] = now; let i = tail; let bytesCount = 0; while (i !== head) { bytesCount += bytes[i++]; i = i % samplesCount; } head = (head + 1) % samplesCount; if (head === tail) { tail = (tail + 1) % samplesCount; } if (now - firstSampleTS < min) { return; } const passed = startedAt && now - startedAt; return passed ? Math.round(bytesCount * 1000 / passed) : undefined; }; } /** * Throttle decorator * @param {Function} fn * @param {Number} freq * @return {Function} */ function throttle(fn, freq) { let timestamp = 0; let threshold = 1000 / freq; let lastArgs; let timer; const invoke = (args, now = Date.now()) => { timestamp = now; lastArgs = null; if (timer) { clearTimeout(timer); timer = null; } fn(...args); }; const throttled = (...args) => { const now = Date.now(); const passed = now - timestamp; if ( passed >= threshold) { invoke(args, now); } else { lastArgs = args; if (!timer) { timer = setTimeout(() => { timer = null; invoke(lastArgs); }, threshold - passed); } } }; const flush = () => lastArgs && invoke(lastArgs); return [throttled, flush]; } const progressEventReducer = (listener, isDownloadStream, freq = 3) => { let bytesNotified = 0; const _speedometer = speedometer(50, 250); return throttle(e => { const loaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; const progressBytes = loaded - bytesNotified; const rate = _speedometer(progressBytes); const inRange = loaded <= total; bytesNotified = loaded; const data = { loaded, total, progress: total ? (loaded / total) : undefined, bytes: progressBytes, rate: rate ? rate : undefined, estimated: rate && total && inRange ? (total - loaded) / rate : undefined, event: e, lengthComputable: total != null, [isDownloadStream ? 'download' : 'upload']: true }; listener(data); }, freq); }; const progressEventDecorator = (total, throttled) => { const lengthComputable = total != null; return [(loaded) => throttled[0]({ lengthComputable, total, loaded }), throttled[1]]; }; const asyncDecorator = (fn) => (...args) => utils$1.asap(() => fn(...args)); const isURLSameOrigin = platform.hasStandardBrowserEnv ? ((origin, isMSIE) => (url) => { url = new URL(url, platform.origin); return ( origin.protocol === url.protocol && origin.host === url.host && (isMSIE || origin.port === url.port) ); })( new URL(platform.origin), platform.navigator && /(msie|trident)/i.test(platform.navigator.userAgent) ) : () => true; const cookies = platform.hasStandardBrowserEnv ? // Standard browser envs support document.cookie { write(name, value, expires, path, domain, secure) { const cookie = [name + '=' + encodeURIComponent(value)]; utils$1.isNumber(expires) && cookie.push('expires=' + new Date(expires).toGMTString()); utils$1.isString(path) && cookie.push('path=' + path); utils$1.isString(domain) && cookie.push('domain=' + domain); secure === true && cookie.push('secure'); document.cookie = cookie.join('; '); }, read(name) { const match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); return (match ? decodeURIComponent(match[3]) : null); }, remove(name) { this.write(name, '', Date.now() - 86400000); } } : // Non-standard browser env (web workers, react-native) lack needed support. { write() {}, read() { return null; }, remove() {} }; /** * Determines whether the specified URL is absolute * * @param {string} url The URL to test * * @returns {boolean} True if the specified URL is absolute, otherwise false */ function isAbsoluteURL(url) { // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed // by any combination of letters, digits, plus, period, or hyphen. return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url); } /** * Creates a new URL by combining the specified URLs * * @param {string} baseURL The base URL * @param {string} relativeURL The relative URL * * @returns {string} The combined URL */ function combineURLs(baseURL, relativeURL) { return relativeURL ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; } /** * Creates a new URL by combining the baseURL with the requestedURL, * only when the requestedURL is not already an absolute URL. * If the requestURL is absolute, this function returns the requestedURL untouched. * * @param {string} baseURL The base URL * @param {string} requestedURL Absolute or relative URL to combine * * @returns {string} The combined full path */ function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { let isRelativeUrl = !isAbsoluteURL(requestedURL); if (baseURL && (isRelativeUrl || allowAbsoluteUrls == false)) { return combineURLs(baseURL, requestedURL); } return requestedURL; } const headersToObject = (thing) => thing instanceof AxiosHeaders ? { ...thing } : thing; /** * Config-specific merge-function which creates a new config-object * by merging two configuration objects together. * * @param {Object} config1 * @param {Object} config2 * * @returns {Object} New object resulting from merging config2 to config1 */ function mergeConfig(config1, config2) { // eslint-disable-next-line no-param-reassign config2 = config2 || {}; const config = {}; function getMergedValue(target, source, prop, caseless) { if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) { return utils$1.merge.call({caseless}, target, source); } else if (utils$1.isPlainObject(source)) { return utils$1.merge({}, source); } else if (utils$1.isArray(source)) { return source.slice(); } return source; } // eslint-disable-next-line consistent-return function mergeDeepProperties(a, b, prop , caseless) { if (!utils$1.isUndefined(b)) { return getMergedValue(a, b, prop , caseless); } else if (!utils$1.isUndefined(a)) { return getMergedValue(undefined, a, prop , caseless); } } // eslint-disable-next-line consistent-return function valueFromConfig2(a, b) { if (!utils$1.isUndefined(b)) { return getMergedValue(undefined, b); } } // eslint-disable-next-line consistent-return function defaultToConfig2(a, b) { if (!utils$1.isUndefined(b)) { return getMergedValue(undefined, b); } else if (!utils$1.isUndefined(a)) { return getMergedValue(undefined, a); } } // eslint-disable-next-line consistent-return function mergeDirectKeys(a, b, prop) { if (prop in config2) { return getMergedValue(a, b); } else if (prop in config1) { return getMergedValue(undefined, a); } } const mergeMap = { url: valueFromConfig2, method: valueFromConfig2, data: valueFromConfig2, baseURL: defaultToConfig2, transformRequest: defaultToConfig2, transformResponse: defaultToConfig2, paramsSerializer: defaultToConfig2, timeout: defaultToConfig2, timeoutMessage: defaultToConfig2, withCredentials: defaultToConfig2, withXSRFToken: defaultToConfig2, adapter: defaultToConfig2, responseType: defaultToConfig2, xsrfCookieName: defaultToConfig2, xsrfHeaderName: defaultToConfig2, onUploadProgress: defaultToConfig2, onDownloadProgress: defaultToConfig2, decompress: defaultToConfig2, maxContentLength: defaultToConfig2, maxBodyLength: defaultToConfig2, beforeRedirect: defaultToConfig2, transport: defaultToConfig2, httpAgent: defaultToConfig2, httpsAgent: defaultToConfig2, cancelToken: defaultToConfig2, socketPath: defaultToConfig2, responseEncoding: defaultToConfig2, validateStatus: mergeDirectKeys, headers: (a, b , prop) => mergeDeepProperties(headersToObject(a), headersToObject(b),prop, true) }; utils$1.forEach(Object.keys({...config1, ...config2}), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils$1.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); }); return config; } const resolveConfig = (config) => { const newConfig = mergeConfig({}, config); let {data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth} = newConfig; newConfig.headers = headers = AxiosHeaders.from(headers); newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config.params, config.paramsSerializer); // HTTP basic authentication if (auth) { headers.set('Authorization', 'Basic ' + btoa((auth.username || '') + ':' + (auth.password ? unescape(encodeURIComponent(auth.password)) : '')) ); } let contentType; if (utils$1.isFormData(data)) { if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) { headers.setContentType(undefined); // Let the browser set it } else if ((contentType = headers.getContentType()) !== false) { // fix semicolon duplication issue for ReactNative FormData implementation const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : []; headers.setContentType([type || 'multipart/form-data', ...tokens].join('; ')); } } // Add xsrf header // This is only done if running in a standard browser environment. // Specifically not if we're in a web worker, or react-native. if (platform.hasStandardBrowserEnv) { withXSRFToken && utils$1.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig)); if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(newConfig.url))) { // Add xsrf header const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName); if (xsrfValue) { headers.set(xsrfHeaderName, xsrfValue); } } } return newConfig; }; const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined'; const xhrAdapter = isXHRAdapterSupported && function (config) { return new Promise(function dispatchXhrRequest(resolve, reject) { const _config = resolveConfig(config); let requestData = _config.data; const requestHeaders = AxiosHeaders.from(_config.headers).normalize(); let {responseType, onUploadProgress, onDownloadProgress} = _config; let onCanceled; let uploadThrottled, downloadThrottled; let flushUpload, flushDownload; function done() { flushUpload && flushUpload(); // flush events flushDownload && flushDownload(); // flush events _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled); _config.signal && _config.signal.removeEventListener('abort', onCanceled); } let request = new XMLHttpRequest(); request.open(_config.method.toUpperCase(), _config.url, true); // Set the request timeout in MS request.timeout = _config.timeout; function onloadend() { if (!request) { return; } // Prepare the response const responseHeaders = AxiosHeaders.from( 'getAllResponseHeaders' in request && request.getAllResponseHeaders() ); const responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; const response = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config, request }; settle(function _resolve(value) { resolve(value); done(); }, function _reject(err) { reject(err); done(); }, response); // Clean up request request = null; } if ('onloadend' in request) { // Use onloadend if available request.onloadend = onloadend; } else { // Listen for ready state to emulate onloadend request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } // The request errored out and we didn't get a response, this will be // handled by onerror instead // With one exception: request that using file: protocol, most browsers // will return status as 0 even though it's a successful request if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // readystate handler is calling before onerror or ontimeout handlers, // so we should call onloadend on the next 'tick' setTimeout(onloadend); }; } // Handle browser request cancellation (as opposed to a manual cancellation) request.onabort = function handleAbort() { if (!request) { return; } reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); // Clean up request request = null; }; // Handle low level network errors request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); // Clean up request request = null; }; // Handle timeout request.ontimeout = function handleTimeout() { let timeoutErrorMessage = _config.timeout ? 'timeout of ' + _config.timeout + 'ms exceeded' : 'timeout exceeded'; const transitional = _config.transitional || transitionalDefaults; if (_config.timeoutErrorMessage) { timeoutErrorMessage = _config.timeoutErrorMessage; } reject(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); // Clean up request request = null; }; // Remove Content-Type if data is undefined requestData === undefined && requestHeaders.setContentType(null); // Add headers to the request if ('setRequestHeader' in request) { utils$1.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) { request.setRequestHeader(key, val); }); } // Add withCredentials to request if needed if (!utils$1.isUndefined(_config.withCredentials)) { request.withCredentials = !!_config.withCredentials; } // Add responseType to request if needed if (responseType && responseType !== 'json') { request.responseType = _config.responseType; } // Handle progress if needed if (onDownloadProgress) { ([downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true)); request.addEventListener('progress', downloadThrottled); } // Not all browsers support upload events if (onUploadProgress && request.upload) { ([uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress)); request.upload.addEventListener('progress', uploadThrottled); request.upload.addEventListener('loadend', flushUpload); } if (_config.cancelToken || _config.signal) { // Handle cancellation // eslint-disable-next-line func-names onCanceled = cancel => { if (!request) { return; } reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel); request.abort(); request = null; }; _config.cancelToken && _config.cancelToken.subscribe(onCanceled); if (_config.signal) { _config.signal.aborted ? onCanceled() : _config.signal.addEventListener('abort', onCanceled); } } const protocol = parseProtocol(_config.url); if (protocol && platform.protocols.indexOf(protocol) === -1) { reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); return; } // Send the request request.send(requestData || null); }); }; const composeSignals = (signals, timeout) => { const {length} = (signals = signals ? signals.filter(Boolean) : []); if (timeout || length) { let controller = new AbortController(); let aborted; const onabort = function (reason) { if (!aborted) { aborted = true; unsubscribe(); const err = reason instanceof Error ? reason : this.reason; controller.abort(err instanceof AxiosError ? err : new CanceledError(err instanceof Error ? err.message : err)); } }; let timer = timeout && setTimeout(() => { timer = null; onabort(new AxiosError(`timeout ${timeout} of ms exceeded`, AxiosError.ETIMEDOUT)); }, timeout); const unsubscribe = () => { if (signals) { timer && clearTimeout(timer); timer = null; signals.forEach(signal => { signal.unsubscribe ? signal.unsubscribe(onabort) : signal.removeEventListener('abort', onabort); }); signals = null; } }; signals.forEach((signal) => signal.addEventListener('abort', onabort)); const {signal} = controller; signal.unsubscribe = () => utils$1.asap(unsubscribe); return signal; } }; const streamChunk = function* (chunk, chunkSize) { let len = chunk.byteLength; if (len < chunkSize) { yield chunk; return; } let pos = 0; let end; while (pos < len) { end = pos + chunkSize; yield chunk.slice(pos, end); pos = end; } }; const readBytes = async function* (iterable, chunkSize) { for await (const chunk of readStream(iterable)) { yield* streamChunk(chunk, chunkSize); } }; const readStream = async function* (stream) { if (stream[Symbol.asyncIterator]) { yield* stream; return; } const reader = stream.getReader(); try { for (;;) { const {done, value} = await reader.read(); if (done) { break; } yield value; } } finally { await reader.cancel(); } }; const trackStream = (stream, chunkSize, onProgress, onFinish) => { const iterator = readBytes(stream, chunkSize); let bytes = 0; let done; let _onFinish = (e) => { if (!done) { done = true; onFinish && onFinish(e); } }; return new ReadableStream({ async pull(controller) { try { const {done, value} = await iterator.next(); if (done) { _onFinish(); controller.close(); return; } let len = value.byteLength; if (onProgress) { let loadedBytes = bytes += len; onProgress(loadedBytes); } controller.enqueue(new Uint8Array(value)); } catch (err) { _onFinish(err); throw err; } }, cancel(reason) { _onFinish(reason); return iterator.return(); } }, { highWaterMark: 2 }) }; const isFetchSupported = typeof fetch === 'function' && typeof Request === 'function' && typeof Response === 'function'; const isReadableStreamSupported = isFetchSupported && typeof ReadableStream === 'function'; // used only inside the fetch adapter const encodeText = isFetchSupported && (typeof TextEncoder === 'function' ? ((encoder) => (str) => encoder.encode(str))(new TextEncoder()) : async (str) => new Uint8Array(await new Response(str).arrayBuffer()) ); const test = (fn, ...args) => { try { return !!fn(...args); } catch (e) { return false } }; const supportsRequestStream = isReadableStreamSupported && test(() => { let duplexAccessed = false; const hasContentType = new Request(platform.origin, { body: new ReadableStream(), method: 'POST', get duplex() { duplexAccessed = true; return 'half'; }, }).headers.has('Content-Type'); return duplexAccessed && !hasContentType; }); const DEFAULT_CHUNK_SIZE = 64 * 1024; const supportsResponseStream = isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response('').body)); const resolvers = { stream: supportsResponseStream && ((res) => res.body) }; isFetchSupported && (((res) => { ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach(type => { !resolvers[type] && (resolvers[type] = utils$1.isFunction(res[type]) ? (res) => res[type]() : (_, config) => { throw new AxiosError(`Response type '${type}' is not supported`, AxiosError.ERR_NOT_SUPPORT, config); }); }); })(new Response)); const getBodyLength = async (body) => { if (body == null) { return 0; } if(utils$1.isBlob(body)) { return body.size; } if(utils$1.isSpecCompliantForm(body)) { const _request = new Request(platform.origin, { method: 'POST', body, }); return (await _request.arrayBuffer()).byteLength; } if(utils$1.isArrayBufferView(body) || utils$1.isArrayBuffer(body)) { return body.byteLength; } if(utils$1.isURLSearchParams(body)) { body = body + ''; } if(utils$1.isString(body)) { return (await encodeText(body)).byteLength; } }; const resolveBodyLength = async (headers, body) => { const length = utils$1.toFiniteNumber(headers.getContentLength()); return length == null ? getBodyLength(body) : length; }; const fetchAdapter = isFetchSupported && (async (config) => { let { url, method, data, signal, cancelToken, timeout, onDownloadProgress, onUploadProgress, responseType, headers, withCredentials = 'same-origin', fetchOptions } = resolveConfig(config); responseType = responseType ? (responseType + '').toLowerCase() : 'text'; let composedSignal = composeSignals([signal, cancelToken && cancelToken.toAbortSignal()], timeout); let request; const unsubscribe = composedSignal && composedSignal.unsubscribe && (() => { composedSignal.unsubscribe(); }); let requestContentLength; try { if ( onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head' && (requestContentLength = await resolveBodyLength(headers, data)) !== 0 ) { let _request = new Request(url, { method: 'POST', body: data, duplex: "half" }); let contentTypeHeader; if (utils$1.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) { headers.setContentType(contentTypeHeader); } if (_request.body) { const [onProgress, flush] = progressEventDecorator( requestContentLength, progressEventReducer(asyncDecorator(onUploadProgress)) ); data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush); } } if (!utils$1.isString(withCredentials)) { withCredentials = withCredentials ? 'include' : 'omit'; } // Cloudflare Workers throws when credentials are defined // see https://github.com/cloudflare/workerd/issues/902 const isCredentialsSupported = "credentials" in Request.prototype; request = new Request(url, { ...fetchOptions, signal: composedSignal, method: method.toUpperCase(), headers: headers.normalize().toJSON(), body: data, duplex: "half", credentials: isCredentialsSupported ? withCredentials : undefined }); let response = await fetch(request, fetchOptions); const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) { const options = {}; ['status', 'statusText', 'headers'].forEach(prop => { options[prop] = response[prop]; }); const responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); const [onProgress, flush] = onDownloadProgress && progressEventDecorator( responseContentLength, progressEventReducer(asyncDecorator(onDownloadProgress), true) ) || []; response = new Response( trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => { flush && flush(); unsubscribe && unsubscribe(); }), options ); } responseType = responseType || 'text'; let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); !isStreamResponse && unsubscribe && unsubscribe(); return await new Promise((resolve, reject) => { settle(resolve, reject, { data: responseData, headers: AxiosHeaders.from(response.headers), status: response.status, statusText: response.statusText, config, request }); }) } catch (err) { unsubscribe && unsubscribe(); if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) { throw Object.assign( new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request), { cause: err.cause || err } ) } throw AxiosError.from(err, err && err.code, config, request); } }); const knownAdapters = { http: httpAdapter, xhr: xhrAdapter, fetch: fetchAdapter }; utils$1.forEach(knownAdapters, (fn, value) => { if (fn) { try { Object.defineProperty(fn, 'name', {value}); } catch (e) { // eslint-disable-next-line no-empty } Object.defineProperty(fn, 'adapterName', {value}); } }); const renderReason = (reason) => `- ${reason}`; const isResolvedHandle = (adapter) => utils$1.isFunction(adapter) || adapter === null || adapter === false; const adapters = { getAdapter: (adapters) => { adapters = utils$1.isArray(adapters) ? adapters : [adapters]; const {length} = adapters; let nameOrAdapter; let adapter; const rejectedReasons = {}; for (let i = 0; i < length; i++) { nameOrAdapter = adapters[i]; let id; adapter = nameOrAdapter; if (!isResolvedHandle(nameOrAdapter)) { adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()]; if (adapter === undefined) { throw new AxiosError(`Unknown adapter '${id}'`); } } if (adapter) { break; } rejectedReasons[id || '#' + i] = adapter; } if (!adapter) { const reasons = Object.entries(rejectedReasons) .map(([id, state]) => `adapter ${id} ` + (state === false ? 'is not supported by the environment' : 'is not available in the build') ); let s = length ? (reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) : 'as no adapter specified'; throw new AxiosError( `There is no suitable adapter to dispatch the request ` + s, 'ERR_NOT_SUPPORT' ); } return adapter; }, adapters: knownAdapters }; /** * Throws a `CanceledError` if cancellation has been requested. * * @param {Object} config The config that is to be used for the request * * @returns {void} */ function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } if (config.signal && config.signal.aborted) { throw new CanceledError(null, config); } } /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * * @returns {Promise} The Promise to be fulfilled */ function dispatchRequest(config) { throwIfCancellationRequested(config); config.headers = AxiosHeaders.from(config.headers); // Transform request data config.data = transformData.call( config, config.transformRequest ); if (['post', 'put', 'patch'].indexOf(config.method) !== -1) { config.headers.setContentType('application/x-www-form-urlencoded', false); } const adapter = adapters.getAdapter(config.adapter || defaults.adapter); return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // Transform response data response.data = transformData.call( config, config.transformResponse, response ); response.headers = AxiosHeaders.from(response.headers); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // Transform response data if (reason && reason.response) { reason.response.data = transformData.call( config, config.transformResponse, reason.response ); reason.response.headers = AxiosHeaders.from(reason.response.headers); } } return Promise.reject(reason); }); } const VERSION = "1.11.0"; const validators$1 = {}; // eslint-disable-next-line func-names ['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach((type, i) => { validators$1[type] = function validator(thing) { return typeof thing === type || 'a' + (i < 1 ? 'n ' : ' ') + type; }; }); const deprecatedWarnings = {}; /** * Transitional option validator * * @param {function|boolean?} validator - set to false if the transitional option has been removed * @param {string?} version - deprecated version / removed since version * @param {string?} message - some message with additional info * * @returns {function} */ validators$1.transitional = function transitional(validator, version, message) { function formatMessage(opt, desc) { return '[Axios v' + VERSION + '] Transitional option \'' + opt + '\'' + desc + (message ? '. ' + message : ''); } // eslint-disable-next-line func-names return (value, opt, opts) => { if (validator === false) { throw new AxiosError( formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')), AxiosError.ERR_DEPRECATED ); } if (version && !deprecatedWarnings[opt]) { deprecatedWarnings[opt] = true; // eslint-disable-next-line no-console console.warn( formatMessage( opt, ' has been deprecated since v' + version + ' and will be removed in the near future' ) ); } return validator ? validator(value, opt, opts) : true; }; }; validators$1.spelling = function spelling(correctSpelling) { return (value, opt) => { // eslint-disable-next-line no-console console.warn(`${opt} is likely a misspelling of ${correctSpelling}`); return true; } }; /** * Assert object's properties type * * @param {object} options * @param {object} schema * @param {boolean?} allowUnknown * * @returns {object} */ function assertOptions(options, schema, allowUnknown) { if (typeof options !== 'object') { throw new AxiosError('options must be an object', AxiosError.ERR_BAD_OPTION_VALUE); } const keys = Object.keys(options); let i = keys.length; while (i-- > 0) { const opt = keys[i]; const validator = schema[opt]; if (validator) { const value = options[opt]; const result = value === undefined || validator(value, opt, options); if (result !== true) { throw new AxiosError('option ' + opt + ' must be ' + result, AxiosError.ERR_BAD_OPTION_VALUE); } continue; } if (allowUnknown !== true) { throw new AxiosError('Unknown option ' + opt, AxiosError.ERR_BAD_OPTION); } } } const validator = { assertOptions, validators: validators$1 }; const validators = validator.validators; /** * Create a new instance of Axios * * @param {Object} instanceConfig The default config for the instance * * @return {Axios} A new instance of Axios */ class Axios { constructor(instanceConfig) { this.defaults = instanceConfig || {}; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } /** * Dispatch a request * * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults) * @param {?Object} config * * @returns {Promise} The Promise to be fulfilled */ async request(configOrUrl, config) { try { return await this._request(configOrUrl, config); } catch (err) { if (err instanceof Error) { let dummy = {}; Error.captureStackTrace ? Error.captureStackTrace(dummy) : (dummy = new Error()); // slice off the Error: ... line const stack = dummy.stack ? dummy.stack.replace(/^.+\n/, '') : ''; try { if (!err.stack) { err.stack = stack; // match without the 2 top stack lines } else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { err.stack += '\n' + stack; } } catch (e) { // ignore the case where "stack" is an un-writable property } } throw err; } } _request(configOrUrl, config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof configOrUrl === 'string') { config = config || {}; config.url = configOrUrl; } else { config = configOrUrl || {}; } config = mergeConfig(this.defaults, config); const {transitional, paramsSerializer, headers} = config; if (transitional !== undefined) { validator.assertOptions(transitional, { silentJSONParsing: validators.transitional(validators.boolean), forcedJSONParsing: validators.transitional(validators.boolean), clarifyTimeoutError: validators.transitional(validators.boolean) }, false); } if (paramsSerializer != null) { if (utils$1.isFunction(paramsSerializer)) { config.paramsSerializer = { serialize: paramsSerializer }; } else { validator.assertOptions(paramsSerializer, { encode: validators.function, serialize: validators.function }, true); } } // Set config.allowAbsoluteUrls if (config.allowAbsoluteUrls !== undefined) ; else if (this.defaults.allowAbsoluteUrls !== undefined) { config.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls; } else { config.allowAbsoluteUrls = true; } validator.assertOptions(config, { baseUrl: validators.spelling('baseURL'), withXsrfToken: validators.spelling('withXSRFToken') }, true); // Set config.method config.method = (config.method || this.defaults.method || 'get').toLowerCase(); // Flatten headers let contextHeaders = headers && utils$1.merge( headers.common, headers[config.method] ); headers && utils$1.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], (method) => { delete headers[method]; } ); config.headers = AxiosHeaders.concat(contextHeaders, headers); // filter out skipped interceptors const requestInterceptorChain = []; let synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); const responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); let promise; let i = 0; let len; if (!synchronousRequestInterceptors) { const chain = [dispatchRequest.bind(this), undefined]; chain.unshift(...requestInterceptorChain); chain.push(...responseInterceptorChain); len = chain.length; promise = Promise.resolve(config); while (i < len) { promise = promise.then(chain[i++], chain[i++]); } return promise; } len = requestInterceptorChain.length; let newConfig = config; i = 0; while (i < len) { const onFulfilled = requestInterceptorChain[i++]; const onRejected = requestInterceptorChain[i++]; try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected.call(this, error); break; } } try { promise = dispatchRequest.call(this, newConfig); } catch (error) { return Promise.reject(error); } i = 0; len = responseInterceptorChain.length; while (i < len) { promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]); } return promise; } getUri(config) { config = mergeConfig(this.defaults, config); const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls); return buildURL(fullPath, config.params, config.paramsSerializer); } } // Provide aliases for supported request methods utils$1.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method, url, data: (config || {}).data })); }; }); utils$1.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { /*eslint func-names:0*/ function generateHTTPMethod(isForm) { return function httpMethod(url, data, config) { return this.request(mergeConfig(config || {}, { method, headers: isForm ? { 'Content-Type': 'multipart/form-data' } : {}, url, data })); }; } Axios.prototype[method] = generateHTTPMethod(); Axios.prototype[method + 'Form'] = generateHTTPMethod(true); }); /** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @param {Function} executor The executor function. * * @returns {CancelToken} */ class CancelToken { constructor(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } let resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); const token = this; // eslint-disable-next-line func-names this.promise.then(cancel => { if (!token._listeners) return; let i = token._listeners.length; while (i-- > 0) { token._listeners[i](cancel); } token._listeners = null; }); // eslint-disable-next-line func-names this.promise.then = onfulfilled => { let _resolve; // eslint-disable-next-line func-names const promise = new Promise(resolve => { token.subscribe(resolve); _resolve = resolve; }).then(onfulfilled); promise.cancel = function reject() { token.unsubscribe(_resolve); }; return promise; }; executor(function cancel(message, config, request) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new CanceledError(message, config, request); resolvePromise(token.reason); }); } /** * Throws a `CanceledError` if cancellation has been requested. */ throwIfRequested() { if (this.reason) { throw this.reason; } } /** * Subscribe to the cancel signal */ subscribe(listener) { if (this.reason) { listener(this.reason); return; } if (this._listeners) { this._listeners.push(listener); } else { this._listeners = [listener]; } } /** * Unsubscribe from the cancel signal */ unsubscribe(listener) { if (!this._listeners) { return; } const index = this._listeners.indexOf(listener); if (index !== -1) { this._listeners.splice(index, 1); } } toAbortSignal() { const controller = new AbortController(); const abort = (err) => { controller.abort(err); }; this.subscribe(abort); controller.signal.unsubscribe = () => this.unsubscribe(abort); return controller.signal; } /** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */ static source() { let cancel; const token = new CancelToken(function executor(c) { cancel = c; }); return { token, cancel }; } } /** * Syntactic sugar for invoking a function and expanding an array for arguments. * * Common use case would be to use `Function.prototype.apply`. * * ```js * function f(x, y, z) {} * var args = [1, 2, 3]; * f.apply(null, args); * ``` * * With `spread` this example can be re-written. * * ```js * spread(function(x, y, z) {})([1, 2, 3]); * ``` * * @param {Function} callback * * @returns {Function} */ function spread(callback) { return function wrap(arr) { return callback.apply(null, arr); }; } /** * Determines whether the payload is an error thrown by Axios * * @param {*} payload The value to test * * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false */ function isAxiosError(payload) { return utils$1.isObject(payload) && (payload.isAxiosError === true); } const HttpStatusCode = { Continue: 100, SwitchingProtocols: 101, Processing: 102, EarlyHints: 103, Ok: 200, Created: 201, Accepted: 202, NonAuthoritativeInformation: 203, NoContent: 204, ResetContent: 205, PartialContent: 206, MultiStatus: 207, AlreadyReported: 208, ImUsed: 226, MultipleChoices: 300, MovedPermanently: 301, Found: 302, SeeOther: 303, NotModified: 304, UseProxy: 305, Unused: 306, TemporaryRedirect: 307, PermanentRedirect: 308, BadRequest: 400, Unauthorized: 401, PaymentRequired: 402, Forbidden: 403, NotFound: 404, MethodNotAllowed: 405, NotAcceptable: 406, ProxyAuthenticationRequired: 407, RequestTimeout: 408, Conflict: 409, Gone: 410, LengthRequired: 411, PreconditionFailed: 412, PayloadTooLarge: 413, UriTooLong: 414, UnsupportedMediaType: 415, RangeNotSatisfiable: 416, ExpectationFailed: 417, ImATeapot: 418, MisdirectedRequest: 421, UnprocessableEntity: 422, Locked: 423, FailedDependency: 424, TooEarly: 425, UpgradeRequired: 426, PreconditionRequired: 428, TooManyRequests: 429, RequestHeaderFieldsTooLarge: 431, UnavailableForLegalReasons: 451, InternalServerError: 500, NotImplemented: 501, BadGateway: 502, ServiceUnavailable: 503, GatewayTimeout: 504, HttpVersionNotSupported: 505, VariantAlsoNegotiates: 506, InsufficientStorage: 507, LoopDetected: 508, NotExtended: 510, NetworkAuthenticationRequired: 511, }; Object.entries(HttpStatusCode).forEach(([key, value]) => { HttpStatusCode[value] = key; }); /** * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * * @returns {Axios} A new instance of Axios */ function createInstance(defaultConfig) { const context = new Axios(defaultConfig); const instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils$1.extend(instance, Axios.prototype, context, {allOwnKeys: true}); // Copy context to instance utils$1.extend(instance, context, null, {allOwnKeys: true}); // Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } // Create the default instance to be exported const axios = createInstance(defaults); // Expose Axios class to allow class inheritance axios.Axios = Axios; // Expose Cancel & CancelToken axios.CanceledError = CanceledError; axios.CancelToken = CancelToken; axios.isCancel = isCancel; axios.VERSION = VERSION; axios.toFormData = toFormData; // Expose AxiosError class axios.AxiosError = AxiosError; // alias for CanceledError for backward compatibility axios.Cancel = axios.CanceledError; // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = spread; // Expose isAxiosError axios.isAxiosError = isAxiosError; // Expose mergeConfig axios.mergeConfig = mergeConfig; axios.AxiosHeaders = AxiosHeaders; axios.formToJSON = thing => formDataToJSON(utils$1.isHTMLForm(thing) ? new FormData(thing) : thing); axios.getAdapter = adapters.getAdapter; axios.HttpStatusCode = HttpStatusCode; axios.default = axios; async function storeData(servers, key, value, ttl) { for (const server of servers) { try { const url = new URL(`${server}/store`); url.searchParams.append("key", key); if (ttl !== null) { url.searchParams.append("ttl", ttl.toString()); } const response = await axios.post(url.toString(), value, { headers: { "Content-Type": "application/octet-stream" } }); console.log("Data stored successfully:", key); if (response.status !== 200) { console.error("Received response status", response.status); continue; } return response; } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 409) { return null; } console.error("Error storing data:", error); } } return null; } async function retrieveData(servers, key) { for (const server of servers) { try { const response = await axios.get(`${server}/retrieve/${key}`, { responseType: "arraybuffer" }); if (response.status !== 200) { console.error("Received response status", response.status); continue; } return response.data; } catch (error) { console.error("Error retrieving data:", error); } } return null; } async function testData(servers, key) { const res = {}; for (const server of servers) { res[server] = null; try { const response = await axios.get(`${server}/test/${key}`); if (response.status !== 200) { console.error(`${server}: Test response status: ${response.status}`); continue; } const data = response.data; res[server] = data.value; } catch (error) { console.error("Error retrieving data:", error); return null; } } return res; } const BASEURL = `http://localhost`; const BOOTSTRAPURL = [`${BASEURL}:8090`]; const STORAGEURL = `${BASEURL}:8081`; const BLINDBITURL = `${BASEURL}:8000`; const DEFAULTAMOUNT = 1000n; const EMPTY32BYTES = String("").padStart(64, "0"); class Services { static initializing = null; static instance; processId = null; stateId = null; sdkClient; processesCache = {}; myProcesses = /* @__PURE__ */ new Set(); notifications = null; subscriptions = []; database; routingInstance; relayAddresses = {}; membersList = {}; currentBlockHeight = -1; // Private constructor to prevent direct instantiation from outside constructor() { } // Method to access the singleton instance of Services static async getInstance() { if (Services.instance) { return Services.instance; } if (!Services.initializing) { Services.initializing = (async () => { const instance = new Services(); await instance.init(); instance.routingInstance = await ModalService$1.getInstance(); return instance; })(); } console.log("initializing services"); Services.instance = await Services.initializing; Services.initializing = null; return Services.instance; } async init() { this.notifications = this.getNotifications(); this.sdkClient = await __vitePreload(() => import('./sdk_client-CuF7MH2z.mjs'),true?[]:void 0); this.sdkClient.setup(); const params = new URLSearchParams(window.location.search); const isE2E = params.has("e2e"); if (isE2E) { return; } for (const wsurl of Object.values(BOOTSTRAPURL)) { this.updateRelay(wsurl, ""); } } setProcessId(processId) { this.processId = processId; } setStateId(stateId) { this.stateId = stateId; } getProcessId() { return this.processId; } getStateId() { return this.stateId; } /** * Calls `this.addWebsocketConnection` for each `wsurl` in relayAddresses. * Waits for at least one handshake message before returning. */ async connectAllRelays() { const connectedUrls = []; for (const wsurl of Object.keys(this.relayAddresses)) { try { console.log(`Connecting to: ${wsurl}`); await this.addWebsocketConnection(wsurl); connectedUrls.push(wsurl); console.log(`Successfully connected to: ${wsurl}`); } catch (error) { console.error(`Failed to connect to ${wsurl}:`, error); } } if (connectedUrls.length > 0) { await this.waitForHandshakeMessage(); } } async addWebsocketConnection(url) { console.log("Opening new websocket connection"); await initWebsocket(url); } /** * Add or update a key/value pair in relayAddresses. * @param wsurl - The WebSocket URL (key). * @param spAddress - The SP Address (value). */ updateRelay(wsurl, spAddress) { this.relayAddresses[wsurl] = spAddress; console.log(`Updated: ${wsurl} -> ${spAddress}`); } /** * Retrieve the spAddress for a given wsurl. * @param wsurl - The WebSocket URL to look up. * @returns The SP Address if found, or undefined if not. */ getSpAddress(wsurl) { return this.relayAddresses[wsurl]; } /** * Get all key/value pairs from relayAddresses. * @returns An array of objects containing wsurl and spAddress. */ getAllRelays() { return Object.entries(this.relayAddresses).map(([wsurl, spAddress]) => ({ wsurl, spAddress })); } /** * Print all key/value pairs for debugging. */ printAllRelays() { console.log("Current relay addresses:"); for (const [wsurl, spAddress] of Object.entries(this.relayAddresses)) { console.log(`${wsurl} -> ${spAddress}`); } } isPaired() { try { return this.sdkClient.is_paired(); } catch (e) { throw new Error(`isPaired ~ Error: ${e}`); } } async unpairDevice() { try { this.sdkClient.unpair_device(); const newDevice = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(newDevice); } catch (e) { throw new Error(`Failed to unpair device: ${e}`); } } async getSecretForAddress(address) { const db = await Database.getInstance(); return await db.getObject("shared_secrets", address); } async getAllSecrets() { const db = await Database.getInstance(); const sharedSecrets = await db.dumpStore("shared_secrets"); const unconfirmedSecrets = await db.dumpStore("unconfirmed_secrets"); const secretsStore = { shared_secrets: sharedSecrets, unconfirmed_secrets: Object.values(unconfirmedSecrets) }; return secretsStore; } async getAllDiffs() { const db = await Database.getInstance(); return await db.dumpStore("diffs"); } async getDiffByValue(value) { const db = await Database.getInstance(); const store = "diffs"; const res = await db.getObject(store, value); return res; } async getTokensFromFaucet() { try { await this.ensureSufficientAmount(); } catch (e) { console.error("Failed to get tokens from relay, check connection"); return; } } async checkConnections(members) { await this.getTokensFromFaucet(); let unconnectedAddresses = /* @__PURE__ */ new Set(); const myAddress = this.getDeviceAddress(); for (const member of members) { const sp_addresses = member.sp_addresses; if (!sp_addresses || sp_addresses.length === 0) continue; for (const address of sp_addresses) { if (address === myAddress) continue; const sharedSecret = await this.getSecretForAddress(address); if (!sharedSecret) { unconnectedAddresses.add(address); } } } if (unconnectedAddresses && unconnectedAddresses.size != 0) { const apiResult = await this.connectAddresses([...unconnectedAddresses]); await this.handleApiReturn(apiResult); } } async connectAddresses(addresses) { if (addresses.length === 0) { throw new Error("Trying to connect to empty addresses list"); } try { return this.sdkClient.create_transaction(addresses, 1); } catch (e) { console.error("Failed to connect member:", e); throw e; } } async ensureSufficientAmount() { const availableAmt = this.getAmount(); const target = DEFAULTAMOUNT * BigInt(10); if (availableAmt < target) { const faucetMsg = this.createFaucetMessage(); this.sendFaucetMessage(faucetMsg); await this.waitForAmount(target); } } async waitForAmount(target) { let attempts = 3; while (attempts > 0) { const amount = this.getAmount(); if (amount >= target) { return amount; } attempts--; if (attempts > 0) { await new Promise((resolve) => setTimeout(resolve, 1e3)); } } throw new Error("Amount is still 0 after 3 attempts"); } async createPairingProcess(userName, pairWith) { if (this.sdkClient.is_paired()) { throw new Error("Device already paired"); } const myAddress = this.sdkClient.get_address(); pairWith.push(myAddress); const privateData = { description: "pairing", counter: 0 }; const publicData = { memberPublicName: userName, pairedAddresses: pairWith }; const validation_fields = [...Object.keys(privateData), ...Object.keys(publicData), "roles"]; const roles = { pairing: { members: [], validation_rules: [ { quorum: 1, fields: validation_fields, min_sig_member: 1 } ], storages: [STORAGEURL] } }; try { return this.createProcess(privateData, publicData, roles); } catch (e) { throw new Error(`Creating process failed:, ${e}`); } } isFileBlob(value) { return typeof value === "object" && value !== null && typeof value.type === "string" && value.data instanceof Uint8Array; } splitData(obj) { const jsonCompatibleData = {}; const binaryData = {}; for (const [key, value] of Object.entries(obj)) { if (this.isFileBlob(value)) { binaryData[key] = value; } else { jsonCompatibleData[key] = value; } } return { jsonCompatibleData, binaryData }; } async createProcess(privateData, publicData, roles) { let relayAddress = this.getAllRelays()[0]?.spAddress; if (!relayAddress || relayAddress === "") { console.log("No relay address found, connecting to relays..."); await this.connectAllRelays(); relayAddress = this.getAllRelays()[0]?.spAddress; if (!relayAddress || relayAddress === "") { throw new Error("No relay address available after connecting to relays"); } } const feeRate = 1; const privateSplitData = this.splitData(privateData); const publicSplitData = this.splitData(publicData); const encodedPrivateData = { ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(privateSplitData.binaryData) }; const encodedPublicData = { ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(publicSplitData.binaryData) }; console.log("encoded data:", encodedPrivateData); console.log("encoded data:", encodedPublicData); let members = /* @__PURE__ */ new Set(); for (const role of Object.values(roles)) { for (const member of role.members) { const memberAddresses = this.getAddressesForMemberId(member); if (memberAddresses && memberAddresses.length != 0) { members.add({ sp_addresses: memberAddresses }); } } } console.log("members:", members); await this.checkConnections([...members]); const result = this.sdkClient.create_new_process(encodedPrivateData, roles, encodedPublicData, relayAddress, feeRate, this.getAllMembers()); return result; } async updateProcess(process, privateData, publicData, roles) { if (!roles) { roles = this.getRoles(process); } else { console.log("Provided new roles:", JSON.stringify(roles)); } let members = /* @__PURE__ */ new Set(); for (const role of Object.values(roles)) { for (const member of role.members) { members.add(member); } } if (members.size === 0) { const publicData2 = this.getPublicData(process); if (!publicData2 || !publicData2["pairedAddresses"]) { throw new Error("Not a pairing process"); } const decodedAddresses = this.decodeValue(publicData2["pairedAddresses"]); if (decodedAddresses.length === 0) { throw new Error("Not a pairing process"); } members.add({ sp_addresses: decodedAddresses }); } await this.checkConnections([...members]); const privateSplitData = this.splitData(privateData); const publicSplitData = this.splitData(publicData); const encodedPrivateData = { ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(privateSplitData.binaryData) }; const encodedPublicData = { ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(publicSplitData.binaryData) }; try { return this.sdkClient.update_process(process, encodedPrivateData, roles, encodedPublicData, this.getAllMembers()); } catch (e) { throw new Error(`Failed to update process: ${e}`); } } async createPrdUpdate(processId, stateId) { const process = await this.getProcess(processId); if (!process) { throw new Error("Unknown process"); } try { return this.sdkClient.create_update_message(process, stateId, this.getAllMembers()); } catch (e) { throw new Error(`Failed to create prd update: ${e}`); } } async createPrdResponse(processId, stateId) { const process = await this.getProcess(processId); if (!process) { throw new Error("Unknown process"); } try { return this.sdkClient.create_response_prd(process, stateId, this.getAllMembers()); } catch (e) { throw new Error(`Failed to create response prd: ${e}`); } } async approveChange(processId, stateId) { const process = await this.getProcess(processId); if (!process) { throw new Error("Failed to get process from db"); } try { return this.sdkClient.validate_state(process, stateId, this.getAllMembers()); } catch (e) { throw new Error(`Failed to create prd response: ${e}`); } } async rejectChange(processId, stateId) { const process = await this.getProcess(processId); if (!process) { throw new Error("Failed to get process from db"); } try { return this.sdkClient.refuse_state(process, stateId); } catch (e) { throw new Error(`Failed to create prd response: ${e}`); } } async resetDevice() { this.sdkClient.reset_device(); const db = await Database.getInstance(); await db.clearStore("wallet"); await db.clearStore("shared_secrets"); await db.clearStore("unconfirmed_secrets"); await db.clearStore("processes"); await db.clearStore("diffs"); } sendNewTxMessage(message) { sendMessage("NewTx", message); } sendCommitMessage(message) { sendMessage("Commit", message); } sendCipherMessages(ciphers) { for (let i = 0; i < ciphers.length; i++) { const cipher = ciphers[i]; sendMessage("Cipher", cipher); } } sendFaucetMessage(message) { sendMessage("Faucet", message); } async parseCipher(message) { const membersList = this.getAllMembers(); const processes = await this.getProcesses(); try { const apiReturn = this.sdkClient.parse_cipher(message, membersList, processes); await this.handleApiReturn(apiReturn); const waitingModal = document.getElementById("waiting-modal"); if (waitingModal) { this.device2Ready = true; } } catch (e) { console.error(`Parsed cipher with error: ${e}`); } } async parseNewTx(newTxMsg) { const parsedMsg = JSON.parse(newTxMsg); if (parsedMsg.error !== null) { console.error("Received error in new tx message:", parsedMsg.error); return; } const membersList = this.getAllMembers(); try { const prevouts = this.sdkClient.get_prevouts(parsedMsg.transaction); console.log("prevouts:", prevouts); for (const process of Object.values(this.processesCache)) { const tip = process.states[process.states.length - 1].commited_in; if (prevouts.includes(tip)) { const processId = process.states[0].commited_in; const newTip = this.sdkClient.get_txid(parsedMsg.transaction); console.log("Transaction", newTip, "spends the tip of process", processId); const newStateId = this.sdkClient.get_opreturn(parsedMsg.transaction); console.log("newStateId:", newStateId); const updatedProcess = this.sdkClient.process_commit_new_state(process, newStateId, newTip); this.processesCache[processId] = updatedProcess; console.log("updatedProcess:", updatedProcess); break; } } } catch (e) { console.error("Failed to parse new tx for commitments:", e); } try { const parsedTx = this.sdkClient.parse_new_tx(newTxMsg, 0, membersList); if (parsedTx) { try { await this.handleApiReturn(parsedTx); const newDevice = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(newDevice); } catch (e) { console.error("Failed to update device with new tx"); } } } catch (e) { console.debug(e); } } async handleApiReturn(apiReturn) { console.log(apiReturn); if (apiReturn.partial_tx) { try { const res = this.sdkClient.sign_transaction(apiReturn.partial_tx); apiReturn.new_tx_to_send = res.new_tx_to_send; } catch (e) { console.error("Failed to sign transaction:", e); } } if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) { this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send)); await new Promise((r) => setTimeout(r, 500)); } if (apiReturn.secrets) { const unconfirmedSecrets = apiReturn.secrets.unconfirmed_secrets; const confirmedSecrets = apiReturn.secrets.shared_secrets; const db = await Database.getInstance(); for (const secret of unconfirmedSecrets) { await db.addObject({ storeName: "unconfirmed_secrets", object: secret, key: null }); } const entries = Object.entries(confirmedSecrets).map(([key, value]) => ({ key, value })); for (const entry of entries) { try { await db.addObject({ storeName: "shared_secrets", object: entry.value, key: entry.key }); } catch (e) { throw e; } } } if (apiReturn.updated_process) { const updatedProcess = apiReturn.updated_process; const processId = updatedProcess.process_id; if (updatedProcess.encrypted_data && Object.keys(updatedProcess.encrypted_data).length != 0) { for (const [hash, cipher] of Object.entries(updatedProcess.encrypted_data)) { const blob = this.hexToBlob(cipher); try { await this.saveBlobToDb(hash, blob); } catch (e) { console.error(e); } } } await this.saveProcessToDb(processId, updatedProcess.current_process); if (updatedProcess.diffs && updatedProcess.diffs.length != 0) { try { await this.saveDiffsToDb(updatedProcess.diffs); } catch (e) { console.error("Failed to save diffs to db:", e); } } } if (apiReturn.push_to_storage && apiReturn.push_to_storage.length != 0) { for (const hash of apiReturn.push_to_storage) { const blob = await this.getBlobFromDb(hash); if (blob) { await this.saveDataToStorage(hash, blob, null); } else { console.error("Failed to get data from db"); } } } if (apiReturn.commit_to_send) { const commit = apiReturn.commit_to_send; this.sendCommitMessage(JSON.stringify(commit)); } if (apiReturn.ciphers_to_send && apiReturn.ciphers_to_send.length != 0) { this.sendCipherMessages(apiReturn.ciphers_to_send); } } async openPairingConfirmationModal(processId) { const process = await this.getProcess(processId); if (!process) { console.error("Failed to find pairing process"); return; } const firstState = process.states[0]; const roles = firstState.roles; const stateId = firstState.state_id; try { await this.routingInstance.openPairingConfirmationModal(roles, processId, stateId); } catch (e) { console.error(e); } } async confirmPairing() { if (!this.processId || !this.stateId) { console.error("Missing process and/or state ID"); return; } let createPrdUpdateReturn; try { createPrdUpdateReturn = await this.createPrdUpdate(this.processId, this.stateId); } catch (e) { throw new Error(`createPrdUpdate failed: ${e}`); } await this.handleApiReturn(createPrdUpdateReturn); let approveChangeReturn; try { approveChangeReturn = await this.approveChange(this.processId, this.stateId); } catch (e) { throw new Error(`approveChange failed: ${e}`); } await this.handleApiReturn(approveChangeReturn); await this.pairDevice(); this.processId = null; this.stateId = null; const newDevice = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(newDevice); await navigate("account"); } async updateDevice() { let myPairingProcessId; try { myPairingProcessId = this.getPairingProcessId(); } catch (e) { console.error("Failed to get pairing process id"); return; } const myPairingProcess = await this.getProcess(myPairingProcessId); if (!myPairingProcess) { console.error("Unknown pairing process"); return; } const myPairingState = this.getLastCommitedState(myPairingProcess); if (myPairingState) { const encodedSpAddressList = myPairingState.public_data["pairedAddresses"]; const spAddressList = this.decodeValue(encodedSpAddressList); if (spAddressList.length === 0) { console.error("Empty pairedAddresses"); return; } if (!spAddressList.includes(this.getDeviceAddress())) { await this.unpairDevice(); return; } this.sdkClient.unpair_device(); this.sdkClient.pair_device(myPairingProcessId, spAddressList); const newDevice = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(newDevice); } } async pairDevice() { if (!this.processId) { console.error("No processId set"); return; } const process = await this.getProcess(this.processId); if (!process) { console.error("Unknown process"); return; } let spAddressList = []; try { let encodedSpAddressList = []; if (this.stateId) { const state = process.states.find((state2) => state2.state_id === this.stateId); if (state) { encodedSpAddressList = state.public_data["pairedAddresses"]; } } else { const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState) { encodedSpAddressList = lastCommitedState.public_data["pairedAddresses"]; } } spAddressList = this.sdkClient.decode_value(encodedSpAddressList); if (!spAddressList || spAddressList.length == 0) { throw new Error("Empty pairedAddresses"); } } catch (e) { throw new Error(`Failed to get pairedAddresses from process: ${e}`); } try { this.sdkClient.pair_device(this.processId, spAddressList); } catch (e) { throw new Error(`Failed to pair device: ${e}`); } } getAmount() { const amount = this.sdkClient.get_available_amount(); return amount; } getDeviceAddress() { try { return this.sdkClient.get_address(); } catch (e) { throw new Error(`Failed to get device address: ${e}`); } } dumpDeviceFromMemory() { try { return this.sdkClient.dump_device(); } catch (e) { throw new Error(`Failed to dump device: ${e}`); } } dumpNeuteredDevice() { try { return this.sdkClient.dump_neutered_device(); } catch (e) { console.error(`Failed to dump device: ${e}`); return null; } } getPairingProcessId() { try { return this.sdkClient.get_pairing_process_id(); } catch (e) { throw new Error(`Failed to get pairing process: ${e}`); } } async saveDeviceInDatabase(device) { const db = await Database.getInstance(); const walletStore = "wallet"; try { const prevDevice = await this.getDeviceFromDatabase(); if (prevDevice) { await db.deleteObject(walletStore, "1"); } await db.addObject({ storeName: walletStore, object: { pre_id: "1", device }, key: null }); } catch (e) { console.error(e); } } async getDeviceFromDatabase() { const db = await Database.getInstance(); const walletStore = "wallet"; try { const dbRes = await db.getObject(walletStore, "1"); if (dbRes) { return dbRes["device"]; } else { return null; } } catch (e) { throw new Error(`Failed to retrieve device from db: ${e}`); } } async getMemberFromDevice() { try { const device = await this.getDeviceFromDatabase(); if (device) { const pairedMember = device["paired_member"]; return pairedMember.sp_addresses; } else { return null; } } catch (e) { throw new Error(`Failed to retrieve paired_member from device: ${e}`); } } isChildRole(parent, child) { try { this.sdkClient.is_child_role(JSON.stringify(parent), JSON.stringify(child)); } catch (e) { console.error(e); return false; } return true; } rolesContainsUs(roles) { let us; try { us = this.sdkClient.get_pairing_process_id(); } catch (e) { throw e; } return this.rolesContainsMember(roles, us); } rolesContainsMember(roles, pairingProcessId) { for (const roleDef of Object.values(roles)) { if (roleDef.members.includes(pairingProcessId)) { return true; } } return false; } async dumpWallet() { const wallet = await this.sdkClient.dump_wallet(); return wallet; } createFaucetMessage() { const message = this.sdkClient.create_faucet_msg(); return message; } async createNewDevice() { let spAddress = ""; try { spAddress = await this.sdkClient.create_new_device(0, "signet"); const device = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(device); } catch (e) { console.error("Services ~ Error:", e); } return spAddress; } restoreDevice(device) { try { this.sdkClient.restore_device(device); } catch (e) { console.error(e); } } async updateDeviceBlockHeight() { if (this.currentBlockHeight === -1) { throw new Error("Current block height not set"); } let device = null; try { device = await this.getDeviceFromDatabase(); } catch (e) { throw new Error(`Failed to get device from database: ${e}`); } if (!device) { throw new Error("Device not found"); } const birthday = device.sp_wallet.birthday; if (birthday === void 0 || birthday === null) { throw new Error("Birthday not found"); } if (birthday === 0) { device.sp_wallet.birthday = this.currentBlockHeight; device.sp_wallet.last_scan = this.currentBlockHeight; try { this.sdkClient.restore_device(device); await this.saveDeviceInDatabase(device); } catch (e) { throw new Error(`Failed to save updated device: ${e}`); } } else { if (device.sp_wallet.last_scan < this.currentBlockHeight) { await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL); } else { return; } } } async removeProcess(processId) { const db = await Database.getInstance(); const storeName = "processes"; try { await db.deleteObject(storeName, processId); } catch (e) { console.error(e); } } async batchSaveProcessesToDb(processes) { if (Object.keys(processes).length === 0) { return; } const db = await Database.getInstance(); const storeName = "processes"; try { await db.batchWriting({ storeName, objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })) }); this.processesCache = { ...this.processesCache, ...processes }; } catch (e) { throw e; } } async saveProcessToDb(processId, process) { const db = await Database.getInstance(); const storeName = "processes"; try { await db.addObject({ storeName, object: process, key: processId }); this.processesCache[processId] = process; } catch (e) { console.error(`Failed to save process ${processId}: ${e}`); } } async saveBlobToDb(hash, data) { const db = await Database.getInstance(); try { await db.addObject({ storeName: "data", object: data, key: hash }); } catch (e) { console.error(`Failed to save data to db: ${e}`); } } async getBlobFromDb(hash) { const db = await Database.getInstance(); try { return await db.getObject("data", hash); } catch (e) { return null; } } async saveDataToStorage(hash, data, ttl) { const storages = [STORAGEURL]; try { await storeData(storages, hash, data, ttl); } catch (e) { console.error(`Failed to store data with hash ${hash}: ${e}`); } } async fetchValueFromStorage(hash) { const storages = [STORAGEURL]; return await retrieveData(storages, hash); } async testDataInStorage(hash) { const storages = [STORAGEURL]; return await testData(storages, hash); } async saveDiffsToDb(diffs) { const db = await Database.getInstance(); try { for (const diff of diffs) { await db.addObject({ storeName: "diffs", object: diff, key: null }); } } catch (e) { throw new Error(`Failed to save process: ${e}`); } } async getProcess(processId) { if (this.processesCache[processId]) { return this.processesCache[processId]; } else { const db = await Database.getInstance(); const process = await db.getObject("processes", processId); return process; } } async getProcesses() { if (Object.keys(this.processesCache).length > 0) { return this.processesCache; } else { try { const db = await Database.getInstance(); this.processesCache = await db.dumpStore("processes"); return this.processesCache; } catch (e) { throw e; } } } async restoreProcessesFromBackUp(processes) { const db = await Database.getInstance(); const storeName = "processes"; try { await db.batchWriting({ storeName, objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })) }); } catch (e) { throw e; } await this.restoreProcessesFromDB(); } // Restore processes cache from persistent storage async restoreProcessesFromDB() { const db = await Database.getInstance(); try { const processes = await db.dumpStore("processes"); if (processes && Object.keys(processes).length != 0) { console.log(`Restoring ${Object.keys(processes).length} processes`); this.processesCache = processes; } else { console.log("No processes to restore!"); } } catch (e) { throw e; } } async clearSecretsFromDB() { const db = await Database.getInstance(); try { await db.clearStore("shared_secrets"); await db.clearStore("unconfirmed_secrets"); } catch (e) { console.error(e); } } async restoreSecretsFromBackUp(secretsStore) { const db = await Database.getInstance(); for (const secret of secretsStore.unconfirmed_secrets) { await db.addObject({ storeName: "unconfirmed_secrets", object: secret, key: null }); } const entries = Object.entries(secretsStore.shared_secrets).map(([key, value]) => ({ key, value })); for (const entry of entries) { await db.addObject({ storeName: "shared_secrets", object: entry.value, key: entry.key }); } await this.restoreSecretsFromDB(); } async restoreSecretsFromDB() { const db = await Database.getInstance(); try { const sharedSecrets = await db.dumpStore("shared_secrets"); const unconfirmedSecrets = await db.dumpStore("unconfirmed_secrets"); const secretsStore = { shared_secrets: sharedSecrets, unconfirmed_secrets: Object.values(unconfirmedSecrets) }; this.sdkClient.set_shared_secrets(JSON.stringify(secretsStore)); } catch (e) { throw e; } } decodeValue(value) { try { return this.sdkClient.decode_value(value); } catch (e) { console.error(`Failed to decode value: ${e}`); return null; } } async decryptAttribute(processId, state, attribute) { let hash = state.pcd_commitment[attribute]; if (!hash) { return null; } let key = state.keys[attribute]; const pairingProcessId = this.getPairingProcessId(); if (!key) { const roles = state.roles; let hasAccess = false; for (const role of Object.values(roles)) { for (const rule of Object.values(role.validation_rules)) { if (rule.fields.includes(attribute)) { if (role.members.includes(pairingProcessId)) { hasAccess = true; break; } } } } if (!hasAccess) return null; await this.requestDataFromPeers(processId, [state.state_id], [state.roles]); const maxRetries = 5; const retryDelay = 500; let retries = 0; while ((!hash || !key) && retries < maxRetries) { await new Promise((resolve) => setTimeout(resolve, retryDelay)); hash = state.pcd_commitment[attribute]; key = state.keys[attribute]; retries++; } } if (hash && key) { const blob = await this.getBlobFromDb(hash); if (blob) { const buf = await blob.arrayBuffer(); const cipher = new Uint8Array(buf); const keyUIntArray = this.hexToUInt8Array(key); try { const clear = this.sdkClient.decrypt_data(keyUIntArray, cipher); if (clear) { const decoded = this.sdkClient.decode_value(clear); return decoded; } else { throw new Error("decrypt_data returned null"); } } catch (e) { console.error(`Failed to decrypt data: ${e}`); } } } return null; } getNotifications() { return this.notifications; } setNotifications(notifications) { this.notifications = notifications; } async importJSON(backup) { const device = backup.device; await this.resetDevice(); await this.saveDeviceInDatabase(device); this.restoreDevice(device); const secretsStore = backup.secrets; await this.restoreSecretsFromBackUp(secretsStore); const processes = backup.processes; await this.restoreProcessesFromBackUp(processes); } async createBackUp() { const device = await this.getDeviceFromDatabase(); if (!device) { console.error("No device loaded"); return null; } const processes = await this.getProcesses(); const secrets = await this.getAllSecrets(); const backUp = { device, secrets, processes }; return backUp; } // Device 1 wait Device 2 device1 = false; device2Ready = false; resetState() { this.device1 = false; this.device2Ready = false; } // Handle the handshake message async handleHandshakeMsg(url, parsedMsg) { try { const handshakeMsg = JSON.parse(parsedMsg); this.updateRelay(url, handshakeMsg.sp_address); this.currentBlockHeight = handshakeMsg.chain_tip; if (this.membersList && Object.keys(this.membersList).length === 0) { this.membersList = handshakeMsg.peers_list; } else { for (const [processId, member] of Object.entries(handshakeMsg.peers_list)) { this.membersList[processId] = member; } } setTimeout(async () => { const newProcesses = handshakeMsg.processes_list; if (!newProcesses || Object.keys(newProcesses).length === 0) { console.debug("Received empty processes list from", url); return; } if (this.processesCache && Object.keys(this.processesCache).length === 0) { try { await this.batchSaveProcessesToDb(newProcesses); } catch (e) { console.error("Failed to save processes to db:", e); } } else { const toSave = {}; for (const [processId, process] of Object.entries(newProcesses)) { const existing = await this.getProcess(processId); if (existing) { let new_states = []; let roles = []; for (const state of process.states) { if (!state.state_id || state.state_id === EMPTY32BYTES) { continue; } if (!this.lookForStateId(existing, state.state_id)) { if (this.rolesContainsUs(state.roles)) { new_states.push(state.state_id); roles.push(state.roles); } } } if (new_states.length != 0) { await this.requestDataFromPeers(processId, new_states, roles); toSave[processId] = process; } const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState && lastCommitedState.public_data && lastCommitedState.public_data["pairedAddresses"]) { try { const pairedAddresses = this.decodeValue(lastCommitedState.public_data["pairedAddresses"]); if (pairedAddresses && pairedAddresses.length > 0 && pairedAddresses.includes(this.getDeviceAddress())) { await this.saveProcessToDb(processId, process); await this.updateDevice(); } } catch (e) { console.error("Failed to check for pairing process:", e); } } } else { console.log(`Saving ${processId} to db`); toSave[processId] = process; } } await this.batchSaveProcessesToDb(toSave); } }, 500); } catch (e) { console.error("Failed to parse init message:", e); } } lookForStateId(process, stateId) { for (const state of process.states) { if (state.state_id === stateId) { return true; } } return false; } /** * Waits for at least one handshake message to be received from any connected relay. * This ensures that the relay addresses are fully populated and the member list is updated. * @returns A promise that resolves when at least one handshake message is received. */ async waitForHandshakeMessage(timeoutMs = 1e4) { const startTime = Date.now(); const pollInterval = 100; return new Promise((resolve, reject) => { const checkForHandshake = () => { if (Object.keys(this.membersList).length > 0) { console.log("Handshake message received, members list populated"); resolve(); return; } if (Date.now() - startTime >= timeoutMs) { reject(new Error(`No handshake message received after ${timeoutMs}ms timeout`)); return; } setTimeout(checkForHandshake, pollInterval); }; checkForHandshake(); }); } /** * Retourne la liste de tous les membres ordonnés par leur process id * @returns Un tableau contenant tous les membres */ getAllMembersSorted() { return Object.fromEntries(Object.entries(this.membersList).sort(([keyA], [keyB]) => keyA.localeCompare(keyB))); } getAllMembers() { return this.membersList; } getAddressesForMemberId(memberId) { try { return this.membersList[memberId].sp_addresses; } catch (e) { return null; } } compareMembers(memberA, memberB) { if (!memberA || !memberB) { return false; } if (memberA.length !== memberB.length) { return false; } const res = memberA.every((item) => memberB.includes(item)) && memberB.every((item) => memberA.includes(item)); return res; } async handleCommitError(response) { const content = JSON.parse(response); const error = content.error; const errorMsg = error["GenericError"]; const dontRetry = ["State is identical to the previous state", "Not enough valid proofs", "Not enough members to validate"]; if (dontRetry.includes(errorMsg)) { return; } setTimeout(async () => { this.sendCommitMessage(JSON.stringify(content)); }, 1e3); } getRoles(process) { const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) { return lastCommitedState.roles; } else if (process.states.length === 2) { const firstState = process.states[0]; if (firstState && firstState.roles && Object.keys(firstState.roles).length != 0) { return firstState.roles; } } return null; } getPublicData(process) { const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState && lastCommitedState.public_data && Object.keys(lastCommitedState.public_data).length != 0) { return lastCommitedState.public_data; } else if (process.states.length === 2) { const firstState = process.states[0]; if (firstState && firstState.public_data && Object.keys(firstState.public_data).length != 0) { return firstState.public_data; } } return null; } getProcessName(process) { const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState && lastCommitedState.public_data) { const processName = lastCommitedState.public_data["processName"]; if (processName) { return this.decodeValue(processName); } else { return null; } } else { return null; } } async getMyProcesses() { let pairingProcessId = null; try { pairingProcessId = this.getPairingProcessId(); } catch (e) { return null; } if (!pairingProcessId) { return null; } try { const processes = await this.getProcesses(); const newMyProcesses = new Set(this.myProcesses || []); newMyProcesses.add(pairingProcessId); for (const [processId, process] of Object.entries(processes)) { if (newMyProcesses.has(processId)) { continue; } try { const roles = this.getRoles(process); if (roles && this.rolesContainsUs(roles)) { newMyProcesses.add(processId); } } catch (e) { console.error(e); } } this.myProcesses = newMyProcesses; return Array.from(this.myProcesses); } catch (e) { console.error("Failed to get processes:", e); return null; } } async requestDataFromPeers(processId, stateIds, roles) { console.log("Requesting data from peers"); const membersList = this.getAllMembers(); try { const res = this.sdkClient.request_data(processId, stateIds, roles, membersList); await this.handleApiReturn(res); } catch (e) { console.error(e); } } hexToBlob(hexString) { const uint8Array = this.hexToUInt8Array(hexString); const ab = new ArrayBuffer(uint8Array.length); const view = new Uint8Array(ab); view.set(uint8Array); return new Blob([ab], { type: "application/octet-stream" }); } hexToUInt8Array(hexString) { if (hexString.length % 2 !== 0) { throw new Error("Invalid hex string: length must be even"); } const uint8Array = new Uint8Array(hexString.length / 2); for (let i = 0; i < hexString.length; i += 2) { uint8Array[i / 2] = parseInt(hexString.substr(i, 2), 16); } return uint8Array; } async blobToHex(blob) { const buffer = await blob.arrayBuffer(); const bytes = new Uint8Array(buffer); return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join(""); } getHashForFile(commitedIn, label, fileBlob) { return this.sdkClient.hash_value(fileBlob, commitedIn, label); } getMerkleProofForFile(processState, attributeName) { return this.sdkClient.get_merkle_proof(processState, attributeName); } validateMerkleProof(proof, hash) { try { return this.sdkClient.validate_merkle_proof(proof, hash); } catch (e) { throw new Error(`Failed to validate merkle proof: ${e}`); } } getLastCommitedState(process) { if (process.states.length === 0) return null; const processTip = process.states[process.states.length - 1].commited_in; const lastCommitedState = process.states.findLast((state) => state.commited_in !== processTip); if (lastCommitedState) { return lastCommitedState; } else { return null; } } getLastCommitedStateIndex(process) { if (process.states.length === 0) return null; const processTip = process.states[process.states.length - 1].commited_in; for (let i = process.states.length - 1; i >= 0; i--) { if (process.states[i].commited_in !== processTip) { return i; } } return null; } getUncommitedStates(process) { if (process.states.length === 0) return []; const processTip = process.states[process.states.length - 1].commited_in; const res = process.states.filter((state) => state.commited_in === processTip); return res.filter((state) => state.state_id !== EMPTY32BYTES); } getStateFromId(process, stateId) { if (process.states.length === 0) return null; const state = process.states.find((state2) => state2.state_id === stateId); if (state) { return state; } else { return null; } } getNextStateAfterId(process, stateId) { if (process.states.length === 0) return null; const index = process.states.findIndex((state) => state.state_id === stateId); if (index !== -1 && index < process.states.length - 1) { return process.states[index + 1]; } return null; } isPairingProcess(roles) { if (Object.keys(roles).length != 1) { return false; } const pairingRole = roles["pairing"]; if (pairingRole) { return true; } else { return false; } } async updateMemberPublicName(process, newName) { const publicData = { memberPublicName: newName }; return await this.updateProcess(process, {}, publicData, null); } } class ModalService { static instance; stateId = null; processId = null; constructor() { } paired_addresses = []; modal = null; // Method to access the singleton instance of Services static async getInstance() { if (!ModalService.instance) { ModalService.instance = new ModalService(); } return ModalService.instance; } openLoginModal(myAddress, receiverAddress) { const container = document.querySelector(".page-container"); let html = modalHtml; html = html.replace("{{device1}}", myAddress); html = html.replace("{{device2}}", receiverAddress); if (container) container.innerHTML += html; const modal = document.getElementById("login-modal"); if (modal) modal.style.display = "flex"; const newScript = document.createElement("script"); newScript.setAttribute("type", "module"); newScript.textContent = modalScript; document.head.appendChild(newScript).parentNode?.removeChild(newScript); } async injectModal(members) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/confirmation-modal.html").then((res) => res.text()); html = html.replace("{{device1}}", await addressToEmoji(members[0]["sp_addresses"][0])); html = html.replace("{{device2}}", await addressToEmoji(members[0]["sp_addresses"][1])); container.innerHTML += html; const script = document.createElement("script"); script.src = "/src/components/modal/confirmation-modal.ts"; script.type = "module"; document.head.appendChild(script); } } async injectCreationModal(members) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/creation-modal.html").then((res) => res.text()); html = html.replace("{{device1}}", await addressToEmoji(members[0]["sp_addresses"][0])); container.innerHTML += html; const script = document.createElement("script"); script.src = "/src/components/modal/confirmation-modal.ts"; script.type = "module"; document.head.appendChild(script); } } // Device 1 wait Device 2 async injectWaitingModal() { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/modal/waiting-modal.html").then((res) => res.text()); container.innerHTML += html; } } async injectValidationModal(processDiff) { const container = document.querySelector("#containerId"); if (container) { let html = await fetch("/src/components/validation-modal/validation-modal.html").then((res) => res.text()); html = interpolate(html, { processId: processDiff.processId }); container.innerHTML += html; const script = document.createElement("script"); script.id = "validation-modal-script"; script.src = "/src/components/validation-modal/validation-modal.ts"; script.type = "module"; document.head.appendChild(script); const css = document.createElement("style"); css.id = "validation-modal-css"; css.innerText = validationModalStyle; document.head.appendChild(css); initValidationModal(processDiff); } } async closeValidationModal() { const script = document.querySelector("#validation-modal-script"); const css = document.querySelector("#validation-modal-css"); const component = document.querySelector("#validation-modal"); script?.remove(); css?.remove(); component?.remove(); } async openPairingConfirmationModal(roleDefinition, processId, stateId) { let memberOutPoints; if (roleDefinition["pairing"]) { const owner = roleDefinition["pairing"]; memberOutPoints = owner.members; } else { throw new Error('No "pairing" role'); } if (memberOutPoints.length != 1) { throw new Error("Must have exactly 1 member"); } console.log("MEMBER OUTPOINTS:", memberOutPoints); const service = await Services.getInstance(); const localAddress = service.getDeviceAddress(); const members = []; for (const outPoint of memberOutPoints) { const member = { sp_addresses: [] }; members.push(member); } for (const member of members) { if (member.sp_addresses) { for (const address of member.sp_addresses) { if (address !== localAddress) { this.paired_addresses.push(address); } } } } this.processId = processId; this.stateId = stateId; if (members[0].sp_addresses.length === 1) { await this.injectCreationModal(members); this.modal = document.getElementById("creation-modal"); console.log("LENGTH:", members[0].sp_addresses.length); } else { await this.injectModal(members); this.modal = document.getElementById("modal"); console.log("LENGTH:", members[0].sp_addresses.length); } if (this.modal) this.modal.style.display = "flex"; window.onclick = (event) => { if (event.target === this.modal) { this.closeConfirmationModal(); } }; } confirmLogin() { console.log("=============> Confirm Login"); } async closeLoginModal() { if (this.modal) this.modal.style.display = "none"; } async showConfirmationModal(options, fullscreen = false) { const modalElement = document.createElement("div"); modalElement.id = "confirmation-modal"; modalElement.innerHTML = ` `; document.body.appendChild(modalElement); return new Promise((resolve) => { const confirmButton = modalElement.querySelector("#confirm-button"); const cancelButton = modalElement.querySelector("#cancel-button"); const modalOverlay = modalElement.querySelector(".modal-overlay"); const cleanup = () => { modalElement.remove(); }; confirmButton?.addEventListener("click", () => { cleanup(); resolve(true); }); cancelButton?.addEventListener("click", () => { cleanup(); resolve(false); }); modalOverlay?.addEventListener("click", (e) => { if (e.target === modalOverlay) { cleanup(); resolve(false); } }); }); } async closeConfirmationModal() { const service = await Services.getInstance(); await service.unpairDevice(); if (this.modal) this.modal.style.display = "none"; } } async function unpair() { const service = await Services.getInstance(); await service.unpairDevice(); await navigate("home"); } window.unpair = unpair; function toggleMenu() { const menu = document.getElementById("menu"); if (menu) { if (menu.style.display === "block") { menu.style.display = "none"; } else { menu.style.display = "block"; } } } window.toggleMenu = toggleMenu; function openCloseNotifications() { const notifications2 = document.querySelector(".notification-board"); notifications2.style.display = notifications2?.style.display === "none" ? "block" : "none"; } window.openCloseNotifications = openCloseNotifications; async function initHeader() { if (currentRoute === "account") { const profileContainer = document.getElementById("profile-header-container"); if (profileContainer) { const profileHeaderHtml = await fetch("/src/components/profile-header/profile-header.html").then((res) => res.text()); profileContainer.innerHTML = profileHeaderHtml; loadUserProfile(); } } if (currentRoute === "home") { hideSomeFunctionnalities(); } else { fetchNotifications(); setInterval(fetchNotifications, 2 * 60 * 1e3); } } function hideSomeFunctionnalities() { const bell = document.querySelector(".bell-icon"); if (bell) bell.style.display = "none"; const notifBadge = document.querySelector(".notification-badge"); if (notifBadge) notifBadge.style.display = "none"; const actions = document.querySelectorAll(".menu-content a"); const excludedActions = ["Import", "Export"]; for (const action of actions) { if (!excludedActions.includes(action.innerHTML)) { action.style.display = "none"; } } } async function setNotification(notifications2) { const badge = document.querySelector(".notification-badge"); const noNotifications = document.querySelector(".no-notification"); if (notifications2?.length) { badge.innerText = notifications2.length.toString(); const notificationBoard = document.querySelector(".notification-board"); notificationBoard.querySelectorAll(".notification-element")?.forEach((elem) => elem.remove()); noNotifications.style.display = "none"; for (const notif of notifications2) { const notifElement = document.createElement("div"); notifElement.className = "notification-element"; notifElement.setAttribute("notif-id", notif.processId); notifElement.innerHTML = `
Validation required :
${notif.processId}
`; notificationBoard.appendChild(notifElement); notifElement.addEventListener("click", async () => { const modalService = await ModalService.getInstance(); modalService.injectValidationModal(notif); }); } } else { noNotifications.style.display = "block"; } } async function fetchNotifications() { const service = await Services.getInstance(); const data = service.getNotifications() || []; await setNotification(data); } async function loadUserProfile() { const userName = localStorage.getItem("userName"); const userLastName = localStorage.getItem("userLastName"); const userAvatar = localStorage.getItem("userAvatar") || "https://via.placeholder.com/150"; const userBanner = localStorage.getItem("userBanner") || "https://via.placeholder.com/800x200"; const nameElement = document.querySelector(".user-name"); const lastNameElement = document.querySelector(".user-lastname"); const avatarElement = document.querySelector(".avatar"); const bannerElement = document.querySelector(".banner-image"); if (nameElement) nameElement.textContent = userName; if (lastNameElement) lastNameElement.textContent = userLastName; if (avatarElement) avatarElement.src = userAvatar; if (bannerElement) bannerElement.src = userBanner; } async function importJSON() { const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; input.onchange = async (e) => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onload = async (e2) => { try { const content = JSON.parse(e2.target?.result); const service = await Services.getInstance(); await service.importJSON(content); alert("Import réussi"); window.location.reload(); } catch (error) { alert("Erreur lors de l'import: " + error); } }; reader.readAsText(file); } }; input.click(); } window.importJSON = importJSON; async function createBackUp() { const service = await Services.getInstance(); const backUp = await service.createBackUp(); if (!backUp) { console.error("No device to backup"); return; } try { const backUpJson = JSON.stringify(backUp, null, 2); const blob = new Blob([backUpJson], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "4nk-backup.json"; a.click(); URL.revokeObjectURL(url); console.log("Backup successfully prepared for download"); } catch (e) { console.error(e); } } window.createBackUp = createBackUp; async function disconnect() { console.log("Disconnecting..."); try { localStorage.clear(); await new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase("4nk"); request.onsuccess = () => { console.log("IndexedDB deleted successfully"); resolve(); }; request.onerror = () => reject(request.error); request.onblocked = () => { console.log("Database deletion was blocked"); resolve(); }; }); const registrations = await navigator.serviceWorker.getRegistrations(); await Promise.all(registrations.map((registration) => registration.unregister())); console.log("Service worker unregistered"); await navigate("home"); setTimeout(() => { window.location.href = window.location.origin; }, 100); } catch (error) { console.error("Error during disconnect:", error); window.location.href = window.location.origin; } } window.disconnect = disconnect; const encoder = new TextEncoder(); const decoder = new TextDecoder(); function concat(...buffers) { const size = buffers.reduce((acc, { length }) => acc + length, 0); const buf = new Uint8Array(size); let i = 0; for (const buffer of buffers) { buf.set(buffer, i); i += buffer.length; } return buf; } function encodeBase64(input) { if (Uint8Array.prototype.toBase64) { return input.toBase64(); } const CHUNK_SIZE = 0x8000; const arr = []; for (let i = 0; i < input.length; i += CHUNK_SIZE) { arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE))); } return btoa(arr.join('')); } function decodeBase64(encoded) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(encoded); } const binary = atob(encoded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } function decode(input) { if (Uint8Array.fromBase64) { return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), { alphabet: 'base64url', }); } let encoded = input; if (encoded instanceof Uint8Array) { encoded = decoder.decode(encoded); } encoded = encoded.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''); try { return decodeBase64(encoded); } catch { throw new TypeError('The input to be decoded is not correctly encoded.'); } } function encode(input) { let unencoded = input; if (typeof unencoded === 'string') { unencoded = encoder.encode(unencoded); } if (Uint8Array.prototype.toBase64) { return unencoded.toBase64({ alphabet: 'base64url', omitPadding: true }); } return encodeBase64(unencoded).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); } class JOSEError extends Error { static code = 'ERR_JOSE_GENERIC'; code = 'ERR_JOSE_GENERIC'; constructor(message, options) { super(message, options); this.name = this.constructor.name; Error.captureStackTrace?.(this, this.constructor); } } class JWTClaimValidationFailed extends JOSEError { static code = 'ERR_JWT_CLAIM_VALIDATION_FAILED'; code = 'ERR_JWT_CLAIM_VALIDATION_FAILED'; claim; reason; payload; constructor(message, payload, claim = 'unspecified', reason = 'unspecified') { super(message, { cause: { claim, reason, payload } }); this.claim = claim; this.reason = reason; this.payload = payload; } } class JWTExpired extends JOSEError { static code = 'ERR_JWT_EXPIRED'; code = 'ERR_JWT_EXPIRED'; claim; reason; payload; constructor(message, payload, claim = 'unspecified', reason = 'unspecified') { super(message, { cause: { claim, reason, payload } }); this.claim = claim; this.reason = reason; this.payload = payload; } } class JOSENotSupported extends JOSEError { static code = 'ERR_JOSE_NOT_SUPPORTED'; code = 'ERR_JOSE_NOT_SUPPORTED'; } class JWSInvalid extends JOSEError { static code = 'ERR_JWS_INVALID'; code = 'ERR_JWS_INVALID'; } class JWTInvalid extends JOSEError { static code = 'ERR_JWT_INVALID'; code = 'ERR_JWT_INVALID'; } class JWSSignatureVerificationFailed extends JOSEError { static code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED'; code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED'; constructor(message = 'signature verification failed', options) { super(message, options); } } function unusable(name, prop = 'algorithm.name') { return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`); } function isAlgorithm(algorithm, name) { return algorithm.name === name; } function getHashLength(hash) { return parseInt(hash.name.slice(4), 10); } function getNamedCurve(alg) { switch (alg) { case 'ES256': return 'P-256'; case 'ES384': return 'P-384'; case 'ES512': return 'P-521'; default: throw new Error('unreachable'); } } function checkUsage(key, usage) { if (usage && !key.usages.includes(usage)) { throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`); } } function checkSigCryptoKey(key, alg, usage) { switch (alg) { case 'HS256': case 'HS384': case 'HS512': { if (!isAlgorithm(key.algorithm, 'HMAC')) throw unusable('HMAC'); const expected = parseInt(alg.slice(2), 10); const actual = getHashLength(key.algorithm.hash); if (actual !== expected) throw unusable(`SHA-${expected}`, 'algorithm.hash'); break; } case 'RS256': case 'RS384': case 'RS512': { if (!isAlgorithm(key.algorithm, 'RSASSA-PKCS1-v1_5')) throw unusable('RSASSA-PKCS1-v1_5'); const expected = parseInt(alg.slice(2), 10); const actual = getHashLength(key.algorithm.hash); if (actual !== expected) throw unusable(`SHA-${expected}`, 'algorithm.hash'); break; } case 'PS256': case 'PS384': case 'PS512': { if (!isAlgorithm(key.algorithm, 'RSA-PSS')) throw unusable('RSA-PSS'); const expected = parseInt(alg.slice(2), 10); const actual = getHashLength(key.algorithm.hash); if (actual !== expected) throw unusable(`SHA-${expected}`, 'algorithm.hash'); break; } case 'Ed25519': case 'EdDSA': { if (!isAlgorithm(key.algorithm, 'Ed25519')) throw unusable('Ed25519'); break; } case 'ES256': case 'ES384': case 'ES512': { if (!isAlgorithm(key.algorithm, 'ECDSA')) throw unusable('ECDSA'); const expected = getNamedCurve(alg); const actual = key.algorithm.namedCurve; if (actual !== expected) throw unusable(expected, 'algorithm.namedCurve'); break; } default: throw new TypeError('CryptoKey does not support this operation'); } checkUsage(key, usage); } function message(msg, actual, ...types) { types = types.filter(Boolean); if (types.length > 2) { const last = types.pop(); msg += `one of type ${types.join(', ')}, or ${last}.`; } else if (types.length === 2) { msg += `one of type ${types[0]} or ${types[1]}.`; } else { msg += `of type ${types[0]}.`; } if (actual == null) { msg += ` Received ${actual}`; } else if (typeof actual === 'function' && actual.name) { msg += ` Received function ${actual.name}`; } else if (typeof actual === 'object' && actual != null) { if (actual.constructor?.name) { msg += ` Received an instance of ${actual.constructor.name}`; } } return msg; } const invalidKeyInput = (actual, ...types) => { return message('Key must be ', actual, ...types); }; function withAlg(alg, actual, ...types) { return message(`Key for the ${alg} algorithm must be `, actual, ...types); } function isCryptoKey(key) { return key?.[Symbol.toStringTag] === 'CryptoKey'; } function isKeyObject(key) { return key?.[Symbol.toStringTag] === 'KeyObject'; } const isKeyLike = (key) => { return isCryptoKey(key) || isKeyObject(key); }; const isDisjoint = (...headers) => { const sources = headers.filter(Boolean); if (sources.length === 0 || sources.length === 1) { return true; } let acc; for (const header of sources) { const parameters = Object.keys(header); if (!acc || acc.size === 0) { acc = new Set(parameters); continue; } for (const parameter of parameters) { if (acc.has(parameter)) { return false; } acc.add(parameter); } } return true; }; function isObjectLike(value) { return typeof value === 'object' && value !== null; } const isObject = (input) => { if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') { return false; } if (Object.getPrototypeOf(input) === null) { return true; } let proto = input; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(input) === proto; }; const checkKeyLength = (alg, key) => { if (alg.startsWith('RS') || alg.startsWith('PS')) { const { modulusLength } = key.algorithm; if (typeof modulusLength !== 'number' || modulusLength < 2048) { throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`); } } }; function subtleMapping(jwk) { let algorithm; let keyUsages; switch (jwk.kty) { case 'RSA': { switch (jwk.alg) { case 'PS256': case 'PS384': case 'PS512': algorithm = { name: 'RSA-PSS', hash: `SHA-${jwk.alg.slice(-3)}` }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'RS256': case 'RS384': case 'RS512': algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${jwk.alg.slice(-3)}` }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'RSA-OAEP': case 'RSA-OAEP-256': case 'RSA-OAEP-384': case 'RSA-OAEP-512': algorithm = { name: 'RSA-OAEP', hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`, }; keyUsages = jwk.d ? ['decrypt', 'unwrapKey'] : ['encrypt', 'wrapKey']; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } case 'EC': { switch (jwk.alg) { case 'ES256': algorithm = { name: 'ECDSA', namedCurve: 'P-256' }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'ES384': algorithm = { name: 'ECDSA', namedCurve: 'P-384' }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'ES512': algorithm = { name: 'ECDSA', namedCurve: 'P-521' }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'ECDH-ES': case 'ECDH-ES+A128KW': case 'ECDH-ES+A192KW': case 'ECDH-ES+A256KW': algorithm = { name: 'ECDH', namedCurve: jwk.crv }; keyUsages = jwk.d ? ['deriveBits'] : []; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } case 'OKP': { switch (jwk.alg) { case 'Ed25519': case 'EdDSA': algorithm = { name: 'Ed25519' }; keyUsages = jwk.d ? ['sign'] : ['verify']; break; case 'ECDH-ES': case 'ECDH-ES+A128KW': case 'ECDH-ES+A192KW': case 'ECDH-ES+A256KW': algorithm = { name: jwk.crv }; keyUsages = jwk.d ? ['deriveBits'] : []; break; default: throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); } break; } default: throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value'); } return { algorithm, keyUsages }; } const importJWK = async (jwk) => { if (!jwk.alg) { throw new TypeError('"alg" argument is required when "jwk.alg" is not present'); } const { algorithm, keyUsages } = subtleMapping(jwk); const keyData = { ...jwk }; delete keyData.alg; delete keyData.use; return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages); }; const validateCrit = (Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) => { if (joseHeader.crit !== undefined && protectedHeader?.crit === undefined) { throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected'); } if (!protectedHeader || protectedHeader.crit === undefined) { return new Set(); } if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input !== 'string' || input.length === 0)) { throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present'); } let recognized; if (recognizedOption !== undefined) { recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]); } else { recognized = recognizedDefault; } for (const parameter of protectedHeader.crit) { if (!recognized.has(parameter)) { throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`); } if (joseHeader[parameter] === undefined) { throw new Err(`Extension Header Parameter "${parameter}" is missing`); } if (recognized.get(parameter) && protectedHeader[parameter] === undefined) { throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`); } } return new Set(protectedHeader.crit); }; function isJWK(key) { return isObject(key) && typeof key.kty === 'string'; } function isPrivateJWK(key) { return key.kty !== 'oct' && typeof key.d === 'string'; } function isPublicJWK(key) { return key.kty !== 'oct' && typeof key.d === 'undefined'; } function isSecretJWK(key) { return key.kty === 'oct' && typeof key.k === 'string'; } let cache; const handleJWK = async (key, jwk, alg, freeze = false) => { cache ||= new WeakMap(); let cached = cache.get(key); if (cached?.[alg]) { return cached[alg]; } const cryptoKey = await importJWK({ ...jwk, alg }); if (freeze) Object.freeze(key); if (!cached) { cache.set(key, { [alg]: cryptoKey }); } else { cached[alg] = cryptoKey; } return cryptoKey; }; const handleKeyObject = (keyObject, alg) => { cache ||= new WeakMap(); let cached = cache.get(keyObject); if (cached?.[alg]) { return cached[alg]; } const isPublic = keyObject.type === 'public'; const extractable = isPublic ? true : false; let cryptoKey; if (keyObject.asymmetricKeyType === 'x25519') { switch (alg) { case 'ECDH-ES': case 'ECDH-ES+A128KW': case 'ECDH-ES+A192KW': case 'ECDH-ES+A256KW': break; default: throw new TypeError('given KeyObject instance cannot be used for this algorithm'); } cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, isPublic ? [] : ['deriveBits']); } if (keyObject.asymmetricKeyType === 'ed25519') { if (alg !== 'EdDSA' && alg !== 'Ed25519') { throw new TypeError('given KeyObject instance cannot be used for this algorithm'); } cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [ isPublic ? 'verify' : 'sign', ]); } if (keyObject.asymmetricKeyType === 'rsa') { let hash; switch (alg) { case 'RSA-OAEP': hash = 'SHA-1'; break; case 'RS256': case 'PS256': case 'RSA-OAEP-256': hash = 'SHA-256'; break; case 'RS384': case 'PS384': case 'RSA-OAEP-384': hash = 'SHA-384'; break; case 'RS512': case 'PS512': case 'RSA-OAEP-512': hash = 'SHA-512'; break; default: throw new TypeError('given KeyObject instance cannot be used for this algorithm'); } if (alg.startsWith('RSA-OAEP')) { return keyObject.toCryptoKey({ name: 'RSA-OAEP', hash, }, extractable, isPublic ? ['encrypt'] : ['decrypt']); } cryptoKey = keyObject.toCryptoKey({ name: alg.startsWith('PS') ? 'RSA-PSS' : 'RSASSA-PKCS1-v1_5', hash, }, extractable, [isPublic ? 'verify' : 'sign']); } if (keyObject.asymmetricKeyType === 'ec') { const nist = new Map([ ['prime256v1', 'P-256'], ['secp384r1', 'P-384'], ['secp521r1', 'P-521'], ]); const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve); if (!namedCurve) { throw new TypeError('given KeyObject instance cannot be used for this algorithm'); } if (alg === 'ES256' && namedCurve === 'P-256') { cryptoKey = keyObject.toCryptoKey({ name: 'ECDSA', namedCurve, }, extractable, [isPublic ? 'verify' : 'sign']); } if (alg === 'ES384' && namedCurve === 'P-384') { cryptoKey = keyObject.toCryptoKey({ name: 'ECDSA', namedCurve, }, extractable, [isPublic ? 'verify' : 'sign']); } if (alg === 'ES512' && namedCurve === 'P-521') { cryptoKey = keyObject.toCryptoKey({ name: 'ECDSA', namedCurve, }, extractable, [isPublic ? 'verify' : 'sign']); } if (alg.startsWith('ECDH-ES')) { cryptoKey = keyObject.toCryptoKey({ name: 'ECDH', namedCurve, }, extractable, isPublic ? [] : ['deriveBits']); } } if (!cryptoKey) { throw new TypeError('given KeyObject instance cannot be used for this algorithm'); } if (!cached) { cache.set(keyObject, { [alg]: cryptoKey }); } else { cached[alg] = cryptoKey; } return cryptoKey; }; const normalizeKey = async (key, alg) => { if (key instanceof Uint8Array) { return key; } if (isCryptoKey(key)) { return key; } if (isKeyObject(key)) { if (key.type === 'secret') { return key.export(); } if ('toCryptoKey' in key && typeof key.toCryptoKey === 'function') { try { return handleKeyObject(key, alg); } catch (err) { if (err instanceof TypeError) { throw err; } } } let jwk = key.export({ format: 'jwk' }); return handleJWK(key, jwk, alg); } if (isJWK(key)) { if (key.k) { return decode(key.k); } return handleJWK(key, key, alg, true); } throw new Error('unreachable'); }; const tag = (key) => key?.[Symbol.toStringTag]; const jwkMatchesOp = (alg, key, usage) => { if (key.use !== undefined) { let expected; switch (usage) { case 'sign': case 'verify': expected = 'sig'; break; case 'encrypt': case 'decrypt': expected = 'enc'; break; } if (key.use !== expected) { throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`); } } if (key.alg !== undefined && key.alg !== alg) { throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`); } if (Array.isArray(key.key_ops)) { let expectedKeyOp; switch (true) { case usage === 'sign' || usage === 'verify': case alg === 'dir': case alg.includes('CBC-HS'): expectedKeyOp = usage; break; case alg.startsWith('PBES2'): expectedKeyOp = 'deriveBits'; break; case /^A\d{3}(?:GCM)?(?:KW)?$/.test(alg): if (!alg.includes('GCM') && alg.endsWith('KW')) { expectedKeyOp = usage === 'encrypt' ? 'wrapKey' : 'unwrapKey'; } else { expectedKeyOp = usage; } break; case usage === 'encrypt' && alg.startsWith('RSA'): expectedKeyOp = 'wrapKey'; break; case usage === 'decrypt': expectedKeyOp = alg.startsWith('RSA') ? 'unwrapKey' : 'deriveBits'; break; } if (expectedKeyOp && key.key_ops?.includes?.(expectedKeyOp) === false) { throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`); } } return true; }; const symmetricTypeCheck = (alg, key, usage) => { if (key instanceof Uint8Array) return; if (isJWK(key)) { if (isSecretJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`); } if (!isKeyLike(key)) { throw new TypeError(withAlg(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key', 'Uint8Array')); } if (key.type !== 'secret') { throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`); } }; const asymmetricTypeCheck = (alg, key, usage) => { if (isJWK(key)) { switch (usage) { case 'decrypt': case 'sign': if (isPrivateJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for this operation be a private JWK`); case 'encrypt': case 'verify': if (isPublicJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for this operation be a public JWK`); } } if (!isKeyLike(key)) { throw new TypeError(withAlg(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key')); } if (key.type === 'secret') { throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`); } if (key.type === 'public') { switch (usage) { case 'sign': throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`); case 'decrypt': throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`); } } if (key.type === 'private') { switch (usage) { case 'verify': throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`); case 'encrypt': throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`); } } }; const checkKeyType = (alg, key, usage) => { const symmetric = alg.startsWith('HS') || alg === 'dir' || alg.startsWith('PBES2') || /^A(?:128|192|256)(?:GCM)?(?:KW)?$/.test(alg) || /^A(?:128|192|256)CBC-HS(?:256|384|512)$/.test(alg); if (symmetric) { symmetricTypeCheck(alg, key, usage); } else { asymmetricTypeCheck(alg, key, usage); } }; const subtleAlgorithm = (alg, algorithm) => { const hash = `SHA-${alg.slice(-3)}`; switch (alg) { case 'HS256': case 'HS384': case 'HS512': return { hash, name: 'HMAC' }; case 'PS256': case 'PS384': case 'PS512': return { hash, name: 'RSA-PSS', saltLength: parseInt(alg.slice(-3), 10) >> 3 }; case 'RS256': case 'RS384': case 'RS512': return { hash, name: 'RSASSA-PKCS1-v1_5' }; case 'ES256': case 'ES384': case 'ES512': return { hash, name: 'ECDSA', namedCurve: algorithm.namedCurve }; case 'Ed25519': case 'EdDSA': return { name: 'Ed25519' }; default: throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`); } }; const getSignKey = async (alg, key, usage) => { if (key instanceof Uint8Array) { if (!alg.startsWith('HS')) { throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key')); } return crypto.subtle.importKey('raw', key, { hash: `SHA-${alg.slice(-3)}`, name: 'HMAC' }, false, [usage]); } checkSigCryptoKey(key, alg, usage); return key; }; const verify = async (alg, key, signature, data) => { const cryptoKey = await getSignKey(alg, key, 'verify'); checkKeyLength(alg, cryptoKey); const algorithm = subtleAlgorithm(alg, cryptoKey.algorithm); try { return await crypto.subtle.verify(algorithm, cryptoKey, signature, data); } catch { return false; } }; async function flattenedVerify(jws, key, options) { if (!isObject(jws)) { throw new JWSInvalid('Flattened JWS must be an object'); } if (jws.protected === undefined && jws.header === undefined) { throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members'); } if (jws.protected !== undefined && typeof jws.protected !== 'string') { throw new JWSInvalid('JWS Protected Header incorrect type'); } if (jws.payload === undefined) { throw new JWSInvalid('JWS Payload missing'); } if (typeof jws.signature !== 'string') { throw new JWSInvalid('JWS Signature missing or incorrect type'); } if (jws.header !== undefined && !isObject(jws.header)) { throw new JWSInvalid('JWS Unprotected Header incorrect type'); } let parsedProt = {}; if (jws.protected) { try { const protectedHeader = decode(jws.protected); parsedProt = JSON.parse(decoder.decode(protectedHeader)); } catch { throw new JWSInvalid('JWS Protected Header is invalid'); } } if (!isDisjoint(parsedProt, jws.header)) { throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint'); } const joseHeader = { ...parsedProt, ...jws.header, }; const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options?.crit, parsedProt, joseHeader); let b64 = true; if (extensions.has('b64')) { b64 = parsedProt.b64; if (typeof b64 !== 'boolean') { throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean'); } } const { alg } = joseHeader; if (typeof alg !== 'string' || !alg) { throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid'); } if (b64) { if (typeof jws.payload !== 'string') { throw new JWSInvalid('JWS Payload must be a string'); } } else if (typeof jws.payload !== 'string' && !(jws.payload instanceof Uint8Array)) { throw new JWSInvalid('JWS Payload must be a string or an Uint8Array instance'); } let resolvedKey = false; if (typeof key === 'function') { key = await key(parsedProt, jws); resolvedKey = true; } checkKeyType(alg, key, 'verify'); const data = concat(encoder.encode(jws.protected ?? ''), encoder.encode('.'), typeof jws.payload === 'string' ? encoder.encode(jws.payload) : jws.payload); let signature; try { signature = decode(jws.signature); } catch { throw new JWSInvalid('Failed to base64url decode the signature'); } const k = await normalizeKey(key, alg); const verified = await verify(alg, k, signature, data); if (!verified) { throw new JWSSignatureVerificationFailed(); } let payload; if (b64) { try { payload = decode(jws.payload); } catch { throw new JWSInvalid('Failed to base64url decode the payload'); } } else if (typeof jws.payload === 'string') { payload = encoder.encode(jws.payload); } else { payload = jws.payload; } const result = { payload }; if (jws.protected !== undefined) { result.protectedHeader = parsedProt; } if (jws.header !== undefined) { result.unprotectedHeader = jws.header; } if (resolvedKey) { return { ...result, key: k }; } return result; } async function compactVerify(jws, key, options) { if (jws instanceof Uint8Array) { jws = decoder.decode(jws); } if (typeof jws !== 'string') { throw new JWSInvalid('Compact JWS must be a string or Uint8Array'); } const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split('.'); if (length !== 3) { throw new JWSInvalid('Invalid Compact JWS'); } const verified = await flattenedVerify({ payload, protected: protectedHeader, signature }, key, options); const result = { payload: verified.payload, protectedHeader: verified.protectedHeader }; if (typeof key === 'function') { return { ...result, key: verified.key }; } return result; } const epoch = (date) => Math.floor(date.getTime() / 1000); const minute = 60; const hour = minute * 60; const day = hour * 24; const week = day * 7; const year = day * 365.25; const REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i; const secs = (str) => { const matched = REGEX.exec(str); if (!matched || (matched[4] && matched[1])) { throw new TypeError('Invalid time period format'); } const value = parseFloat(matched[2]); const unit = matched[3].toLowerCase(); let numericDate; switch (unit) { case 'sec': case 'secs': case 'second': case 'seconds': case 's': numericDate = Math.round(value); break; case 'minute': case 'minutes': case 'min': case 'mins': case 'm': numericDate = Math.round(value * minute); break; case 'hour': case 'hours': case 'hr': case 'hrs': case 'h': numericDate = Math.round(value * hour); break; case 'day': case 'days': case 'd': numericDate = Math.round(value * day); break; case 'week': case 'weeks': case 'w': numericDate = Math.round(value * week); break; default: numericDate = Math.round(value * year); break; } if (matched[1] === '-' || matched[4] === 'ago') { return -numericDate; } return numericDate; }; function validateInput(label, input) { if (!Number.isFinite(input)) { throw new TypeError(`Invalid ${label} input`); } return input; } const normalizeTyp = (value) => { if (value.includes('/')) { return value.toLowerCase(); } return `application/${value.toLowerCase()}`; }; const checkAudiencePresence = (audPayload, audOption) => { if (typeof audPayload === 'string') { return audOption.includes(audPayload); } if (Array.isArray(audPayload)) { return audOption.some(Set.prototype.has.bind(new Set(audPayload))); } return false; }; function validateClaimsSet(protectedHeader, encodedPayload, options = {}) { let payload; try { payload = JSON.parse(decoder.decode(encodedPayload)); } catch { } if (!isObject(payload)) { throw new JWTInvalid('JWT Claims Set must be a top-level JSON object'); } const { typ } = options; if (typ && (typeof protectedHeader.typ !== 'string' || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) { throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, 'typ', 'check_failed'); } const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options; const presenceCheck = [...requiredClaims]; if (maxTokenAge !== undefined) presenceCheck.push('iat'); if (audience !== undefined) presenceCheck.push('aud'); if (subject !== undefined) presenceCheck.push('sub'); if (issuer !== undefined) presenceCheck.push('iss'); for (const claim of new Set(presenceCheck.reverse())) { if (!(claim in payload)) { throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, 'missing'); } } if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) { throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, 'iss', 'check_failed'); } if (subject && payload.sub !== subject) { throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, 'sub', 'check_failed'); } if (audience && !checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) { throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, 'aud', 'check_failed'); } let tolerance; switch (typeof options.clockTolerance) { case 'string': tolerance = secs(options.clockTolerance); break; case 'number': tolerance = options.clockTolerance; break; case 'undefined': tolerance = 0; break; default: throw new TypeError('Invalid clockTolerance option type'); } const { currentDate } = options; const now = epoch(currentDate || new Date()); if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') { throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, 'iat', 'invalid'); } if (payload.nbf !== undefined) { if (typeof payload.nbf !== 'number') { throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, 'nbf', 'invalid'); } if (payload.nbf > now + tolerance) { throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, 'nbf', 'check_failed'); } } if (payload.exp !== undefined) { if (typeof payload.exp !== 'number') { throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, 'exp', 'invalid'); } if (payload.exp <= now - tolerance) { throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', 'check_failed'); } } if (maxTokenAge) { const age = now - payload.iat; const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge); if (age - tolerance > max) { throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, 'iat', 'check_failed'); } if (age < 0 - tolerance) { throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', 'check_failed'); } } return payload; } class JWTClaimsBuilder { #payload; constructor(payload) { if (!isObject(payload)) { throw new TypeError('JWT Claims Set MUST be an object'); } this.#payload = structuredClone(payload); } data() { return encoder.encode(JSON.stringify(this.#payload)); } get iss() { return this.#payload.iss; } set iss(value) { this.#payload.iss = value; } get sub() { return this.#payload.sub; } set sub(value) { this.#payload.sub = value; } get aud() { return this.#payload.aud; } set aud(value) { this.#payload.aud = value; } set jti(value) { this.#payload.jti = value; } set nbf(value) { if (typeof value === 'number') { this.#payload.nbf = validateInput('setNotBefore', value); } else if (value instanceof Date) { this.#payload.nbf = validateInput('setNotBefore', epoch(value)); } else { this.#payload.nbf = epoch(new Date()) + secs(value); } } set exp(value) { if (typeof value === 'number') { this.#payload.exp = validateInput('setExpirationTime', value); } else if (value instanceof Date) { this.#payload.exp = validateInput('setExpirationTime', epoch(value)); } else { this.#payload.exp = epoch(new Date()) + secs(value); } } set iat(value) { if (typeof value === 'undefined') { this.#payload.iat = epoch(new Date()); } else if (value instanceof Date) { this.#payload.iat = validateInput('setIssuedAt', epoch(value)); } else if (typeof value === 'string') { this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value)); } else { this.#payload.iat = validateInput('setIssuedAt', value); } } } async function jwtVerify(jwt, key, options) { const verified = await compactVerify(jwt, key, options); if (verified.protectedHeader.crit?.includes('b64') && verified.protectedHeader.b64 === false) { throw new JWTInvalid('JWTs MUST NOT use unencoded payload'); } const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options); const result = { payload, protectedHeader: verified.protectedHeader }; if (typeof key === 'function') { return { ...result, key: verified.key }; } return result; } const sign = async (alg, key, data) => { const cryptoKey = await getSignKey(alg, key, 'sign'); checkKeyLength(alg, cryptoKey); const signature = await crypto.subtle.sign(subtleAlgorithm(alg, cryptoKey.algorithm), cryptoKey, data); return new Uint8Array(signature); }; class FlattenedSign { #payload; #protectedHeader; #unprotectedHeader; constructor(payload) { if (!(payload instanceof Uint8Array)) { throw new TypeError('payload must be an instance of Uint8Array'); } this.#payload = payload; } setProtectedHeader(protectedHeader) { if (this.#protectedHeader) { throw new TypeError('setProtectedHeader can only be called once'); } this.#protectedHeader = protectedHeader; return this; } setUnprotectedHeader(unprotectedHeader) { if (this.#unprotectedHeader) { throw new TypeError('setUnprotectedHeader can only be called once'); } this.#unprotectedHeader = unprotectedHeader; return this; } async sign(key, options) { if (!this.#protectedHeader && !this.#unprotectedHeader) { throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()'); } if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader)) { throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint'); } const joseHeader = { ...this.#protectedHeader, ...this.#unprotectedHeader, }; const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options?.crit, this.#protectedHeader, joseHeader); let b64 = true; if (extensions.has('b64')) { b64 = this.#protectedHeader.b64; if (typeof b64 !== 'boolean') { throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean'); } } const { alg } = joseHeader; if (typeof alg !== 'string' || !alg) { throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid'); } checkKeyType(alg, key, 'sign'); let payload = this.#payload; if (b64) { payload = encoder.encode(encode(payload)); } let protectedHeader; if (this.#protectedHeader) { protectedHeader = encoder.encode(encode(JSON.stringify(this.#protectedHeader))); } else { protectedHeader = encoder.encode(''); } const data = concat(protectedHeader, encoder.encode('.'), payload); const k = await normalizeKey(key, alg); const signature = await sign(alg, k, data); const jws = { signature: encode(signature), payload: '', }; if (b64) { jws.payload = decoder.decode(payload); } if (this.#unprotectedHeader) { jws.header = this.#unprotectedHeader; } if (this.#protectedHeader) { jws.protected = decoder.decode(protectedHeader); } return jws; } } class CompactSign { #flattened; constructor(payload) { this.#flattened = new FlattenedSign(payload); } setProtectedHeader(protectedHeader) { this.#flattened.setProtectedHeader(protectedHeader); return this; } async sign(key, options) { const jws = await this.#flattened.sign(key, options); if (jws.payload === undefined) { throw new TypeError('use the flattened module for creating JWS with b64: false'); } return `${jws.protected}.${jws.payload}.${jws.signature}`; } } class SignJWT { #protectedHeader; #jwt; constructor(payload = {}) { this.#jwt = new JWTClaimsBuilder(payload); } setIssuer(issuer) { this.#jwt.iss = issuer; return this; } setSubject(subject) { this.#jwt.sub = subject; return this; } setAudience(audience) { this.#jwt.aud = audience; return this; } setJti(jwtId) { this.#jwt.jti = jwtId; return this; } setNotBefore(input) { this.#jwt.nbf = input; return this; } setExpirationTime(input) { this.#jwt.exp = input; return this; } setIssuedAt(input) { this.#jwt.iat = input; return this; } setProtectedHeader(protectedHeader) { this.#protectedHeader = protectedHeader; return this; } async sign(key, options) { const sig = new CompactSign(this.#jwt.data()); sig.setProtectedHeader(this.#protectedHeader); if (Array.isArray(this.#protectedHeader?.crit) && this.#protectedHeader.crit.includes('b64') && this.#protectedHeader.b64 === false) { throw new JWTInvalid('JWTs MUST NOT use unencoded payload'); } return sig.sign(key, options); } } class TokenService { static instance; SECRET_KEY = (() => { const viteEnv = (() => { try { return (0, eval)("import.meta")?.env; } catch { return void 0; } })(); const viteKey = viteEnv?.VITE_JWT_SECRET_KEY; const nodeKey = globalThis?.process?.env?.VITE_JWT_SECRET_KEY; const resolved = viteKey || nodeKey || ""; return resolved; })(); ACCESS_TOKEN_EXPIRATION = "30s"; REFRESH_TOKEN_EXPIRATION = "7d"; encoder = new TextEncoder(); constructor() { } static async getInstance() { if (!TokenService.instance) { TokenService.instance = new TokenService(); } return TokenService.instance; } async generateSessionToken(origin) { const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const accessToken = await new SignJWT({ origin, type: "access" }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(this.ACCESS_TOKEN_EXPIRATION).sign(secret); const refreshToken = await new SignJWT({ origin, type: "refresh" }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(this.REFRESH_TOKEN_EXPIRATION).sign(secret); return { accessToken, refreshToken }; } async validateToken(token, origin) { try { const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const { payload } = await jwtVerify(token, secret); return payload.origin === origin; } catch (error) { if (error?.code === "ERR_JWT_EXPIRED") { console.log("Token expiré"); return false; } console.error("Erreur de validation du token:", error); return false; } } async refreshAccessToken(refreshToken, origin) { try { const isValid = await this.validateToken(refreshToken, origin); if (!isValid) { return null; } const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY)); const { payload } = await jwtVerify(refreshToken, secret); if (payload.type !== "refresh") { return null; } const newAccessToken = await new SignJWT({ origin, type: "access" }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(this.ACCESS_TOKEN_EXPIRATION).sign(secret); return newAccessToken; } catch (error) { console.error("Erreur lors du refresh du token:", error); return null; } } } const loginHtml = "
\r\n

Create Account / New Session

\r\n
\r\n\r\n
\r\n
\r\n
Create an account
\r\n
Add a device for an existing memeber
\r\n
\r\n
\r\n\r\n
\r\n
\r\n
Create an account :
\r\n
\r\n \r\n \r\n
\r\n
\r\n
\r\n
Add a device for an existing member :
\r\n
\r\n \"QR\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n

Or

\r\n \r\n
Chose a member :
\r\n \r\n\r\n \r\n
\r\n
\r\n"; const loginScript = "import Routing from '../../services/modal.service';\r\nimport Services from '../../services/service';\r\nimport { addSubscription } from '../../utils/subscription.utils';\r\nimport { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji } from '../../utils/sp-address.utils';\r\nimport { getCorrectDOM } from '../../utils/html.utils';\r\nimport QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';\r\nimport { navigate, registerAllListeners } from '../../router';\r\n\r\nexport { QrScannerComponent };\r\nexport async function initHomePage(): Promise {\r\n console.log('INIT-HOME');\r\n const container = getCorrectDOM('login-4nk-component') as HTMLElement;\r\n container.querySelectorAll('.tab').forEach((tab) => {\r\n addSubscription(tab, 'click', () => {\r\n container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));\r\n tab.classList.add('active');\r\n\r\n container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));\r\n container.querySelector(`#${tab.getAttribute('data-tab') as string}`)?.classList.add('active');\r\n });\r\n });\r\n\r\n const service = await Services.getInstance();\r\n const spAddress = await service.getDeviceAddress();\r\n // generateQRCode(spAddress);\r\n generateCreateBtn();\r\n displayEmojis(spAddress);\r\n\r\n // Add this line to populate the select when the page loads\r\n await populateMemberSelect();\r\n}\r\n\r\n//// Modal\r\nexport async function openModal(myAddress: string, receiverAddress: string) {\r\n const router = await Routing.getInstance();\r\n router.openLoginModal(myAddress, receiverAddress);\r\n}\r\n\r\n// const service = await Services.getInstance()\r\n// service.setNotification()\r\n\r\nfunction scanDevice() {\r\n const container = getCorrectDOM('login-4nk-component') as HTMLElement;\r\n const scannerImg = container.querySelector('#scanner') as HTMLElement;\r\n if (scannerImg) scannerImg.style.display = 'none';\r\n const scannerQrCode = container.querySelector('.qr-code-scanner') as HTMLElement;\r\n if (scannerQrCode) scannerQrCode.style.display = 'block';\r\n const scanButton = container?.querySelector('#scan-btn') as HTMLElement;\r\n if (scanButton) scanButton.style.display = 'none';\r\n const reader = container?.querySelector('#qr-reader');\r\n if (reader) reader.innerHTML = '';\r\n}\r\n\r\nasync function populateMemberSelect() {\r\n const container = getCorrectDOM('login-4nk-component') as HTMLElement;\r\n const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement;\r\n\r\n if (!memberSelect) {\r\n console.error('Could not find memberSelect element');\r\n return;\r\n }\r\n\r\n const service = await Services.getInstance();\r\n const members = await service.getAllMembersSorted();\r\n\r\n for (const [processId, member] of Object.entries(members)) {\r\n const process = await service.getProcess(processId);\r\n let memberPublicName;\r\n\r\n if (process) {\r\n const publicMemberData = service.getPublicData(process);\r\n if (publicMemberData) {\r\n const extractedName = publicMemberData['memberPublicName'];\r\n if (extractedName !== undefined && extractedName !== null) {\r\n memberPublicName = extractedName;\r\n }\r\n }\r\n }\r\n\r\n if (!memberPublicName) {\r\n memberPublicName = 'Unnamed Member';\r\n }\r\n\r\n // Récupérer les emojis pour ce processId\r\n const emojis = await addressToEmoji(processId);\r\n\r\n const option = document.createElement('option');\r\n option.value = processId;\r\n option.textContent = `${memberPublicName} (${emojis})`;\r\n memberSelect.appendChild(option);\r\n }\r\n}\r\n\r\n(window as any).populateMemberSelect = populateMemberSelect;\r\n\r\n(window as any).scanDevice = scanDevice;\r\n"; const loginCss = ":host {\r\n --primary-color: #3a506b;\r\n /* Bleu métallique */\r\n --secondary-color: #b0bec5;\r\n /* Gris acier */\r\n --accent-color: #d68c45;\r\n /* Cuivre */\r\n font-family: Arial, sans-serif;\r\n height: 100vh;\r\n font-size: 16px;\r\n}\r\nbody {\r\n font-family: Arial, sans-serif;\r\n margin: 0;\r\n padding: 0;\r\n background-color: #f4f4f4;\r\n background-image: url(../assets/bgd.webp);\r\n background-repeat: no-repeat;\r\n background-size: cover;\r\n background-blend-mode: soft-light;\r\n height: 100vh;\r\n}\r\n.message {\r\n margin: 30px 0;\r\n font-size: 14px;\r\n overflow-wrap: anywhere;\r\n}\r\n\r\n.message strong {\r\n font-family: 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', sans-serif;\r\n font-size: 20px;\r\n}\r\n\r\n/** Modal Css */\r\n.modal {\r\n display: none;\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 3;\r\n}\r\n\r\n.modal-content {\r\n width: 55%;\r\n height: 30%;\r\n background-color: white;\r\n border-radius: 4px;\r\n padding: 20px;\r\n text-align: center;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n.modal-title {\r\n margin: 0;\r\n padding-bottom: 8px;\r\n width: 100%;\r\n font-size: 0.9rem;\r\n border-bottom: 1px solid #ccc;\r\n}\r\n\r\n.confirmation-box {\r\n /* margin-top: 20px; */\r\n align-content: center;\r\n width: 70%;\r\n height: 20%;\r\n /* padding: 20px; */\r\n font-size: 1.5em;\r\n color: #333333;\r\n top: 5%;\r\n position: relative;\r\n}\r\n\r\n.nav-wrapper {\r\n position: fixed;\r\n background: radial-gradient(circle, white, var(--primary-color));\r\n /* background-color: #CFD8DC; */\r\n display: flex;\r\n justify-content: flex-end;\r\n align-items: center;\r\n color: #37474f;\r\n height: 9vh;\r\n width: 100vw;\r\n left: 0;\r\n top: 0;\r\n box-shadow:\r\n 0px 8px 10px -5px rgba(0, 0, 0, 0.2),\r\n 0px 16px 24px 2px rgba(0, 0, 0, 0.14),\r\n 0px 6px 30px 5px rgba(0, 0, 0, 0.12);\r\n\r\n .nav-right-icons {\r\n display: flex;\r\n .notification-container {\r\n position: relative;\r\n display: inline-block;\r\n }\r\n .notification-bell,\r\n .burger-menu {\r\n z-index: 3;\r\n height: 20px;\r\n width: 20px;\r\n margin-right: 1rem;\r\n }\r\n .notification-badge {\r\n position: absolute;\r\n top: -0.7rem;\r\n left: -0.8rem;\r\n background-color: red;\r\n color: white;\r\n border-radius: 50%;\r\n padding: 2.5px 6px;\r\n font-size: 0.8rem;\r\n font-weight: bold;\r\n }\r\n }\r\n .notification-board {\r\n position: absolute;\r\n width: 20rem;\r\n min-height: 8rem;\r\n background-color: white;\r\n right: 0.5rem;\r\n display: none;\r\n border-radius: 4px;\r\n text-align: center;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\r\n display: none;\r\n\r\n .notification-element {\r\n padding: 0.8rem 0;\r\n width: 100%;\r\n &:hover {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n }\r\n .notification-element:not(:last-child) {\r\n border-bottom: 1px solid;\r\n }\r\n }\r\n}\r\n\r\n.brand-logo {\r\n height: 100%;\r\n width: 100vw;\r\n align-content: center;\r\n position: relative;\r\n display: flex;\r\n position: absolute;\r\n align-items: center;\r\n justify-content: center;\r\n text-align: center;\r\n font-size: 1.5em;\r\n font-weight: bold;\r\n}\r\n\r\n.container {\r\n text-align: center;\r\n display: grid;\r\n height: 100vh;\r\n grid-template-columns: repeat(7, 1fr);\r\n gap: 10px;\r\n grid-auto-rows: 10vh 15vh 1fr;\r\n}\r\n.title-container {\r\n grid-column: 2 / 7;\r\n grid-row: 2;\r\n}\r\n.page-container {\r\n grid-column: 2 / 7;\r\n grid-row: 3;\r\n justify-content: center;\r\n display: flex;\r\n padding: 1rem;\r\n box-sizing: border-box;\r\n max-height: 60vh;\r\n}\r\n\r\nh1 {\r\n font-size: 2em;\r\n margin: 20px 0;\r\n}\r\n@media only screen and (min-width: 600px) {\r\n .tab-container {\r\n display: none;\r\n }\r\n .page-container {\r\n display: flex;\r\n align-items: center;\r\n }\r\n .process-container {\r\n grid-column: 3 / 6;\r\n grid-row: 3;\r\n\r\n .card {\r\n min-width: 40vw;\r\n }\r\n }\r\n .separator {\r\n width: 2px;\r\n background-color: #78909c;\r\n height: 80%;\r\n margin: 0 0.5em;\r\n }\r\n .tab-content {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-evenly;\r\n align-items: center;\r\n height: 80%;\r\n }\r\n}\r\n\r\n@media only screen and (max-width: 600px) {\r\n .process-container {\r\n grid-column: 2 / 7;\r\n grid-row: 3;\r\n }\r\n .container {\r\n grid-auto-rows: 10vh 15vh 15vh 1fr;\r\n }\r\n .tab-container {\r\n grid-column: 1 / 8;\r\n grid-row: 3;\r\n }\r\n .page-container {\r\n grid-column: 2 / 7;\r\n grid-row: 4;\r\n }\r\n .separator {\r\n display: none;\r\n }\r\n .tabs {\r\n display: flex;\r\n flex-grow: 1;\r\n overflow: hidden;\r\n z-index: 1;\r\n border-bottom-style: solid;\r\n border-bottom-width: 1px;\r\n border-bottom-color: #e0e4d6;\r\n }\r\n\r\n .tab {\r\n flex: 1;\r\n text-align: center;\r\n padding: 10px 0;\r\n cursor: pointer;\r\n font-size: 1rem;\r\n color: #6200ea;\r\n &:hover {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n }\r\n .tab.active {\r\n border-bottom: 2px solid #6200ea;\r\n font-weight: bold;\r\n }\r\n\r\n .card.tab-content {\r\n display: none;\r\n }\r\n\r\n .tab-content.active {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n height: 80%;\r\n }\r\n .modal-content {\r\n width: 80%;\r\n height: 20%;\r\n }\r\n}\r\n\r\n.qr-code {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 200px;\r\n}\r\n\r\n.emoji-display {\r\n font-family: 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', sans-serif;\r\n font-size: 20px;\r\n}\r\n\r\n#emoji-display-2 {\r\n margin-top: 30px;\r\n font-family: 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', sans-serif;\r\n font-size: 20px;\r\n}\r\n\r\n#okButton {\r\n margin-bottom: 2em;\r\n cursor: pointer;\r\n background-color: #d0d0d7;\r\n color: white;\r\n border-style: none;\r\n border-radius: 5px;\r\n color: #000;\r\n padding: 2px;\r\n margin-top: 10px;\r\n}\r\n\r\n.pairing-request {\r\n font-family: 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', sans-serif;\r\n font-size: 14px;\r\n margin-top: 0px;\r\n}\r\n\r\n.create-btn {\r\n margin-bottom: 2em;\r\n cursor: pointer;\r\n background-color: #d0d0d7;\r\n color: white;\r\n border-style: none;\r\n border-radius: 5px;\r\n color: #000;\r\n padding: 2px;\r\n}\r\n\r\n.camera-card {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n flex-direction: column;\r\n /* height: 200px; */\r\n}\r\n\r\n.btn {\r\n display: inline-block;\r\n padding: 10px 20px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n text-align: center;\r\n border-radius: 5px;\r\n cursor: pointer;\r\n text-decoration: none;\r\n}\r\n\r\n.btn:hover {\r\n background-color: #3700b3;\r\n}\r\n\r\n.card {\r\n min-width: 300px;\r\n border: 1px solid #e0e0e0;\r\n border-radius: 8px;\r\n background-color: white;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\r\n box-sizing: border-box;\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n text-align: center;\r\n height: 60vh;\r\n justify-content: flex-start;\r\n padding: 1rem;\r\n overflow-y: auto;\r\n}\r\n\r\n.card-content {\r\n flex-grow: 1;\r\n flex-direction: column;\r\n display: flex;\r\n justify-content: flex-start;\r\n align-items: center;\r\n text-align: left;\r\n font-size: 0.8em;\r\n position: relative;\r\n left: 2vw;\r\n width: 90%;\r\n .process-title {\r\n font-weight: bold;\r\n padding: 1rem 0;\r\n }\r\n .process-element {\r\n padding: 0.4rem 0;\r\n &:hover {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n &.selected {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n }\r\n}\r\n\r\n.card-description {\r\n padding: 20px;\r\n font-size: 1rem;\r\n color: #333;\r\n width: 90%;\r\n height: 50px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n margin-bottom: 0px;\r\n}\r\n\r\n.card-action {\r\n width: 100%;\r\n}\r\n\r\n.menu-content {\r\n display: none;\r\n position: absolute;\r\n top: 3.4rem;\r\n right: 1rem;\r\n background-color: white;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\r\n border-radius: 5px;\r\n overflow: hidden;\r\n}\r\n\r\n.menu-content a {\r\n display: block;\r\n padding: 10px 20px;\r\n text-decoration: none;\r\n color: #333;\r\n border-bottom: 1px solid #e0e0e0;\r\n &:hover {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n}\r\n\r\n.menu-content a:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.qr-code-scanner {\r\n display: none;\r\n}\r\n\r\n/* QR READER */\r\n#qr-reader div {\r\n position: inherit;\r\n}\r\n\r\n#qr-reader div img {\r\n top: 15px;\r\n right: 25px;\r\n margin-top: 5px;\r\n}\r\n\r\n/* INPUT CSS **/\r\n.input-container {\r\n position: relative;\r\n width: 100%;\r\n background-color: #eceff1;\r\n}\r\n\r\n.input-field {\r\n width: 36vw;\r\n padding: 10px 0;\r\n font-size: 1rem;\r\n border: none;\r\n border-bottom: 1px solid #ccc;\r\n outline: none;\r\n background: transparent;\r\n transition: border-color 0.3s;\r\n}\r\n\r\n.input-field:focus {\r\n border-bottom: 2px solid #6200ea;\r\n}\r\n\r\n.input-label {\r\n position: absolute;\r\n margin-top: -0.5em;\r\n top: 0;\r\n left: 0;\r\n padding: 10px 0;\r\n font-size: 1rem;\r\n color: #999;\r\n pointer-events: none;\r\n transition:\r\n transform 0.3s,\r\n color 0.3s,\r\n font-size 0.3s;\r\n}\r\n\r\n.input-field:focus + .input-label,\r\n.input-field:not(:placeholder-shown) + .input-label {\r\n transform: translateY(-20px);\r\n font-size: 0.8em;\r\n color: #6200ea;\r\n}\r\n\r\n.input-underline {\r\n position: absolute;\r\n bottom: 0;\r\n left: 50%;\r\n width: 0;\r\n height: 2px;\r\n background-color: #6200ea;\r\n transition:\r\n width 0.3s,\r\n left 0.3s;\r\n}\r\n\r\n.input-field:focus ~ .input-underline {\r\n width: 100%;\r\n left: 0;\r\n}\r\n\r\n.dropdown-content {\r\n position: absolute;\r\n flex-direction: column;\r\n top: 100%;\r\n left: 0;\r\n width: 100%;\r\n max-height: 150px;\r\n overflow-y: auto;\r\n border: 1px solid #ccc;\r\n border-radius: 4px;\r\n background-color: white;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\r\n display: none;\r\n z-index: 1;\r\n}\r\n\r\n.dropdown-content span {\r\n padding: 10px;\r\n cursor: pointer;\r\n list-style: none;\r\n}\r\n\r\n.dropdown-content span:hover {\r\n background-color: #f0f0f0;\r\n}\r\n\r\n/** AUTOCOMPLETE **/\r\n\r\nselect[data-multi-select-plugin] {\r\n display: none !important;\r\n}\r\n\r\n.multi-select-component {\r\n width: 36vw;\r\n padding: 5px 0;\r\n font-size: 1rem;\r\n border: none;\r\n border-bottom: 1px solid #ccc;\r\n outline: none;\r\n background: transparent;\r\n display: flex;\r\n flex-direction: row;\r\n height: auto;\r\n width: 100%;\r\n -o-transition:\r\n border-color ease-in-out 0.15s,\r\n box-shadow ease-in-out 0.15s;\r\n transition:\r\n border-color ease-in-out 0.15s,\r\n box-shadow ease-in-out 0.15s;\r\n}\r\n\r\n.autocomplete-list {\r\n border-radius: 4px 0px 0px 4px;\r\n}\r\n\r\n.multi-select-component:focus-within {\r\n box-shadow: inset 0px 0px 0px 2px #78abfe;\r\n}\r\n\r\n.multi-select-component .btn-group {\r\n display: none !important;\r\n}\r\n\r\n.multiselect-native-select .multiselect-container {\r\n width: 100%;\r\n}\r\n\r\n.selected-processes {\r\n background-color: white;\r\n padding: 0.4em;\r\n}\r\n\r\n.selected-wrapper {\r\n -webkit-box-sizing: border-box;\r\n -moz-box-sizing: border-box;\r\n box-sizing: border-box;\r\n -webkit-border-radius: 3px;\r\n -moz-border-radius: 3px;\r\n border-radius: 3px;\r\n display: inline-block;\r\n border: 1px solid #d9d9d9;\r\n background-color: #ededed;\r\n white-space: nowrap;\r\n margin: 1px 5px 5px 0;\r\n height: 22px;\r\n vertical-align: top;\r\n cursor: default;\r\n}\r\n\r\n.selected-wrapper .selected-label {\r\n max-width: 514px;\r\n display: inline-block;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n padding-left: 4px;\r\n vertical-align: top;\r\n}\r\n\r\n.selected-wrapper .selected-close {\r\n display: inline-block;\r\n text-decoration: none;\r\n font-size: 14px;\r\n line-height: 1.49rem;\r\n margin-left: 5px;\r\n padding-bottom: 10px;\r\n height: 100%;\r\n vertical-align: top;\r\n padding-right: 4px;\r\n opacity: 0.2;\r\n color: #000;\r\n text-shadow: 0 1px 0 #fff;\r\n font-weight: 700;\r\n}\r\n\r\n.search-container {\r\n display: flex;\r\n flex-direction: row;\r\n}\r\n\r\n.search-container .selected-input {\r\n background: none;\r\n border: 0;\r\n height: 20px;\r\n width: 60px;\r\n padding: 0;\r\n margin-bottom: 6px;\r\n -webkit-box-shadow: none;\r\n box-shadow: none;\r\n}\r\n\r\n.search-container .selected-input:focus {\r\n outline: none;\r\n}\r\n\r\n.dropdown-icon.active {\r\n transform: rotateX(180deg);\r\n}\r\n\r\n.search-container .dropdown-icon {\r\n display: inline-block;\r\n padding: 10px 5px;\r\n position: absolute;\r\n top: 5px;\r\n right: 5px;\r\n width: 10px;\r\n height: 10px;\r\n border: 0 !important;\r\n /* needed */\r\n -webkit-appearance: none;\r\n -moz-appearance: none;\r\n /* SVG background image */\r\n background-image: url('data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2012%2012%22%3E%3Ctitle%3Edown-arrow%3C%2Ftitle%3E%3Cg%20fill%3D%22%23818181%22%3E%3Cpath%20d%3D%22M10.293%2C3.293%2C6%2C7.586%2C1.707%2C3.293A1%2C1%2C0%2C0%2C0%2C.293%2C4.707l5%2C5a1%2C1%2C0%2C0%2C0%2C1.414%2C0l5-5a1%2C1%2C0%2C1%2C0-1.414-1.414Z%22%20fill%3D%22%23818181%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E');\r\n background-position: center;\r\n background-size: 10px;\r\n background-repeat: no-repeat;\r\n}\r\n\r\n.search-container ul {\r\n position: absolute;\r\n list-style: none;\r\n padding: 0;\r\n z-index: 3;\r\n margin-top: 29px;\r\n width: 100%;\r\n right: 0px;\r\n background: #fff;\r\n border: 1px solid #ccc;\r\n border-top: none;\r\n border-bottom: none;\r\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\r\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\r\n}\r\n\r\n.search-container ul :focus {\r\n outline: none;\r\n}\r\n\r\n.search-container ul li {\r\n display: block;\r\n text-align: left;\r\n padding: 8px 29px 2px 12px;\r\n border-bottom: 1px solid #ccc;\r\n font-size: 14px;\r\n min-height: 31px;\r\n}\r\n\r\n.search-container ul li:first-child {\r\n border-top: 1px solid #ccc;\r\n border-radius: 4px 0px 0 0;\r\n}\r\n\r\n.search-container ul li:last-child {\r\n border-radius: 4px 0px 0 0;\r\n}\r\n\r\n.search-container ul li:hover.not-cursor {\r\n cursor: default;\r\n}\r\n\r\n.search-container ul li:hover {\r\n color: #333;\r\n background-color: #f0f0f0;\r\n border-color: #adadad;\r\n cursor: pointer;\r\n}\r\n\r\n/* Adding scrool to select options */\r\n.autocomplete-list {\r\n max-height: 130px;\r\n overflow-y: auto;\r\n}\r\n\r\n/**************************************** Process page card ******************************************************/\r\n.process-card {\r\n min-width: 300px;\r\n border: 1px solid #e0e0e0;\r\n border-radius: 8px;\r\n background-color: white;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n text-align: center;\r\n min-height: 40vh;\r\n max-height: 60vh;\r\n justify-content: space-between;\r\n padding: 1rem;\r\n overflow-y: auto;\r\n}\r\n\r\n.process-card-content {\r\n text-align: left;\r\n font-size: 0.8em;\r\n position: relative;\r\n left: 2vw;\r\n width: 90%;\r\n .process-title {\r\n font-weight: bold;\r\n padding: 1rem 0;\r\n }\r\n .process-element {\r\n padding: 0.4rem 0;\r\n &:hover {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n &.selected {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n }\r\n .selected-process-zone {\r\n background-color: rgba(26, 28, 24, 0.08);\r\n }\r\n}\r\n\r\n.process-card-description {\r\n padding: 20px;\r\n font-size: 1rem;\r\n color: #333;\r\n width: 90%;\r\n}\r\n\r\n.process-card-action {\r\n width: 100%;\r\n}\r\n\r\n/**************************************** Select Member Home Page ******************************************************/\r\n.custom-select {\r\n width: 100%;\r\n max-height: 150px;\r\n overflow-y: auto;\r\n direction: ltr;\r\n background-color: white;\r\n border: 1px solid #ccc;\r\n border-radius: 4px;\r\n margin: 10px 0;\r\n}\r\n\r\n.custom-select option {\r\n padding: 8px 12px;\r\n cursor: pointer;\r\n}\r\n\r\n.custom-select option:hover {\r\n background-color: #f0f0f0;\r\n}\r\n\r\n.custom-select::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.custom-select::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n}\r\n\r\n.custom-select::-webkit-scrollbar-thumb {\r\n background: #888;\r\n border-radius: 4px;\r\n}\r\n\r\n.custom-select::-webkit-scrollbar-thumb:hover {\r\n background: #555;\r\n}\r\n"; class e{constructor(a,b,c,d,f){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredCamera="environment";this._maxScansPerSecond=25;this._lastScanTimestamp=-1;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");c&&"object"===typeof c?this._onDecode=b:(c||d||f?console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future"):console.warn("Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."), this._legacyOnDecode=b);b="object"===typeof c?c:{};this._onDecodeError=b.onDecodeError||("function"===typeof c?c:this._onDecodeError);this._calculateScanRegion=b.calculateScanRegion||("function"===typeof d?d:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof c?c:"number"===typeof d?d:this._legacyCanvasSize;this._maxScansPerSecond=b.maxScansPerSecond||this._maxScansPerSecond;this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData= this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);this._updateOverlay=this._updateOverlay.bind(this);a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let h=!1;a.hidden&&(a.hidden=!1,h=!0);document.body.contains(a)||(document.body.appendChild(a),h=!0);c=a.parentElement;if(b.highlightScanRegion||b.highlightCodeOutline){d=!!b.overlay;this.$overlay=b.overlay||document.createElement("div");f=this.$overlay.style;f.position="absolute";f.display="none"; f.pointerEvents="none";this.$overlay.classList.add("scan-region-highlight");if(!d&&b.highlightScanRegion){this.$overlay.innerHTML='';try{this.$overlay.firstElementChild.animate({transform:["scale(.98)", "scale(1.01)"]},{duration:400,iterations:Infinity,direction:"alternate",easing:"ease-in-out"});}catch(m){}c.insertBefore(this.$overlay,this.$video.nextSibling);}b.highlightCodeOutline&&(this.$overlay.insertAdjacentHTML("beforeend",''),this.$codeOutlineHighlight=this.$overlay.lastElementChild);}this._scanRegion= this._calculateScanRegion(a);requestAnimationFrame(()=>{let m=window.getComputedStyle(a);"none"===m.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==m.visibility&&(a.style.setProperty("visibility","visible","important"),h=!0);h&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity="0",a.style.width="0",a.style.height="0",this.$overlay&&this.$overlay.parentElement&&this.$overlay.parentElement.removeChild(this.$overlay), delete this.$overlay,delete this.$codeOutlineHighlight);this.$overlay&&this._updateOverlay();});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);window.addEventListener("resize",this._updateOverlay);this._qrEnginePromise=e.createQrEngine();}static set WORKER_PATH(a){console.warn("Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions.");}static async hasCamera(){try{return !!(await e.listCameras(!1)).length}catch(a){return !1}}static async listCameras(a= !1){if(!navigator.mediaDevices)return [];let b=async()=>(await navigator.mediaDevices.enumerateDevices()).filter(d=>"videoinput"===d.kind),c;try{a&&(await b()).every(d=>!d.label)&&(c=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0}));}catch(d){}try{return (await b()).map((d,f)=>({id:d.deviceId,label:d.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{c&&(console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"),e._stopVideoStream(c));}}async hasFlash(){let a; try{if(this.$video.srcObject){if(!(this.$video.srcObject instanceof MediaStream))return !1;a=this.$video.srcObject;}else a=(await this._getCameraStream()).stream;return "torch"in a.getVideoTracks()[0].getSettings()}catch(b){return !1}finally{a&&a!==this.$video.srcObject&&(console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"),e._stopVideoStream(a));}}isFlashOn(){return this._flashOn}async toggleFlash(){this._flashOn?await this.turnFlashOff():await this.turnFlashOn();}async turnFlashOn(){if(!this._flashOn&& !this._destroyed&&(this._flashOn=!0,this._active&&!this._paused))try{if(!await this.hasFlash())throw "No flash available";await this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]});}catch(a){throw this._flashOn=!1,a;}}async turnFlashOff(){this._flashOn&&(this._flashOn=!1,await this._restartVideoStream());}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange", this._onVisibilityChange);window.removeEventListener("resize",this._updateOverlay);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close");}async start(){if(this._destroyed)throw Error("The QR scanner can not be started as it had been destroyed.");if(!this._active||this._paused)if("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),this._active=!0,!document.hidden)if(this._paused= !1,this.$video.srcObject)await this.$video.play();else try{let {stream:a,facingMode:b}=await this._getCameraStream();!this._active||this._paused?e._stopVideoStream(a):(this._setVideoMirror(b),this.$video.srcObject=a,await this.$video.play(),this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{})));}catch(a){if(!this._paused)throw this._active=!1,a;}}stop(){this.pause();this._active=!1;}async pause(a=!1){this._paused=!0;if(!this._active)return !0;this.$video.pause();this.$overlay&&(this.$overlay.style.display= "none");let b=()=>{this.$video.srcObject instanceof MediaStream&&(e._stopVideoStream(this.$video.srcObject),this.$video.srcObject=null);};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return !1;b();return !0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream());}static async scanImage(a,b,c,d,f=!1,h=!1){let m,n=!1;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b||"returnDetailedScanResult"in b)?(m=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1,n=!0):b||c||d||f||h?console.warn("You're using a deprecated api for scanImage which will be removed in the future."):console.warn("Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true.");b=!!c;try{let p,k;[c,p]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]); [d,k]=e._drawToCanvas(p,m,d,f);let q;if(c instanceof Worker){let g=c;b||e._postWorkerMessageSync(g,"inversionMode","both");q=await new Promise((l,v)=>{let w,u,r,y=-1;u=t=>{t.data.id===y&&(g.removeEventListener("message",u),g.removeEventListener("error",r),clearTimeout(w),null!==t.data.data?l({data:t.data.data,cornerPoints:e._convertPoints(t.data.cornerPoints,m)}):v(e.NO_QR_CODE_FOUND));};r=t=>{g.removeEventListener("message",u);g.removeEventListener("error",r);clearTimeout(w);v("Scanner error: "+(t? t.message||t:"Unknown Error"));};g.addEventListener("message",u);g.addEventListener("error",r);w=setTimeout(()=>r("timeout"),1E4);let x=k.getImageData(0,0,d.width,d.height);y=e._postWorkerMessageSync(g,"decode",x,[x.data.buffer]);});}else q=await Promise.race([new Promise((g,l)=>window.setTimeout(()=>l("Scanner error: timeout"),1E4)),(async()=>{try{var [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return {data:g.rawValue,cornerPoints:e._convertPoints(g.cornerPoints,m)}}catch(l){g=l.message||l; if(/not implemented|service unavailable/.test(g))return e._disableBarcodeDetector=!0,e.scanImage(a,{scanRegion:m,canvas:d,disallowCanvasResizing:f,alsoTryWithoutScanRegion:h});throw `Scanner error: ${g}`;}})()]);return n?q:q.data}catch(p){if(!m||!h)throw p;let k=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return n?k:k.data}finally{b||e._postWorkerMessage(c,"close");}}setGrayscaleWeights(a,b,c,d=!0){e._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b, blue:c,useIntegerApproximation:d});}setInversionMode(a){e._postWorkerMessage(this._qrEnginePromise,"inversionMode",a);}static async createQrEngine(a){a&&console.warn("Specifying a worker path is not required and not supported anymore.");a=()=>__vitePreload(() => import('./qr-scanner-worker.min-Dy0qkKA4.mjs'),true?[]:void 0).then(c=>c.createWorker());if(!(!e._disableBarcodeDetector&&"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")))return a();let b=navigator.userAgentData; return b&&b.brands.some(({brand:c})=>/Chromium/i.test(c))&&/mac ?OS/i.test(b.platform)&&await b.getHighEntropyValues(["architecture","platformVersion"]).then(({architecture:c,platformVersion:d})=>/arm/i.test(c||"arm")&&13<=parseInt(d||"13")).catch(()=>!0)?a():new BarcodeDetector({formats:["qr_code"]})}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateOverlay();this.$overlay&&(this.$overlay.style.display="");this._scanFrame();}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video); this._updateOverlay();}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start();}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return {x:Math.round((a.videoWidth-b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,downScaledHeight:this._legacyCanvasSize}}_updateOverlay(){requestAnimationFrame(()=>{if(this.$overlay){var a=this.$video,b=a.videoWidth,c=a.videoHeight,d=a.offsetWidth,f=a.offsetHeight,h=a.offsetLeft, m=a.offsetTop,n=window.getComputedStyle(a),p=n.objectFit,k=b/c,q=d/f;switch(p){case "none":var g=b;var l=c;break;case "fill":g=d;l=f;break;default:("cover"===p?k>q:k{const x=parseFloat(r);return r.endsWith("%")?(y?f-l:d-g)*x/100:x});n=this._scanRegion.width||b;q=this._scanRegion.height||c;p=this._scanRegion.x||0;var u=this._scanRegion.y||0;k=this.$overlay.style;k.width= `${n/b*g}px`;k.height=`${q/c*l}px`;k.top=`${m+w+u/c*l}px`;c=/scaleX\(-1\)/.test(a.style.transform);k.left=`${h+(c?d-v-g:v)+(c?b-p-n:p)/b*g}px`;k.transform=a.style.transform;}});}static _convertPoints(a,b){if(!b)return a;let c=b.x||0,d=b.y||0,f=b.width&&b.downScaledWidth?b.width/b.downScaledWidth:1;b=b.height&&b.downScaledHeight?b.height/b.downScaledHeight:1;for(let h of a)h.x=h.x*f+c,h.y=h.y*b+d;return a}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||("requestVideoFrameCallback"in this.$video?this.$video.requestVideoFrameCallback.bind(this.$video):requestAnimationFrame)(async()=>{if(!(1>=this.$video.readyState)){var a=Date.now()-this._lastScanTimestamp,b=1E3/this._maxScansPerSecond;asetTimeout(d,b-a));this._lastScanTimestamp=Date.now();try{var c=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas});}catch(d){if(!this._active)return;this._onDecodeError(d);}!e._disableBarcodeDetector||await this._qrEnginePromise instanceof Worker||(this._qrEnginePromise=e.createQrEngine());c?(this._onDecode?this._onDecode(c):this._legacyOnDecode&&this._legacyOnDecode(c.data),this.$codeOutlineHighlight&&(clearTimeout(this._codeOutlineHighlightRemovalTimeout),this._codeOutlineHighlightRemovalTimeout=void 0,this.$codeOutlineHighlight.setAttribute("viewBox",`${this._scanRegion.x||0} `+`${this._scanRegion.y||0} `+`${this._scanRegion.width||this.$video.videoWidth} `+`${this._scanRegion.height||this.$video.videoHeight}`),this.$codeOutlineHighlight.firstElementChild.setAttribute("points", c.cornerPoints.map(({x:d,y:f})=>`${d},${f}`).join(" ")),this.$codeOutlineHighlight.style.display="")):this.$codeOutlineHighlight&&!this._codeOutlineHighlightRemovalTimeout&&(this._codeOutlineHighlightRemovalTimeout=setTimeout(()=>this.$codeOutlineHighlight.style.display="none",100));}this._scanFrame();});}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&console.log(a);}async _getCameraStream(){if(!navigator.mediaDevices)throw "Camera not found.";let a=/^(environment|user)$/.test(this._preferredCamera)?"facingMode": "deviceId",b=[{width:{min:1024}},{width:{min:768}},{}],c=b.map(d=>Object.assign({},d,{[a]:{exact:this._preferredCamera}}));for(let d of [...c,...b])try{let f=await navigator.mediaDevices.getUserMedia({video:d,audio:!1}),h=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return {stream:f,facingMode:h}}catch(f){}throw "Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start();}static _stopVideoStream(a){for(let b of a.getTracks())b.stop(), a.removeTrack(b);}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")";}_getFacingMode(a){return (a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b,c,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,m=b&&b.width?b.width:a.videoWidth||a.width,n=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:m,b=b&&b.downScaledHeight? b.downScaledHeight:n,c.width!==d&&(c.width=d),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,m,n,0,0,c.width,c.height);return [c,b]}static async _loadImage(a){if(a instanceof Image)return await e._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src);}}else throw "Unsupported image type.";}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=f=>{a.removeEventListener("load",d);a.removeEventListener("error",d);f instanceof ErrorEvent? c("Image load error"):b();};a.addEventListener("load",d);a.addEventListener("error",d);});}static async _postWorkerMessage(a,b,c,d){return e._postWorkerMessageSync(await a,b,c,d)}static _postWorkerMessageSync(a,b,c,d){if(!(a instanceof Worker))return -1;let f=e._workerMessageId++;a.postMessage({id:f,type:b,data:c},d);return f}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e._disableBarcodeDetector=!1;e._workerMessageId=0; async function prepareAndSendPairingTx() { const service = await Services.getInstance(); try { await service.checkConnections([]); } catch (e) { throw e; } try { const relayAddress = service.getAllRelays(); const createPairingProcessReturn = await service.createPairingProcess("", []); if (!createPairingProcessReturn.updated_process) { throw new Error("createPairingProcess returned an empty new process"); } service.setProcessId(createPairingProcessReturn.updated_process.process_id); service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id); await service.handleApiReturn(createPairingProcessReturn); } catch (err) { console.error(err); } } class QrScannerComponent extends HTMLElement { videoElement; wrapper; qrScanner; constructor() { super(); this.attachShadow({ mode: "open" }); this.wrapper = document.createElement("div"); this.wrapper.style.position = "relative"; this.wrapper.style.width = "150px"; this.wrapper.style.height = "150px"; this.videoElement = document.createElement("video"); this.videoElement.style.width = "100%"; document.body?.append(this.wrapper); this.wrapper.prepend(this.videoElement); } connectedCallback() { this.initializeScanner(); } async initializeScanner() { if (!this.videoElement) { console.error("Video element not found!"); return; } console.log("🚀 ~ QrScannerComponent ~ initializeScanner ~ this.videoElement:", this.videoElement); this.qrScanner = new e(this.videoElement, (result) => this.onQrCodeScanned(result), { highlightScanRegion: true, highlightCodeOutline: true }); try { await e.hasCamera(); this.qrScanner.start(); this.videoElement.style = "height: 200px; width: 200px"; this.shadowRoot?.appendChild(this.wrapper); } catch (e) { console.error("No camera found or error starting the QR scanner", e); } } async onQrCodeScanned(result) { console.log(`QR Code detected:`, result); const data = result.data; const scannedUrl = new URL(data); const spAddress = scannedUrl.searchParams.get("sp_address"); if (spAddress) { try { await prepareAndSendPairingTx(); } catch (e) { console.error("Failed to pair:", e); } } this.qrScanner.stop(); } disconnectedCallback() { if (this.qrScanner) { this.qrScanner.destroy(); } } } customElements.define("qr-scanner", QrScannerComponent); async function initHomePage() { console.log("INIT-HOME"); const container = getCorrectDOM("login-4nk-component"); container.querySelectorAll(".tab").forEach((tab) => { addSubscription(tab, "click", () => { container.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); tab.classList.add("active"); container.querySelectorAll(".tab-content").forEach((content) => content.classList.remove("active")); container.querySelector(`#${tab.getAttribute("data-tab")}`)?.classList.add("active"); }); }); const service = await Services.getInstance(); const spAddress = await service.getDeviceAddress(); generateCreateBtn(); displayEmojis(spAddress); await populateMemberSelect(); } function scanDevice() { const container = getCorrectDOM("login-4nk-component"); const scannerImg = container.querySelector("#scanner"); if (scannerImg) scannerImg.style.display = "none"; const scannerQrCode = container.querySelector(".qr-code-scanner"); if (scannerQrCode) scannerQrCode.style.display = "block"; const scanButton = container?.querySelector("#scan-btn"); if (scanButton) scanButton.style.display = "none"; const reader = container?.querySelector("#qr-reader"); if (reader) reader.innerHTML = ""; } async function populateMemberSelect() { const container = getCorrectDOM("login-4nk-component"); const memberSelect = container.querySelector("#memberSelect"); if (!memberSelect) { console.error("Could not find memberSelect element"); return; } const service = await Services.getInstance(); const members = await service.getAllMembersSorted(); for (const [processId, member] of Object.entries(members)) { const process = await service.getProcess(processId); let memberPublicName; if (process) { const publicMemberData = service.getPublicData(process); if (publicMemberData) { const extractedName = publicMemberData["memberPublicName"]; if (extractedName !== void 0 && extractedName !== null) { memberPublicName = extractedName; } } } if (!memberPublicName) { memberPublicName = "Unnamed Member"; } const emojis = await addressToEmoji(processId); const option = document.createElement("option"); option.value = processId; option.textContent = `${memberPublicName} (${emojis})`; memberSelect.appendChild(option); } } window.populateMemberSelect = populateMemberSelect; window.scanDevice = scanDevice; class LoginComponent extends HTMLElement { _callback; constructor() { super(); this.attachShadow({ mode: "open" }); } connectedCallback() { console.log("CALLBACK LOGIN PAGE"); this.render(); setTimeout(() => { initHomePage(); }, 500); } set callback(fn) { if (typeof fn === "function") { this._callback = fn; } else { console.error("Callback is not a function"); } } get callback() { return this._callback; } render() { if (this.shadowRoot) this.shadowRoot.innerHTML = ` ${loginHtml}