UNPKG

qrloop

Version:

Envelop big blob of data into frames that can be displayed in series of QR Codes

175 lines 6.62 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.framesToData = exports.areFramesComplete = exports.progressOfFrames = exports.currentNumberOfFrames = exports.totalNumberOfFrames = exports.parseFramesReducer = void 0; const md5_1 = __importDefault(require("md5")); const buffer_1 = require("buffer"); const Buffer_1 = require("./Buffer"); const constants_1 = require("./constants"); const initialState = { frames: [], fountainsQueue: [], exploredFountains: [], }; function resolveFountains(state) { if (!state) return state; const fountainsQueue = state.fountainsQueue.slice(0); const frames = state.frames.slice(0); if (fountainsQueue.length === 0 || frames.length === 0) return state; const { framesCount } = frames[0]; const framesByIndex = {}; for (let i = 0; i < frames.length; ++i) { const frame = frames[i]; framesByIndex[frame.index] = frame; } let i = 0; while (i < fountainsQueue.length) { const fountain = fountainsQueue[i]; const existingFramesData = []; const missing = []; for (let j = 0; j < fountain.frameIndexes.length; ++j) { const index = fountain.frameIndexes[j]; const f = framesByIndex[index]; if (f) { existingFramesData.push(f.data); } else { missing.push(index); } } if (existingFramesData.length > 0 && fountain.data.length !== Math.min(...existingFramesData.map((f) => f.length))) { // drop the fountain that no longer match the frames data length fountainsQueue.splice(i, 1); } else if (missing.length === 0) { // fountain useless, simply eat it and continue on same index // TODO we could assert the data is equal to xor to do a checksum. not sure to do if does not match fountainsQueue.splice(i, 1); } else if (missing.length === 1) { // found a frame to recover. rebuild it const [index] = missing; const recoveredData = (0, Buffer_1.xor)(existingFramesData.concat([fountain.data])); const head = buffer_1.Buffer.alloc(5); head.writeUInt8(0, 0); head.writeUInt16BE(framesCount, 1); head.writeUInt16BE(index, 3); const frame = { index, framesCount, data: recoveredData, }; frames.push(frame); framesByIndex[index] = frame; fountainsQueue.splice(i, 1); // we start over to see if there is no impacted fountains i = 0; } else { i++; } } return Object.assign(Object.assign({}, state), { frames, fountainsQueue }); } /** * reduce frames data array to add on more chunk to it. * As a user of this function, consider the frames to be a black box and use the available functions to extract things. */ function parseFramesReducer(_state, chunkStr) { const state = _state || initialState; const chunk = buffer_1.Buffer.from(chunkStr, "base64"); const head = chunk.slice(0, 5); const version = head.readUInt8(0); if (version === constants_1.FOUNTAIN_V1) { if (state.exploredFountains.includes(chunkStr)) return state; // no need to address again const exploredFountains = state.exploredFountains.concat(chunkStr); const k = chunk.readUInt16BE(1); const frameIndexes = []; for (let i = 0; i < k; ++i) { frameIndexes.push(chunk.readUInt16BE(3 + 2 * i)); } const data = chunk.slice(3 + 2 * k); const frames = state.frames; const fountain = { frameIndexes, data, }; const fountainsQueue = state.fountainsQueue.concat(fountain); return resolveFountains({ frames, fountainsQueue, exploredFountains, }); } if (version >= constants_1.MAX_NONCE) { throw new Error("version " + version + " not supported"); } const framesCount = head.readUInt16BE(1); const index = head.readUInt16BE(3); const data = chunk.slice(5); if (framesCount <= 0) { throw new Error("invalid framesCount"); } if (index < 0 || index >= framesCount) { throw new Error("invalid index"); } return resolveFountains(Object.assign(Object.assign({}, state), { frames: state.frames // override frame by index and also make sure all frames have same framesCount. this allows to not be stucked and recover any scenario. .filter((c) => c.index !== index && c.framesCount === framesCount) .concat({ framesCount, index, data }) })); } exports.parseFramesReducer = parseFramesReducer; /** * retrieve the total number of frames */ const totalNumberOfFrames = (s) => s && s.frames.length > 0 ? s.frames[0].framesCount : null; exports.totalNumberOfFrames = totalNumberOfFrames; /** * get the currently captured number of frames */ const currentNumberOfFrames = (s) => s ? s.frames.length : 0; exports.currentNumberOfFrames = currentNumberOfFrames; /** * get a progress value from 0 to 1 */ const progressOfFrames = (s) => { const total = (0, exports.totalNumberOfFrames)(s); if (!total) return 0; return (0, exports.currentNumberOfFrames)(s) / total; }; exports.progressOfFrames = progressOfFrames; /** * check if the frames have all been retrieved */ const areFramesComplete = (s) => (0, exports.totalNumberOfFrames)(s) === (0, exports.currentNumberOfFrames)(s); exports.areFramesComplete = areFramesComplete; /** * return final result of the frames. assuming you have checked `areFramesComplete` */ function framesToData(s) { if (!s) { throw new Error("invalid date: frames is undefined"); } const all = buffer_1.Buffer.concat(s.frames .slice(0) .sort((a, b) => a.index - b.index) .map((frame) => frame.data)); const length = all.readUInt32BE(0); const expectedMD5 = all.slice(4, 20).toString("hex"); const data = all.slice(20).slice(0, length); if ((0, md5_1.default)(data) !== expectedMD5) { throw new Error("invalid data: md5 doesn't match"); } return data; } exports.framesToData = framesToData; //# sourceMappingURL=importer.js.map