12 changed files with 2146 additions and 1870 deletions
@ -1,3 +1,4 @@ |
|||
{ |
|||
"extends": "@joepie91/eslint-config" |
|||
"extends": "@joepie91/eslint-config", |
|||
"ignorePatterns": "test/**" |
|||
} |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,55 @@ |
|||
"use strict"; |
|||
|
|||
const utf7 = require('utf7').imap; |
|||
|
|||
const RE_BACKSLASH = /\\/g; |
|||
const RE_DBLQUOTE = /"/g; |
|||
|
|||
function escape(str) { |
|||
return str.replace(RE_BACKSLASH, '\\\\').replace(RE_DBLQUOTE, '\\"'); |
|||
} |
|||
|
|||
module.exports = { |
|||
command: function (strings, ... interpolations) { |
|||
return { |
|||
toCommandString: function () { |
|||
let processedInterpolations = interpolations.map((interpolation) => { |
|||
let isObject = interpolation != null && typeof interpolation === "object"; |
|||
|
|||
if (isObject && interpolation._isRaw) { |
|||
return interpolation.string; |
|||
} else if (isObject && interpolation._is7Bit) { |
|||
return escape(interpolation.string); |
|||
} else if (typeof interpolation === "string") { |
|||
return escape(utf7.encode(interpolation)); |
|||
} else { |
|||
throw new Error(`Invalid input into command string: ${interpolation}`); |
|||
} |
|||
}); |
|||
|
|||
let combined = ""; |
|||
|
|||
strings.slice(0, -1).forEach((string, i) => { |
|||
combined += string; |
|||
combined += processedInterpolations[i]; |
|||
}); |
|||
|
|||
combined += strings[strings.length - 1]; |
|||
|
|||
return combined; |
|||
} |
|||
}; |
|||
}, |
|||
unsafeRaw: function (string) { |
|||
return { |
|||
_isRaw: true, |
|||
string: string |
|||
}; |
|||
}, |
|||
already7Bit: function (string) { |
|||
return { |
|||
_is7Bit: true, |
|||
string: string |
|||
}; |
|||
} |
|||
}; |
@ -0,0 +1,112 @@ |
|||
"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"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
}; |
@ -0,0 +1,32 @@ |
|||
"use strict"; |
|||
|
|||
// TODO: Make separate package
|
|||
|
|||
const pTry = require("p-try"); |
|||
|
|||
module.exports = function pInterval(interval, handler) { |
|||
let timer = setTimeout(doHandler, interval); |
|||
|
|||
function doHandler() { |
|||
let startTime = Date.now(); |
|||
|
|||
pTry(() => { |
|||
return handler(); |
|||
}).then(() => { |
|||
let timeElapsed = Date.now() - startTime(); |
|||
|
|||
let timeout = (timeElapsed < interval) |
|||
? interval - timeElapsed |
|||
: 1; // Be consistently async!
|
|||
|
|||
timer = setTimeout(doHandler, timeout); |
|||
}); |
|||
} |
|||
|
|||
return function cancelInterval() { |
|||
if (timer != null) { |
|||
clearTimeout(timer); |
|||
timer = null; |
|||
} |
|||
}; |
|||
}; |
@ -0,0 +1,13 @@ |
|||
"use strict"; |
|||
|
|||
module.exports = function pickObject(object, keys) { |
|||
let newObject = {}; |
|||
|
|||
for (let key of keys) { |
|||
if (key in object) { |
|||
newObject[key] = object[key]; |
|||
} |
|||
} |
|||
|
|||
return newObject; |
|||
}; |
@ -1,4 +1,5 @@ |
|||
require('fs').readdirSync(__dirname).forEach(function(f) { |
|||
// if (f === "test-connection-fetch-dup.js")
|
|||
if (f.substr(0, 5).toLowerCase() === 'test-') |
|||
require('./' + f); |
|||
}); |
|||
}); |
|||
|
Loading…
Reference in new issue