"use strict"; const events = require("events"); const syncpipe = require("syncpipe"); const defaultValue = require("default-value"); const splitFilter = require("split-filter"); const mapObject = require("map-obj"); const pickObject = require("./pick-object"); const RE_BODYPART = /^BODY\[/; const RE_ESCAPE = /\\\\/g; const FETCH_ATTR_MAP = { 'RFC822.SIZE': 'size', 'BODY': 'struct', 'BODYSTRUCTURE': 'struct', 'ENVELOPE': 'envelope', 'INTERNALDATE': 'date' }; function mapIncomingAttributeKey(key) { return defaultValue(FETCH_ATTR_MAP[key], key.toLowerCase()); } // FIXME: Get rid of the separate-emitter hackery here, and make the task itself an emitter instead, or find some other better way to wire things up module.exports = function createFetchTaskTracker() { let tasks = new Map(); return { get: function (id) { // TODO: Eventually make this fail hard, after the calling code has been fully refactored return tasks.get(id); }, create: function (id, keysToFetch) { let emitter = new events.EventEmitter(); let task = createFetchTask({ emitter, keysToFetch }); // FIXME: Delete after completion tasks.set(id, task); return task; }, getOrCreate: function (id, keysToFetch) { if (tasks.has(id)) { return this.get(id); } else { return this.create(id, keysToFetch); } } }; }; function createFetchTask({ emitter, keysToFetch }) { let attributes = {}; let endHandled = false; return { emitter: emitter, getRemainingKeys: function () { return keysToFetch; }, processFetchResponse: function (payload) { if (keysToFetch.length > 0) { let caseMappedPayload = mapObject(payload, (key, value) => { return [ key.toUpperCase(), value ]; }); let [ existingKeys, missingKeys ] = splitFilter(keysToFetch, (key) => caseMappedPayload[key] != null); let relevantKeys = existingKeys.filter((key) => RE_BODYPART.test(key) === false); let newAttributes = syncpipe(caseMappedPayload, [ (_) => pickObject(_, relevantKeys), (_) => mapObject(_, (key, value) => { return [ /* key */ mapIncomingAttributeKey(key), /* value */ (key === 'X-GM-LABELS') // TODO: Why is this special case needed? ? value.map((label) => String(label).replace(RE_ESCAPE, '\\')) : value ]; }) ]); Object.assign(attributes, newAttributes); keysToFetch = missingKeys; let isDone = (missingKeys.length === 0); if (isDone === true) { endHandled = true; // FIXME: Why nextTick? process.nextTick(function() { emitter.emit("attributes", attributes); emitter.emit("end"); }); } } else { if (endHandled === false) { endHandled = true; // FIXME: Why nextTick? process.nextTick(function() { emitter.emit("end"); }); } } } }; };