Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
3877 lines
No EOL
146 KiB
JavaScript
3877 lines
No EOL
146 KiB
JavaScript
define('ext/ext/equal', ['require'], function (require) {
|
|
var COMPARE_DEPTH = 6;
|
|
function boundedCmpObjs(a, b, depthLeft) {
|
|
var aAttrCount = 0, bAttrCount = 0, key, nextDepth = depthLeft - 1;
|
|
if ('toJSON' in a)
|
|
a = a.toJSON();
|
|
if ('toJSON' in b)
|
|
b = b.toJSON();
|
|
for (key in a) {
|
|
aAttrCount++;
|
|
if (!(key in b))
|
|
return false;
|
|
if (depthLeft) {
|
|
if (!equal(a[key], b[key], nextDepth))
|
|
return false;
|
|
} else {
|
|
if (a[key] !== b[key])
|
|
return false;
|
|
}
|
|
}
|
|
for (key in b) {
|
|
bAttrCount++;
|
|
}
|
|
if (aAttrCount !== bAttrCount)
|
|
return false;
|
|
return true;
|
|
}
|
|
function equal(a, b, depthLeft) {
|
|
if (depthLeft === undefined) {
|
|
depthLeft = COMPARE_DEPTH;
|
|
}
|
|
var ta = typeof a, tb = typeof b;
|
|
if (ta !== 'object' || tb !== ta || a == null || b == null)
|
|
return a === b;
|
|
if (a === b)
|
|
return true;
|
|
if (Array.isArray(a)) {
|
|
if (!Array.isArray(b))
|
|
return false;
|
|
if (a.length !== b.length)
|
|
return false;
|
|
for (var iArr = 0; iArr < a.length; iArr++) {
|
|
if (!equal(a[iArr], b[iArr], depthLeft - 1))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return boundedCmpObjs(a, b, depthLeft);
|
|
}
|
|
return equal;
|
|
});
|
|
;
|
|
define('ext/logic', [
|
|
'require',
|
|
'evt',
|
|
'./ext/equal'
|
|
], function (require) {
|
|
var evt = require('evt');
|
|
var equal = require('./ext/equal');
|
|
function logic() {
|
|
return logic.event.apply(logic, arguments);
|
|
}
|
|
evt.mix(logic);
|
|
logic.scope = function (namespace, defaultDetails) {
|
|
return new Scope(namespace, defaultDetails);
|
|
};
|
|
var objectToScope = new WeakMap();
|
|
function toScope(scope) {
|
|
if (!(scope instanceof Scope)) {
|
|
scope = objectToScope.get(scope);
|
|
if (!scope) {
|
|
throw new Error('Invalid scope ' + scope + ' passed to logic.event(); ' + 'did you remember to call logic.defineScope()? ' + new Error().stack);
|
|
}
|
|
}
|
|
return scope;
|
|
}
|
|
logic.defineScope = function (obj, namespace, defaultDetails) {
|
|
if (!namespace && obj && obj.constructor && obj.constructor.name) {
|
|
namespace = obj.constructor.name;
|
|
}
|
|
var scope = new Scope(namespace, defaultDetails);
|
|
objectToScope.set(obj, scope);
|
|
return scope;
|
|
};
|
|
logic.subscope = function (scope, defaultDetails) {
|
|
scope = toScope(scope);
|
|
return new Scope(scope.namespace, into(shallowClone(scope.defaultDetails), shallowClone(defaultDetails)));
|
|
};
|
|
logic.event = function (scope, type, details) {
|
|
scope = toScope(scope);
|
|
var isDefaultPrevented = false;
|
|
var preprocessEvent = {
|
|
scope: scope,
|
|
namespace: scope.namespace,
|
|
type: type,
|
|
details: details,
|
|
preventDefault: function () {
|
|
isDefaultPrevented = true;
|
|
}
|
|
};
|
|
logic.emit('preprocessEvent', preprocessEvent);
|
|
if (isDefaultPrevented) {
|
|
return { id: 0 };
|
|
}
|
|
type = preprocessEvent.type;
|
|
details = preprocessEvent.details;
|
|
if (typeof type !== 'string') {
|
|
throw new Error('Invalid "type" passed to logic.event(); ' + 'expected a string, got "' + type + '"');
|
|
}
|
|
if (scope.defaultDetails) {
|
|
if (isPlainObject(details)) {
|
|
details = into(shallowClone(scope.defaultDetails), shallowClone(details));
|
|
} else {
|
|
details = shallowClone(scope.defaultDetails);
|
|
}
|
|
} else {
|
|
details = shallowClone(details);
|
|
}
|
|
var event = new LogicEvent(scope, type, details);
|
|
logic.emit('censorEvent', event);
|
|
logic.emit('event', event);
|
|
if (logic.realtimeLogEverything) {
|
|
dump('logic: ' + event.toString() + '\n');
|
|
}
|
|
return event;
|
|
};
|
|
logic.underTest = false;
|
|
logic._currentTestRejectFunction = null;
|
|
logic.fail = function (ex) {
|
|
if (logic.underTest) {
|
|
if (logic._currentTestRejectFunction) {
|
|
logic._currentTestRejectFunction(ex);
|
|
} else {
|
|
throw ex;
|
|
}
|
|
} else {
|
|
console.error('Logic fail:', ex);
|
|
}
|
|
};
|
|
var nextId = 1;
|
|
logic.uniqueId = function () {
|
|
return nextId++;
|
|
};
|
|
logic.isCensored = false;
|
|
logic.realtimeLogEverything = false;
|
|
var interceptions = {};
|
|
logic.interceptable = function (type, fn) {
|
|
if (interceptions[type]) {
|
|
return interceptions[type]();
|
|
} else {
|
|
return fn();
|
|
}
|
|
};
|
|
logic.interceptOnce = function (type, replacementFn) {
|
|
var prevFn = interceptions[type];
|
|
interceptions[type] = function () {
|
|
interceptions[type] = prevFn;
|
|
return replacementFn();
|
|
};
|
|
};
|
|
logic.match = function (ns, type, detailPredicate) {
|
|
return new LogicMatcher(LogicMatcher.normalizeMatchArgs(ns, type, detailPredicate));
|
|
};
|
|
function MismatchError(matcher, event) {
|
|
this.matcher = matcher;
|
|
this.event = event;
|
|
}
|
|
MismatchError.prototype = Object.create(Error.prototype, {
|
|
constructor: { value: MismatchError },
|
|
toString: {
|
|
value: function () {
|
|
if (this.matcher.not) {
|
|
return 'MismatchError: expected ' + this.event + ' to not occur (failIfMatched ' + this.matcher + ').';
|
|
} else {
|
|
return 'MismatchError: expected ' + this.event + ' to match ' + JSON.stringify(this.matcher.detailPredicate) + '.';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
function LogicMatcher(opts) {
|
|
this.matchedLogs = opts.prevMatcher ? opts.prevMatcher.matchedLogs : [];
|
|
this.capturedLogs = [];
|
|
this.ns = opts.ns;
|
|
this.type = opts.type;
|
|
this.detailPredicate = opts.detailPredicate;
|
|
this.failOnMismatchedDetails = true;
|
|
this.not = opts.not;
|
|
this.timeoutMS = 2000;
|
|
this.resolved = false;
|
|
this.anotherMatcherNeedsMyLogs = false;
|
|
if (opts.prevMatcher) {
|
|
opts.prevMatcher.anotherMatcherNeedsMyLogs = true;
|
|
}
|
|
logic.defineScope(this, 'LogicMatcher');
|
|
var hasPrevPromise = !!opts.prevPromise;
|
|
var normalizedPrevPromise = opts.prevPromise || Promise.resolve();
|
|
if (this.not) {
|
|
this.promise = normalizedPrevPromise.then(() => {
|
|
this.capturedLogs.some(event => {
|
|
if ((!this.ns || event.namespace === this.ns) && event.matches(this.type, this.detailPredicate)) {
|
|
throw new MismatchError(this, event);
|
|
}
|
|
});
|
|
});
|
|
} else if (this.type) {
|
|
this.promise = new Promise((resolve, reject) => {
|
|
var subscribeToNextMatch = () => {
|
|
var timeoutId = setTimeout(() => {
|
|
logic(this, 'failedMatch', {
|
|
ns: this.ns,
|
|
type: this.type,
|
|
detailPredicate: this.detailPredicate,
|
|
capturedLogs: this.capturedLogs
|
|
});
|
|
reject(new Error('LogicMatcherTimeout: ' + this));
|
|
}, this.timeoutMS);
|
|
var resolveThisMatcher = event => {
|
|
this.resolved = true;
|
|
this.capturedLogs = [];
|
|
if (!this.anotherMatcherNeedsMyLogs) {
|
|
this.removeMatchListener();
|
|
}
|
|
};
|
|
var matchFn = event => {
|
|
this.capturedLogs.push(event);
|
|
if (this.resolved) {
|
|
return;
|
|
}
|
|
if (this.ns && event.namespace !== this.ns || event.type !== this.type) {
|
|
return false;
|
|
}
|
|
if (event.matches(this.type, this.detailPredicate)) {
|
|
resolveThisMatcher(event);
|
|
this.matchedLogs.push(event);
|
|
clearTimeout(timeoutId);
|
|
logic(this, 'match', {
|
|
ns: this.ns,
|
|
type: this.type,
|
|
event: event
|
|
});
|
|
resolve(event);
|
|
return true;
|
|
} else {
|
|
if (this.failOnMismatchedDetails) {
|
|
resolveThisMatcher(event);
|
|
reject(new MismatchError(this, event));
|
|
return true;
|
|
} else {
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.removeMatchListener = () => {
|
|
logic.removeListener('event', matchFn);
|
|
};
|
|
logic.on('event', matchFn);
|
|
if (opts.prevMatcher) {
|
|
var prevLogs = opts.prevMatcher.capturedLogs;
|
|
var matchIndex = prevLogs.findIndex(matchFn);
|
|
if (matchIndex !== -1) {
|
|
this.capturedLogs = prevLogs.slice(matchIndex + 1);
|
|
}
|
|
opts.prevMatcher.removeMatchListener();
|
|
}
|
|
};
|
|
if (hasPrevPromise) {
|
|
normalizedPrevPromise.then(subscribeToNextMatch, e => reject(e));
|
|
} else {
|
|
try {
|
|
subscribeToNextMatch();
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
this.promise = normalizedPrevPromise;
|
|
}
|
|
}
|
|
LogicMatcher.normalizeMatchArgs = function (ns, type, details) {
|
|
if (typeof type === 'object') {
|
|
details = type;
|
|
type = ns;
|
|
ns = null;
|
|
}
|
|
return {
|
|
ns: ns,
|
|
type: type,
|
|
detailPredicate: details
|
|
};
|
|
};
|
|
LogicMatcher.prototype = {
|
|
match(ns, type, details) {
|
|
var args = LogicMatcher.normalizeMatchArgs(ns, type, details);
|
|
args.prevMatcher = this;
|
|
args.prevPromise = this.promise;
|
|
return new LogicMatcher(args);
|
|
},
|
|
failIfMatched(ns, type, details) {
|
|
var args = LogicMatcher.normalizeMatchArgs(ns, type, details);
|
|
args.not = true;
|
|
args.prevMatcher = this;
|
|
args.prevPromise = this.promise;
|
|
return new LogicMatcher(args);
|
|
},
|
|
then(fn, catchFn) {
|
|
return new LogicMatcher({
|
|
prevPromise: this.promise.then(() => {
|
|
var ret = fn(this.matchedLogs.slice());
|
|
if (ret instanceof Promise) {
|
|
ret = new LogicMatcher({ prevPromise: ret });
|
|
}
|
|
return ret;
|
|
}, catchFn)
|
|
});
|
|
},
|
|
toString() {
|
|
return '<LogicMatcher ' + (this.ns ? this.ns + '/' : '') + this.type + ' ' + new ObjectSimplifier().simplify(this.detailPredicate) + '>';
|
|
}
|
|
};
|
|
function Scope(namespace, defaultDetails) {
|
|
this.namespace = namespace;
|
|
if (defaultDetails && !isPlainObject(defaultDetails)) {
|
|
throw new Error('Invalid defaultDetails; expected a plain-old object: ' + defaultDetails);
|
|
}
|
|
this.defaultDetails = defaultDetails;
|
|
}
|
|
function ObjectSimplifier(opts) {
|
|
opts = opts || {};
|
|
this.maxDepth = opts.maxDepth || 10;
|
|
this.maxStringLength = opts.maxStringLength || 1000;
|
|
this.maxArrayLength = opts.maxArrayLength || 1000;
|
|
this.maxObjectLength = opts.maxObjectLength || 10;
|
|
}
|
|
ObjectSimplifier.prototype = {
|
|
simplify: function (x) {
|
|
return this._simplify(x, 0, new WeakSet());
|
|
},
|
|
_simplify: function (x, depth, cacheSet) {
|
|
if (cacheSet.has(x)) {
|
|
return '(cycle)';
|
|
}
|
|
if (typeof x === 'number') {
|
|
return x;
|
|
} else if (typeof x === 'string') {
|
|
return x.slice(0, this.maxStringLength);
|
|
} else if (x && x.BYTES_PER_ELEMENT) {
|
|
return x.slice(0, this.maxArrayLength);
|
|
} else if (Array.isArray(x)) {
|
|
if (depth < this.maxDepth) {
|
|
return x.slice(0, this.maxArrayLength).map(element => this._simplify(element, depth + 1, cacheSet));
|
|
} else {
|
|
return '[Array length=' + x.length + ']';
|
|
}
|
|
} else if (x && typeof x === 'object') {
|
|
cacheSet.add(x);
|
|
if (!isPlainObject(x)) {
|
|
if (x.toJSON) {
|
|
return this._simplify(x.toJSON(), depth, cacheSet);
|
|
} else if (x.toString) {
|
|
return this._simplify(x.toString(), depth, cacheSet);
|
|
} else {
|
|
return '(?)';
|
|
}
|
|
} else {
|
|
if (depth < this.maxDepth) {
|
|
var retObj = {};
|
|
var idx = 0;
|
|
for (var key in x) {
|
|
if (idx > this.maxObjectLength) {
|
|
break;
|
|
}
|
|
retObj[key] = this._simplify(x[key], depth + 1, cacheSet);
|
|
idx++;
|
|
}
|
|
return retObj;
|
|
} else if (x.toString) {
|
|
return this._simplify(x.toString(), depth, cacheSet);
|
|
} else {
|
|
return '(object?)';
|
|
}
|
|
}
|
|
} else if (typeof x === 'function') {
|
|
return '(function)';
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
};
|
|
function LogicEvent(scope, type, details) {
|
|
if (!(scope instanceof Scope)) {
|
|
throw new Error('Invalid "scope" passed to LogicEvent(); ' + 'did you remember to call logic.defineScope()?');
|
|
}
|
|
this.scope = scope;
|
|
this.type = type;
|
|
this.details = details;
|
|
this.time = Date.now();
|
|
this.id = logic.uniqueId();
|
|
this.jsonRepresentation = {
|
|
namespace: this.scope.namespace,
|
|
type: this.type,
|
|
details: new ObjectSimplifier().simplify(this.details),
|
|
time: this.time,
|
|
id: this.id
|
|
};
|
|
}
|
|
LogicEvent.fromJSON = function (data) {
|
|
var event = new LogicEvent(new Scope(data.namespace), data.type, data.details);
|
|
event.time = data.time;
|
|
event.id = data.id;
|
|
return event;
|
|
};
|
|
LogicEvent.prototype = {
|
|
get namespace() {
|
|
return this.scope.namespace;
|
|
},
|
|
toJSON: function () {
|
|
return this.jsonRepresentation;
|
|
},
|
|
toString: function () {
|
|
return '<LogicEvent ' + this.namespace + '/' + this.type + ' ' + JSON.stringify(this.jsonRepresentation.details) + '>';
|
|
},
|
|
matches: function (type, detailPredicate) {
|
|
if (this.type !== type) {
|
|
return false;
|
|
}
|
|
if (typeof detailPredicate === 'function') {
|
|
return !!detailPredicate(this.details);
|
|
} else if (isPlainObject(detailPredicate)) {
|
|
for (var key in detailPredicate) {
|
|
var expected = detailPredicate && detailPredicate[key];
|
|
var actual = this.details && this.details[key];
|
|
if (actual === undefined) {
|
|
actual = null;
|
|
}
|
|
if (expected === undefined) {
|
|
continue;
|
|
} else if (!this.details || !equal(expected, actual)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else if (detailPredicate != null) {
|
|
return equal(this.details, detailPredicate);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
function isPlainObject(obj) {
|
|
if (!obj || typeof obj !== 'object') {
|
|
return false;
|
|
}
|
|
if (obj.toString && obj.toString() !== '[object Object]') {
|
|
return false;
|
|
}
|
|
for (var k in obj) {
|
|
if (typeof k === 'function') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
logic.isPlainObject = isPlainObject;
|
|
var promiseToStartEventMap = new WeakMap();
|
|
var promiseToResultEventMap = new WeakMap();
|
|
logic.startAsync = function (scope, type, details) {
|
|
var resolve, reject;
|
|
var promise = logic.async(scope, type, details, (_resolve, _reject) => {
|
|
resolve = _resolve;
|
|
reject = _reject;
|
|
});
|
|
return {
|
|
resolve: resolve,
|
|
reject: reject
|
|
};
|
|
};
|
|
logic.async = function (scope, type, details, fn) {
|
|
if (!fn && typeof details === 'function') {
|
|
fn = details;
|
|
details = null;
|
|
}
|
|
scope = logic.subscope(scope, details);
|
|
var startEvent;
|
|
var promise = new Promise((resolve, reject) => {
|
|
startEvent = logic(scope, 'begin ' + type, {
|
|
asyncStatus: 0,
|
|
asyncName: type
|
|
});
|
|
fn(result => {
|
|
promiseToResultEventMap.set(promise, logic(scope, type, {
|
|
asyncStatus: 1,
|
|
sourceEventIds: [startEvent.id],
|
|
result: result
|
|
}));
|
|
resolve(result);
|
|
}, error => {
|
|
promiseToResultEventMap.set(promise, logic(scope, type, {
|
|
asyncStatus: 2,
|
|
sourceEventIds: [startEvent.id],
|
|
error: error
|
|
}));
|
|
reject(error);
|
|
});
|
|
});
|
|
promiseToStartEventMap.set(promise, startEvent);
|
|
return promise;
|
|
};
|
|
logic.await = function (scope, type, details, promise) {
|
|
if (!promise && details.then) {
|
|
promise = details;
|
|
details = null;
|
|
}
|
|
scope = logic.subscope(scope, details).subscope(scope);
|
|
var startEvent = promiseToStartEventMap.get(promise);
|
|
var awaitEvent = logic.event(scope, 'await ' + type, {
|
|
awaitStatus: 0,
|
|
sourceEventIds: startEvent ? [startEvent.id] : null,
|
|
awaitName: type
|
|
});
|
|
return promise.then(result => {
|
|
var resultEvent = promiseToResultEventMap.get(promise);
|
|
logic(scope, type, {
|
|
awaitStatus: 1,
|
|
result: result,
|
|
sourceEventIds: resultEvent ? [
|
|
resultEvent.id,
|
|
awaitEvent.id
|
|
] : [awaitEvent.id]
|
|
});
|
|
return result;
|
|
}, error => {
|
|
var resultEvent = promiseToResultEventMap.get(promise);
|
|
logic(scope, type, {
|
|
awaitStatus: 2,
|
|
error: error,
|
|
sourceEventIds: resultEvent ? [
|
|
resultEvent.id,
|
|
awaitEvent.id
|
|
] : [awaitEvent.id]
|
|
});
|
|
throw error;
|
|
});
|
|
};
|
|
function shallowClone(x) {
|
|
if (isPlainObject(x)) {
|
|
var ret = {};
|
|
for (var key in x) {
|
|
ret[key] = x[key];
|
|
}
|
|
return ret;
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
function into(target, source) {
|
|
if (!target) {
|
|
target = {};
|
|
}
|
|
for (var key in source) {
|
|
target[key] = source[key];
|
|
}
|
|
return target;
|
|
}
|
|
return logic;
|
|
});
|
|
(function (root, factory) {
|
|
'use strict';
|
|
if (typeof define === 'function' && define.amd) {
|
|
define('ext/ext/addressparser', factory);
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory();
|
|
} else {
|
|
root.addressparser = factory();
|
|
}
|
|
}(this, function () {
|
|
'use strict';
|
|
var addressparser = {};
|
|
addressparser.parse = function (str) {
|
|
var tokenizer = new addressparser.Tokenizer(str), tokens = tokenizer.tokenize();
|
|
var addresses = [], address = [], parsedAddresses = [];
|
|
tokens.forEach(function (token) {
|
|
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
|
if (address.length) {
|
|
addresses.push(address);
|
|
}
|
|
address = [];
|
|
} else {
|
|
address.push(token);
|
|
}
|
|
});
|
|
if (address.length) {
|
|
addresses.push(address);
|
|
}
|
|
addresses.forEach(function (address) {
|
|
address = addressparser._handleAddress(address);
|
|
if (address.length) {
|
|
parsedAddresses = parsedAddresses.concat(address);
|
|
}
|
|
});
|
|
return parsedAddresses;
|
|
};
|
|
addressparser._handleAddress = function (tokens) {
|
|
var token, isGroup = false, state = 'text', address, addresses = [], data = {
|
|
address: [],
|
|
comment: [],
|
|
group: [],
|
|
text: []
|
|
}, i, len;
|
|
for (i = 0, len = tokens.length; i < len; i++) {
|
|
token = tokens[i];
|
|
if (token.type === 'operator') {
|
|
switch (token.value) {
|
|
case '<':
|
|
state = 'address';
|
|
break;
|
|
case '(':
|
|
state = 'comment';
|
|
break;
|
|
case ':':
|
|
state = 'group';
|
|
isGroup = true;
|
|
break;
|
|
default:
|
|
state = 'text';
|
|
}
|
|
} else {
|
|
if (token.value) {
|
|
data[state].push(token.value);
|
|
}
|
|
}
|
|
}
|
|
if (!data.text.length && data.comment.length) {
|
|
data.text = data.comment;
|
|
data.comment = [];
|
|
}
|
|
if (isGroup) {
|
|
data.text = data.text.join(' ');
|
|
addresses.push({
|
|
name: data.text || address && address.name,
|
|
group: data.group.length ? addressparser.parse(data.group.join(',')) : []
|
|
});
|
|
} else {
|
|
if (!data.address.length && data.text.length) {
|
|
for (i = data.text.length - 1; i >= 0; i--) {
|
|
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
|
data.address = data.text.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
var _regexHandler = function (address) {
|
|
if (!data.address.length) {
|
|
data.address = [address.trim()];
|
|
return ' ';
|
|
} else {
|
|
return address;
|
|
}
|
|
};
|
|
if (!data.address.length) {
|
|
for (i = data.text.length - 1; i >= 0; i--) {
|
|
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, _regexHandler).trim();
|
|
if (data.address.length) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!data.text.length && data.comment.length) {
|
|
data.text = data.comment;
|
|
data.comment = [];
|
|
}
|
|
if (data.address.length > 1) {
|
|
data.text = data.text.concat(data.address.splice(1));
|
|
}
|
|
data.text = data.text.join(' ');
|
|
data.address = data.address.join(' ');
|
|
if (!data.address && isGroup) {
|
|
return [];
|
|
} else {
|
|
address = {
|
|
address: data.address || data.text || '',
|
|
name: data.text || data.address || ''
|
|
};
|
|
if (address.address === address.name) {
|
|
if ((address.address || '').match(/@/)) {
|
|
address.name = '';
|
|
} else {
|
|
address.address = '';
|
|
}
|
|
}
|
|
addresses.push(address);
|
|
}
|
|
}
|
|
return addresses;
|
|
};
|
|
addressparser.Tokenizer = function (str) {
|
|
this.str = (str || '').toString();
|
|
this.operatorCurrent = '';
|
|
this.operatorExpecting = '';
|
|
this.node = null;
|
|
this.escaped = false;
|
|
this.list = [];
|
|
};
|
|
addressparser.Tokenizer.prototype.operators = {
|
|
'"': '"',
|
|
'(': ')',
|
|
'<': '>',
|
|
',': '',
|
|
':': ';',
|
|
';': ''
|
|
};
|
|
addressparser.Tokenizer.prototype.tokenize = function () {
|
|
var chr, list = [];
|
|
for (var i = 0, len = this.str.length; i < len; i++) {
|
|
chr = this.str.charAt(i);
|
|
this.checkChar(chr);
|
|
}
|
|
this.list.forEach(function (node) {
|
|
node.value = (node.value || '').toString().trim();
|
|
if (node.value) {
|
|
list.push(node);
|
|
}
|
|
});
|
|
return list;
|
|
};
|
|
addressparser.Tokenizer.prototype.checkChar = function (chr) {
|
|
if ((chr in this.operators || chr === '\\') && this.escaped) {
|
|
this.escaped = false;
|
|
} else if (this.operatorExpecting && chr === this.operatorExpecting) {
|
|
this.node = {
|
|
type: 'operator',
|
|
value: chr
|
|
};
|
|
this.list.push(this.node);
|
|
this.node = null;
|
|
this.operatorExpecting = '';
|
|
this.escaped = false;
|
|
return;
|
|
} else if (!this.operatorExpecting && chr in this.operators) {
|
|
this.node = {
|
|
type: 'operator',
|
|
value: chr
|
|
};
|
|
this.list.push(this.node);
|
|
this.node = null;
|
|
this.operatorExpecting = this.operators[chr];
|
|
this.escaped = false;
|
|
return;
|
|
}
|
|
if (!this.escaped && chr === '\\') {
|
|
this.escaped = true;
|
|
return;
|
|
}
|
|
if (!this.node) {
|
|
this.node = {
|
|
type: 'text',
|
|
value: ''
|
|
};
|
|
this.list.push(this.node);
|
|
}
|
|
if (this.escaped && chr !== '\\') {
|
|
this.node.value += '\\';
|
|
}
|
|
this.node.value += chr;
|
|
this.escaped = false;
|
|
};
|
|
return addressparser;
|
|
}));
|
|
define('ext/mailapi', [
|
|
'exports',
|
|
'./logic',
|
|
'./ext/addressparser'
|
|
], function (exports, logic, addressparser) {
|
|
function objCopy(obj) {
|
|
var copy = {};
|
|
Object.keys(obj).forEach(function (key) {
|
|
copy[key] = obj[key];
|
|
});
|
|
return copy;
|
|
}
|
|
function reportError() {
|
|
var msg = null;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
if (msg)
|
|
msg += ' ' + arguments[i];
|
|
else
|
|
msg = '' + arguments[i];
|
|
}
|
|
logic.fail(new Error(msg));
|
|
}
|
|
var unexpectedBridgeDataError = reportError, internalError = reportError, reportClientCodeError = reportError;
|
|
var HEADER_CACHE_LIMIT = 8;
|
|
function MailAccount(api, wireRep, acctsSlice) {
|
|
this._api = api;
|
|
this.id = wireRep.id;
|
|
this._wireRep = wireRep;
|
|
this.acctsSlice = acctsSlice;
|
|
this.type = wireRep.type;
|
|
this.name = wireRep.name;
|
|
this.syncRange = wireRep.syncRange;
|
|
this.syncInterval = wireRep.syncInterval;
|
|
this.notifyOnNew = wireRep.notifyOnNew;
|
|
this.playSoundOnSend = wireRep.playSoundOnSend;
|
|
this.enabled = wireRep.enabled;
|
|
this.problems = wireRep.problems;
|
|
this.identities = [];
|
|
for (var iIdent = 0; iIdent < wireRep.identities.length; iIdent++) {
|
|
this.identities.push(new MailSenderIdentity(this._api, wireRep.identities[iIdent]));
|
|
}
|
|
this.username = wireRep.credentials.username;
|
|
this.servers = wireRep.servers;
|
|
this.authMechanism = wireRep.credentials.oauth2 ? 'oauth2' : 'password';
|
|
this.element = null;
|
|
this.data = null;
|
|
}
|
|
MailAccount.prototype = {
|
|
toString: function () {
|
|
return '[MailAccount: ' + this.type + ' ' + this.id + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MailAccount',
|
|
accountType: this.type,
|
|
id: this.id
|
|
};
|
|
},
|
|
__update: function (wireRep) {
|
|
this.enabled = wireRep.enabled;
|
|
this.problems = wireRep.problems;
|
|
this.syncRange = wireRep.syncRange;
|
|
this.syncInterval = wireRep.syncInterval;
|
|
this.notifyOnNew = wireRep.notifyOnNew;
|
|
this.playSoundOnSend = wireRep.playSoundOnSend;
|
|
this._wireRep.defaultPriority = wireRep.defaultPriority;
|
|
for (var i = 0; i < wireRep.identities.length; i++) {
|
|
if (this.identities[i]) {
|
|
this.identities[i].__update(wireRep.identities[i]);
|
|
} else {
|
|
this.identities.push(new MailSenderIdentity(this._api, wireRep.identities[i]));
|
|
}
|
|
}
|
|
},
|
|
__die: function () {
|
|
},
|
|
clearProblems: function (callback) {
|
|
this._api._clearAccountProblems(this, callback);
|
|
},
|
|
modifyAccount: function (mods, callback) {
|
|
this._api._modifyAccount(this, mods, callback);
|
|
},
|
|
deleteAccount: function () {
|
|
this._api._deleteAccount(this);
|
|
},
|
|
get isDefault() {
|
|
if (!this.acctsSlice)
|
|
throw new Error('No account slice available');
|
|
return this.acctsSlice.defaultAccount === this;
|
|
}
|
|
};
|
|
function MailSenderIdentity(api, wireRep) {
|
|
this._api = api;
|
|
this.id = wireRep.id;
|
|
this.name = wireRep.name;
|
|
this.address = wireRep.address;
|
|
this.replyTo = wireRep.replyTo;
|
|
this.signature = wireRep.signature;
|
|
this.signatureEnabled = wireRep.signatureEnabled;
|
|
}
|
|
MailSenderIdentity.prototype = {
|
|
toString: function () {
|
|
return '[MailSenderIdentity: ' + this.type + ' ' + this.id + ']';
|
|
},
|
|
toJSON: function () {
|
|
return { type: 'MailSenderIdentity' };
|
|
},
|
|
__update: function (wireRep) {
|
|
this.id = wireRep.id;
|
|
this.name = wireRep.name;
|
|
this.address = wireRep.address;
|
|
this.replyTo = wireRep.replyTo;
|
|
this.signature = wireRep.signature;
|
|
this.signatureEnabled = wireRep.signatureEnabled;
|
|
},
|
|
modifyIdentity: function (mods, callback) {
|
|
if (typeof mods.signature !== 'undefined') {
|
|
this.signature = mods.signature;
|
|
}
|
|
if (typeof mods.signatureEnabled !== 'undefined') {
|
|
this.signatureEnabled = mods.signatureEnabled;
|
|
}
|
|
this._api._modifyIdentity(this, mods, callback);
|
|
},
|
|
__die: function () {
|
|
}
|
|
};
|
|
exports._MailFolder = MailFolder;
|
|
function MailFolder(api, wireRep) {
|
|
this._api = api;
|
|
this.__update(wireRep);
|
|
this.onchange = null;
|
|
this.onremove = null;
|
|
this.element = null;
|
|
this.data = null;
|
|
}
|
|
MailFolder.prototype = {
|
|
toString: function () {
|
|
return '[MailFolder: ' + this.path + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: this.type,
|
|
path: this.path
|
|
};
|
|
},
|
|
__update: function (wireRep) {
|
|
this._wireRep = wireRep;
|
|
this.unread = wireRep.unreadCount;
|
|
this.lastSyncedAt = wireRep.lastSyncedAt ? new Date(wireRep.lastSyncedAt) : null;
|
|
this.path = wireRep.path;
|
|
this.id = wireRep.id;
|
|
this.name = wireRep.name;
|
|
this.path = wireRep.path;
|
|
this.depth = wireRep.depth;
|
|
this.type = wireRep.type;
|
|
this.name = this._api.l10n_folder_name(this.name, this.type);
|
|
this.selectable = wireRep.type !== 'account' && wireRep.type !== 'nomail';
|
|
this.neededForHierarchy = !this.selectable;
|
|
switch (this.type) {
|
|
case 'localdrafts':
|
|
case 'outbox':
|
|
case 'account':
|
|
case 'nomail':
|
|
this.isValidMoveTarget = false;
|
|
break;
|
|
default:
|
|
this.isValidMoveTarget = true;
|
|
}
|
|
},
|
|
__die: function () {
|
|
}
|
|
};
|
|
function filterOutBuiltinFlags(flags) {
|
|
var outFlags = [];
|
|
for (var i = flags.length - 1; i >= 0; i--) {
|
|
if (flags[i][0] !== '\\')
|
|
outFlags.push(flags[i]);
|
|
}
|
|
return outFlags;
|
|
}
|
|
function serializeMessageName(x) {
|
|
return {
|
|
date: x.date.valueOf(),
|
|
suid: x.id,
|
|
guid: x.guid
|
|
};
|
|
}
|
|
var ContactCache = exports.ContactCache = {
|
|
_contactCache: Object.create(null),
|
|
_cacheHitEntries: 0,
|
|
_cacheEmptyEntries: 0,
|
|
MAX_CACHE_HITS: 256,
|
|
MAX_CACHE_EMPTY: 1024,
|
|
_livePeepsById: Object.create(null),
|
|
_livePeepsByEmail: Object.create(null),
|
|
pendingLookupCount: 0,
|
|
callbacks: [],
|
|
init: function () {
|
|
var contactsAPI = navigator.mozContacts;
|
|
if (!contactsAPI)
|
|
return;
|
|
contactsAPI.oncontactchange = this._onContactChange.bind(this);
|
|
},
|
|
_resetCache: function () {
|
|
this._contactCache = Object.create(null);
|
|
this._cacheHitEntries = 0;
|
|
this._cacheEmptyEntries = 0;
|
|
},
|
|
shutdown: function () {
|
|
var contactsAPI = navigator.mozContacts;
|
|
if (!contactsAPI)
|
|
return;
|
|
contactsAPI.oncontactchange = null;
|
|
},
|
|
_onContactChange: function (event) {
|
|
var contactsAPI = navigator.mozContacts;
|
|
var livePeepsById = this._livePeepsById, livePeepsByEmail = this._livePeepsByEmail;
|
|
if (this._cacheHitEntries || this._cacheEmptyEntries)
|
|
this._resetCache();
|
|
if (event.reason === 'remove') {
|
|
function cleanOutPeeps(livePeeps) {
|
|
for (var iPeep = 0; iPeep < livePeeps.length; iPeep++) {
|
|
var peep = livePeeps[iPeep];
|
|
peep.contactId = null;
|
|
if (peep.onchange) {
|
|
try {
|
|
peep.onchange(peep);
|
|
} catch (ex) {
|
|
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var livePeeps;
|
|
if (!event.contactID) {
|
|
for (var contactId in livePeepsById) {
|
|
livePeeps = livePeepsById[contactId];
|
|
cleanOutPeeps(livePeeps);
|
|
this._livePeepsById = Object.create(null);
|
|
}
|
|
} else {
|
|
livePeeps = livePeepsById[event.contactID];
|
|
if (livePeeps) {
|
|
cleanOutPeeps(livePeeps);
|
|
delete livePeepsById[event.contactID];
|
|
}
|
|
}
|
|
} else {
|
|
var req = contactsAPI.find({
|
|
filterBy: ['id'],
|
|
filterOp: 'equals',
|
|
filterValue: event.contactID
|
|
});
|
|
req.onsuccess = function () {
|
|
if (!req.result.length)
|
|
return;
|
|
var contact = req.result[0], livePeeps, iPeep, peep;
|
|
if (event.reason === 'update') {
|
|
livePeeps = livePeepsById[contact.id];
|
|
if (livePeeps) {
|
|
var contactEmails = contact.email ? contact.email.map(function (e) {
|
|
return e.value;
|
|
}) : [];
|
|
for (iPeep = 0; iPeep < livePeeps.length; iPeep++) {
|
|
peep = livePeeps[iPeep];
|
|
if (contactEmails.indexOf(peep.address) === -1) {
|
|
livePeeps.splice(iPeep--, 1);
|
|
peep.contactId = null;
|
|
if (peep.onchange) {
|
|
try {
|
|
peep.onchange(peep);
|
|
} catch (ex) {
|
|
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (livePeeps.length === 0)
|
|
delete livePeepsById[contact.id];
|
|
}
|
|
}
|
|
if (!contact.email)
|
|
return;
|
|
for (var iEmail = 0; iEmail < contact.email.length; iEmail++) {
|
|
var email = contact.email[iEmail].value;
|
|
livePeeps = livePeepsByEmail[email];
|
|
if (!livePeeps)
|
|
continue;
|
|
for (iPeep = 0; iPeep < livePeeps.length; iPeep++) {
|
|
peep = livePeeps[iPeep];
|
|
if (!peep.contactId) {
|
|
peep.contactId = contact.id;
|
|
var idLivePeeps = livePeepsById[peep.contactId];
|
|
if (idLivePeeps === undefined)
|
|
idLivePeeps = livePeepsById[peep.contactId] = [];
|
|
idLivePeeps.push(peep);
|
|
} else if (peep.contactId !== contact.id) {
|
|
continue;
|
|
}
|
|
if (contact.name && contact.name.length)
|
|
peep.name = contact.name[0];
|
|
if (peep.onchange) {
|
|
try {
|
|
peep.onchange(peep);
|
|
} catch (ex) {
|
|
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
},
|
|
resolvePeeps: function (addressPairs) {
|
|
if (addressPairs == null)
|
|
return null;
|
|
var resolved = [];
|
|
for (var i = 0; i < addressPairs.length; i++) {
|
|
resolved.push(this.resolvePeep(addressPairs[i]));
|
|
}
|
|
return resolved;
|
|
},
|
|
resolvePeep: function (addressPair) {
|
|
if (!addressPair) {
|
|
console.error('NO ADDRESS PAIR?', new Error().stack);
|
|
return;
|
|
}
|
|
var emailAddress = addressPair.address;
|
|
var entry = this._contactCache[emailAddress], contact, peep;
|
|
var contactsAPI = navigator.mozContacts;
|
|
if (entry === null || !contactsAPI) {
|
|
peep = new MailPeep(addressPair.name || '', emailAddress, null, null);
|
|
if (!contactsAPI)
|
|
return peep;
|
|
} else if (entry !== undefined) {
|
|
var name = addressPair.name || '';
|
|
if (entry.name && entry.name.length)
|
|
name = entry.name[0];
|
|
peep = new MailPeep(name, emailAddress, entry.id, entry.photo && entry.photo.length ? entry.photo[0] : null);
|
|
} else {
|
|
peep = new MailPeep(addressPair.name || '', emailAddress, null, null);
|
|
this._contactCache[emailAddress] = null;
|
|
this.pendingLookupCount++;
|
|
var filterValue = emailAddress ? emailAddress.toLowerCase() : '';
|
|
var req = contactsAPI.find({
|
|
filterBy: ['email'],
|
|
filterOp: 'equals',
|
|
filterValue: filterValue
|
|
});
|
|
var self = this, handleResult = function () {
|
|
if (req.result && req.result.length) {
|
|
var contact = req.result[0];
|
|
ContactCache._contactCache[emailAddress] = contact;
|
|
if (++ContactCache._cacheHitEntries > ContactCache.MAX_CACHE_HITS)
|
|
self._resetCache();
|
|
var peepsToFixup = self._livePeepsByEmail[emailAddress];
|
|
if (!peepsToFixup)
|
|
return;
|
|
for (var i = 0; i < peepsToFixup.length; i++) {
|
|
var peep = peepsToFixup[i];
|
|
if (!peep.contactId) {
|
|
peep.contactId = contact.id;
|
|
var livePeeps = self._livePeepsById[peep.contactId];
|
|
if (livePeeps === undefined)
|
|
livePeeps = self._livePeepsById[peep.contactId] = [];
|
|
livePeeps.push(peep);
|
|
}
|
|
if (contact.name && contact.name.length)
|
|
peep.name = contact.name[0];
|
|
if (contact.photo && contact.photo.length)
|
|
peep._thumbnailBlob = contact.photo[0];
|
|
if (!self.callbacks.length) {
|
|
if (peep.onchange) {
|
|
try {
|
|
peep.onchange(peep);
|
|
} catch (ex) {
|
|
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ContactCache._contactCache[emailAddress] = null;
|
|
if (++ContactCache._cacheEmptyEntries > ContactCache.MAX_CACHE_EMPTY)
|
|
self._resetCache();
|
|
}
|
|
if (--self.pendingLookupCount === 0) {
|
|
for (i = 0; i < ContactCache.callbacks.length; i++) {
|
|
ContactCache.callbacks[i]();
|
|
}
|
|
ContactCache.callbacks.splice(0, ContactCache.callbacks.length);
|
|
}
|
|
};
|
|
req.onsuccess = handleResult;
|
|
req.onerror = handleResult;
|
|
}
|
|
var livePeeps;
|
|
livePeeps = this._livePeepsByEmail[emailAddress];
|
|
if (livePeeps === undefined)
|
|
livePeeps = this._livePeepsByEmail[emailAddress] = [];
|
|
livePeeps.push(peep);
|
|
if (peep.contactId) {
|
|
livePeeps = this._livePeepsById[peep.contactId];
|
|
if (livePeeps === undefined)
|
|
livePeeps = this._livePeepsById[peep.contactId] = [];
|
|
livePeeps.push(peep);
|
|
}
|
|
return peep;
|
|
},
|
|
forgetPeepInstances: function () {
|
|
var livePeepsById = this._livePeepsById, livePeepsByEmail = this._livePeepsByEmail;
|
|
for (var iArg = 0; iArg < arguments.length; iArg++) {
|
|
var peeps = arguments[iArg];
|
|
if (!peeps)
|
|
continue;
|
|
for (var iPeep = 0; iPeep < peeps.length; iPeep++) {
|
|
var peep = peeps[iPeep], livePeeps, idx;
|
|
if (peep.contactId) {
|
|
livePeeps = livePeepsById[peep.contactId];
|
|
if (livePeeps) {
|
|
idx = livePeeps.indexOf(peep);
|
|
if (idx !== -1) {
|
|
livePeeps.splice(idx, 1);
|
|
if (livePeeps.length === 0)
|
|
delete livePeepsById[peep.contactId];
|
|
}
|
|
}
|
|
}
|
|
livePeeps = livePeepsByEmail[peep.address];
|
|
if (livePeeps) {
|
|
idx = livePeeps.indexOf(peep);
|
|
if (idx !== -1) {
|
|
livePeeps.splice(idx, 1);
|
|
if (livePeeps.length === 0)
|
|
delete livePeepsByEmail[peep.address];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
function revokeImageSrc() {
|
|
var useWin = this.ownerDocument.defaultView || window;
|
|
useWin.URL.revokeObjectURL(this.src);
|
|
}
|
|
function showBlobInImg(imgNode, blob) {
|
|
var useWin = imgNode.ownerDocument.defaultView || window;
|
|
imgNode.src = useWin.URL.createObjectURL(blob);
|
|
imgNode.addEventListener('load', revokeImageSrc);
|
|
}
|
|
function MailPeep(name, address, contactId, thumbnailBlob) {
|
|
this.name = name;
|
|
this.address = address;
|
|
this.contactId = contactId;
|
|
this._thumbnailBlob = thumbnailBlob;
|
|
this.element = null;
|
|
this.data = null;
|
|
this.type = null;
|
|
this.onchange = null;
|
|
}
|
|
MailPeep.prototype = {
|
|
get isContact() {
|
|
return this.contactId !== null;
|
|
},
|
|
toString: function () {
|
|
return '[MailPeep: ' + this.address + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
name: this.name,
|
|
address: this.address,
|
|
contactId: this.contactId
|
|
};
|
|
},
|
|
toWireRep: function () {
|
|
return {
|
|
name: this.name,
|
|
address: this.address
|
|
};
|
|
},
|
|
get hasPicture() {
|
|
return this._thumbnailBlob !== null;
|
|
},
|
|
displayPictureInImageTag: function (imgNode) {
|
|
if (this._thumbnailBlob)
|
|
showBlobInImg(imgNode, this._thumbnailBlob);
|
|
}
|
|
};
|
|
function MailHeader(slice, wireRep) {
|
|
this._slice = slice;
|
|
this._wireRep = wireRep;
|
|
this.id = wireRep.suid;
|
|
this.guid = wireRep.guid;
|
|
this.author = ContactCache.resolvePeep(wireRep.author);
|
|
this.to = ContactCache.resolvePeeps(wireRep.to);
|
|
this.cc = ContactCache.resolvePeeps(wireRep.cc);
|
|
this.bcc = ContactCache.resolvePeeps(wireRep.bcc);
|
|
this.replyTo = wireRep.replyTo;
|
|
this.date = new Date(wireRep.date);
|
|
this.__update(wireRep);
|
|
this.hasAttachments = wireRep.hasAttachments;
|
|
this.subject = wireRep.subject;
|
|
this.snippet = wireRep.snippet;
|
|
this.onchange = null;
|
|
this.onremove = null;
|
|
this.element = null;
|
|
this.data = null;
|
|
}
|
|
MailHeader.prototype = {
|
|
toString: function () {
|
|
return '[MailHeader: ' + this.id + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MailHeader',
|
|
id: this.id
|
|
};
|
|
},
|
|
makeCopy: function () {
|
|
return new MailHeader(this._slice, this._wireRep);
|
|
},
|
|
__update: function (wireRep) {
|
|
this._wireRep = wireRep;
|
|
if (wireRep.snippet !== null) {
|
|
this.snippet = wireRep.snippet;
|
|
}
|
|
this.isRead = wireRep.flags.indexOf('\\Seen') !== -1;
|
|
this.isStarred = wireRep.flags.indexOf('\\Flagged') !== -1;
|
|
this.isRepliedTo = wireRep.flags.indexOf('\\Answered') !== -1;
|
|
this.isForwarded = wireRep.flags.indexOf('$Forwarded') !== -1;
|
|
this.isJunk = wireRep.flags.indexOf('$Junk') !== -1;
|
|
this.tags = filterOutBuiltinFlags(wireRep.flags);
|
|
this.sendStatus = wireRep.sendStatus || {};
|
|
},
|
|
__die: function () {
|
|
ContactCache.forgetPeepInstances([this.author], this.to, this.cc, this.bcc);
|
|
},
|
|
deleteMessage: function () {
|
|
return this._slice._api.deleteMessages([this]);
|
|
},
|
|
moveMessage: function (targetFolder) {
|
|
return this._slice._api.moveMessages([this], targetFolder);
|
|
},
|
|
setRead: function (beRead) {
|
|
return this._slice._api.markMessagesRead([this], beRead);
|
|
},
|
|
setStarred: function (beStarred) {
|
|
return this._slice._api.markMessagesStarred([this], beStarred);
|
|
},
|
|
modifyTags: function (addTags, removeTags) {
|
|
return this._slice._api.modifyMessageTags([this], addTags, removeTags);
|
|
},
|
|
getBody: function (options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = null;
|
|
}
|
|
this._slice._api._getBodyForMessage(this, options, callback);
|
|
},
|
|
get bytesToDownloadForBodyDisplay() {
|
|
return this._wireRep.bytesToDownloadForBodyDisplay || 0;
|
|
},
|
|
editAsDraft: function (callback) {
|
|
var composer = this._slice._api.resumeMessageComposition(this, callback);
|
|
composer.hasDraft = true;
|
|
return composer;
|
|
},
|
|
replyToMessage: function (replyMode, callback) {
|
|
return this._slice._api.beginMessageComposition(this, null, {
|
|
replyTo: this,
|
|
replyMode: replyMode
|
|
}, callback);
|
|
},
|
|
forwardMessage: function (forwardMode, callback) {
|
|
return this._slice._api.beginMessageComposition(this, null, {
|
|
forwardOf: this,
|
|
forwardMode: forwardMode
|
|
}, callback);
|
|
}
|
|
};
|
|
function MailMatchedHeader(slice, wireRep) {
|
|
this.header = new MailHeader(slice, wireRep.header);
|
|
this.matches = wireRep.matches;
|
|
this.element = null;
|
|
this.data = null;
|
|
}
|
|
MailMatchedHeader.prototype = {
|
|
toString: function () {
|
|
return '[MailMatchedHeader: ' + this.header.id + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MailMatchedHeader',
|
|
id: this.header.id
|
|
};
|
|
},
|
|
__update: function (wireRep) {
|
|
this.matches = wireRep.matches;
|
|
this.header.__update(wireRep.header);
|
|
},
|
|
__die: function () {
|
|
this.header.__die();
|
|
}
|
|
};
|
|
function MailBody(api, suid, wireRep, handle) {
|
|
this._api = api;
|
|
this.id = suid;
|
|
this._date = wireRep.date;
|
|
this._handle = handle;
|
|
this.attachments = null;
|
|
if (wireRep.attachments) {
|
|
this.attachments = [];
|
|
for (var iAtt = 0; iAtt < wireRep.attachments.length; iAtt++) {
|
|
this.attachments.push(new MailAttachment(this, wireRep.attachments[iAtt]));
|
|
}
|
|
}
|
|
this._relatedParts = wireRep.relatedParts;
|
|
this.bodyReps = wireRep.bodyReps;
|
|
this._references = wireRep.references;
|
|
this.onchange = null;
|
|
this.ondead = null;
|
|
}
|
|
MailBody.prototype = {
|
|
toString: function () {
|
|
return '[MailBody: ' + this.id + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MailBody',
|
|
id: this.id
|
|
};
|
|
},
|
|
__update: function (wireRep, detail) {
|
|
this._relatedParts = wireRep.relatedParts;
|
|
this.bodyReps = wireRep.bodyReps;
|
|
if (detail && detail.changeDetails && detail.changeDetails.detachedAttachments) {
|
|
var indices = detail.changeDetails.detachedAttachments;
|
|
for (var iSplice = 0; iSplice < indices.length; iSplice++) {
|
|
this.attachments.splice(indices[iSplice], 1);
|
|
}
|
|
}
|
|
if (wireRep.attachments) {
|
|
var i, attachment;
|
|
for (i = 0; i < this.attachments.length; i++) {
|
|
attachment = this.attachments[i];
|
|
attachment.__update(wireRep.attachments[i]);
|
|
}
|
|
for (i = this.attachments.length; i < wireRep.attachments.length; i++) {
|
|
this.attachments.push(new MailAttachment(this, wireRep.attachments[i]));
|
|
}
|
|
}
|
|
},
|
|
get embeddedImageCount() {
|
|
if (!this._relatedParts)
|
|
return 0;
|
|
return this._relatedParts.length;
|
|
},
|
|
get bodyRepsDownloaded() {
|
|
var i = 0;
|
|
var len = this.bodyReps.length;
|
|
for (; i < len; i++) {
|
|
if (!this.bodyReps[i].isDownloaded) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
get embeddedImagesDownloaded() {
|
|
for (var i = 0; i < this._relatedParts.length; i++) {
|
|
var relatedPart = this._relatedParts[i];
|
|
if (!relatedPart.file)
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
downloadEmbeddedImages: function (callWhenDone, callOnProgress) {
|
|
var relPartIndices = [];
|
|
for (var i = 0; i < this._relatedParts.length; i++) {
|
|
var relatedPart = this._relatedParts[i];
|
|
if (relatedPart.file)
|
|
continue;
|
|
relPartIndices.push(i);
|
|
}
|
|
if (!relPartIndices.length) {
|
|
if (callWhenDone)
|
|
callWhenDone();
|
|
return;
|
|
}
|
|
this._api._downloadAttachments(this, relPartIndices, [], [], callWhenDone, callOnProgress);
|
|
},
|
|
showEmbeddedImages: function (htmlNode, loadCallback) {
|
|
var i, cidToBlob = {};
|
|
for (i = 0; i < this._relatedParts.length; i++) {
|
|
var relPart = this._relatedParts[i];
|
|
if (relPart.file && !Array.isArray(relPart.file))
|
|
cidToBlob[relPart.contentId] = relPart.file;
|
|
}
|
|
var nodes = htmlNode.querySelectorAll('.moz-embedded-image');
|
|
for (i = 0; i < nodes.length; i++) {
|
|
var node = nodes[i], cid = node.getAttribute('cid-src');
|
|
if (!cidToBlob.hasOwnProperty(cid))
|
|
continue;
|
|
showBlobInImg(node, cidToBlob[cid]);
|
|
if (loadCallback)
|
|
node.addEventListener('load', loadCallback, false);
|
|
node.removeAttribute('cid-src');
|
|
node.classList.remove('moz-embedded-image');
|
|
}
|
|
},
|
|
checkForExternalImages: function (htmlNode) {
|
|
var someNode = htmlNode.querySelector('.moz-external-image');
|
|
return someNode !== null;
|
|
},
|
|
showExternalImages: function (htmlNode, loadCallback) {
|
|
var nodes = htmlNode.querySelectorAll('.moz-external-image');
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var node = nodes[i];
|
|
if (loadCallback) {
|
|
node.addEventListener('load', loadCallback, false);
|
|
}
|
|
node.setAttribute('src', node.getAttribute('ext-src'));
|
|
node.removeAttribute('ext-src');
|
|
node.classList.remove('moz-external-image');
|
|
}
|
|
},
|
|
die: function () {
|
|
this.onchange = null;
|
|
this._api.__bridgeSend({
|
|
type: 'killBody',
|
|
id: this.id,
|
|
handle: this._handle
|
|
});
|
|
}
|
|
};
|
|
function MailAttachment(_body, wireRep) {
|
|
this._body = _body;
|
|
this.partId = wireRep.part;
|
|
this.filename = wireRep.name;
|
|
this.mimetype = wireRep.type;
|
|
this.sizeEstimateInBytes = wireRep.sizeEstimate;
|
|
this._file = wireRep.file;
|
|
this.element = null;
|
|
this.data = null;
|
|
}
|
|
MailAttachment.prototype = {
|
|
toString: function () {
|
|
return '[MailAttachment: "' + this.filename + '"]';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MailAttachment',
|
|
filename: this.filename
|
|
};
|
|
},
|
|
__update: function (wireRep) {
|
|
this.mimetype = wireRep.type;
|
|
this.sizeEstimateInBytes = wireRep.sizeEstimate;
|
|
this._file = wireRep.file;
|
|
},
|
|
get isDownloaded() {
|
|
return !!this._file;
|
|
},
|
|
get isDownloadable() {
|
|
return this.mimetype !== 'application/x-gelam-no-download';
|
|
},
|
|
download: function (callWhenDone, callOnProgress, registerWithDownloadManager) {
|
|
if (this.isDownloaded) {
|
|
callWhenDone();
|
|
return;
|
|
}
|
|
this._body._api._downloadAttachments(this._body, [], [this._body.attachments.indexOf(this)], [registerWithDownloadManager || false], callWhenDone, callOnProgress);
|
|
}
|
|
};
|
|
function UndoableOperation(_api, operation, affectedCount, _tempHandle, _longtermIds) {
|
|
this._api = _api;
|
|
this.operation = operation;
|
|
this.affectedCount = affectedCount;
|
|
this._tempHandle = _tempHandle;
|
|
this._longtermIds = null;
|
|
this._undoRequested = false;
|
|
}
|
|
UndoableOperation.prototype = {
|
|
toString: function () {
|
|
return '[UndoableOperation]';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'UndoableOperation',
|
|
handle: this._tempHandle,
|
|
longtermIds: this._longtermIds
|
|
};
|
|
},
|
|
undo: function () {
|
|
if (!this._longtermIds) {
|
|
this._undoRequested = true;
|
|
return;
|
|
}
|
|
this._api.__undo(this);
|
|
}
|
|
};
|
|
function BridgedViewSlice(api, ns, handle) {
|
|
this._api = api;
|
|
this._ns = ns;
|
|
this._handle = handle;
|
|
this.items = [];
|
|
this.status = 'new';
|
|
this.syncProgress = 0;
|
|
this.atTop = false;
|
|
this.atBottom = false;
|
|
this.userCanGrowUpwards = false;
|
|
this.userCanGrowDownwards = false;
|
|
this.pendingRequestCount = 0;
|
|
this._growing = 0;
|
|
this.onadd = null;
|
|
this.onchange = null;
|
|
this.onsplice = null;
|
|
this.onremove = null;
|
|
this.onstatus = null;
|
|
this.oncomplete = null;
|
|
this.ondead = null;
|
|
}
|
|
BridgedViewSlice.prototype = {
|
|
toString: function () {
|
|
return '[BridgedViewSlice: ' + this._ns + ' ' + this._handle + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'BridgedViewSlice',
|
|
namespace: this._ns,
|
|
handle: this._handle
|
|
};
|
|
},
|
|
requestShrinkage: function (firstUsedIndex, lastUsedIndex) {
|
|
this.pendingRequestCount++;
|
|
if (lastUsedIndex >= this.items.length)
|
|
lastUsedIndex = this.items.length - 1;
|
|
this._api.__bridgeSend({
|
|
type: 'shrinkSlice',
|
|
handle: this._handle,
|
|
firstIndex: firstUsedIndex,
|
|
firstSuid: this.items[firstUsedIndex].id,
|
|
lastIndex: lastUsedIndex,
|
|
lastSuid: this.items[lastUsedIndex].id
|
|
});
|
|
},
|
|
requestGrowth: function (dirMagnitude, userRequestsGrowth) {
|
|
if (this._growing) {
|
|
reportError('Already growing in ' + this._growing + ' dir.');
|
|
return;
|
|
}
|
|
this._growing = dirMagnitude;
|
|
this.pendingRequestCount++;
|
|
this._api.__bridgeSend({
|
|
type: 'growSlice',
|
|
dirMagnitude: dirMagnitude,
|
|
userRequestsGrowth: userRequestsGrowth,
|
|
handle: this._handle
|
|
});
|
|
},
|
|
die: function () {
|
|
this.onadd = null;
|
|
this.onchange = null;
|
|
this.onsplice = null;
|
|
this.onremove = null;
|
|
this.onstatus = null;
|
|
this.oncomplete = null;
|
|
this._api.__bridgeSend({
|
|
type: 'killSlice',
|
|
handle: this._handle
|
|
});
|
|
for (var i = 0; i < this.items.length; i++) {
|
|
var item = this.items[i];
|
|
item.__die();
|
|
}
|
|
}
|
|
};
|
|
function AccountsViewSlice(api, handle) {
|
|
BridgedViewSlice.call(this, api, 'accounts', handle);
|
|
}
|
|
AccountsViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
|
|
AccountsViewSlice.prototype.getAccountById = function (id) {
|
|
for (var i = 0; i < this.items.length; i++) {
|
|
if (this.items[i]._wireRep.id === id) {
|
|
return this.items[i];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
Object.defineProperty(AccountsViewSlice.prototype, 'defaultAccount', {
|
|
get: function () {
|
|
var defaultAccount = this.items[0];
|
|
for (var i = 1; i < this.items.length; i++) {
|
|
if ((this.items[i]._wireRep.defaultPriority || 0) > (defaultAccount._wireRep.defaultPriority || 0))
|
|
defaultAccount = this.items[i];
|
|
}
|
|
return defaultAccount;
|
|
}
|
|
});
|
|
function FoldersViewSlice(api, handle) {
|
|
BridgedViewSlice.call(this, api, 'folders', handle);
|
|
}
|
|
FoldersViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
|
|
FoldersViewSlice.prototype.getFirstFolderWithType = function (type, items) {
|
|
if (!items)
|
|
items = this.items;
|
|
for (var i = 0; i < items.length; i++) {
|
|
var folder = items[i];
|
|
if (folder.type === type)
|
|
return folder;
|
|
}
|
|
return null;
|
|
};
|
|
FoldersViewSlice.prototype.getFirstFolderWithName = function (name, items) {
|
|
if (!items)
|
|
items = this.items;
|
|
for (var i = 0; i < items.length; i++) {
|
|
var folder = items[i];
|
|
if (folder.name === name)
|
|
return folder;
|
|
}
|
|
return null;
|
|
};
|
|
FoldersViewSlice.prototype.getFirstFolderWithPath = function (path, items) {
|
|
if (!items)
|
|
items = this.items;
|
|
for (var i = 0; i < items.length; i++) {
|
|
var folder = items[i];
|
|
if (folder.path === path)
|
|
return folder;
|
|
}
|
|
return null;
|
|
};
|
|
function HeadersViewSlice(api, handle, ns) {
|
|
BridgedViewSlice.call(this, api, ns || 'headers', handle);
|
|
this._bodiesRequestId = 1;
|
|
this._bodiesRequest = {};
|
|
}
|
|
HeadersViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
|
|
HeadersViewSlice.prototype.refresh = function () {
|
|
this._api.__bridgeSend({
|
|
type: 'refreshHeaders',
|
|
handle: this._handle
|
|
});
|
|
};
|
|
HeadersViewSlice.prototype._notifyRequestBodiesComplete = function (reqId) {
|
|
var callback = this._bodiesRequest[reqId];
|
|
if (reqId && callback) {
|
|
callback(true);
|
|
delete this._bodiesRequest[reqId];
|
|
}
|
|
};
|
|
HeadersViewSlice.prototype.maybeRequestBodies = function (idxStart, idxEnd, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = null;
|
|
}
|
|
var messages = [];
|
|
idxEnd = Math.min(idxEnd, this.items.length - 1);
|
|
for (; idxStart <= idxEnd; idxStart++) {
|
|
var item = this.items[idxStart];
|
|
if (this._ns === 'matchedHeaders') {
|
|
item = item.header;
|
|
}
|
|
if (item && item.snippet === null) {
|
|
messages.push({
|
|
suid: item.id,
|
|
date: item.date.valueOf()
|
|
});
|
|
}
|
|
}
|
|
if (!messages.length)
|
|
return callback && window.setZeroTimeout(callback, false);
|
|
var reqId = this._bodiesRequestId++;
|
|
this._bodiesRequest[reqId] = callback;
|
|
this._api.__bridgeSend({
|
|
type: 'requestBodies',
|
|
handle: this._handle,
|
|
requestId: reqId,
|
|
messages: messages,
|
|
options: options
|
|
});
|
|
};
|
|
function MessageComposition(api, handle) {
|
|
this._api = api;
|
|
this._handle = handle;
|
|
this.senderIdentity = null;
|
|
this.to = null;
|
|
this.cc = null;
|
|
this.bcc = null;
|
|
this.subject = null;
|
|
this.body = null;
|
|
this._references = null;
|
|
this.attachments = null;
|
|
this.hasDraft = false;
|
|
}
|
|
MessageComposition.prototype = {
|
|
toString: function () {
|
|
return '[MessageComposition: ' + this._handle + ']';
|
|
},
|
|
toJSON: function () {
|
|
return {
|
|
type: 'MessageComposition',
|
|
handle: this._handle
|
|
};
|
|
},
|
|
die: function () {
|
|
if (this._handle) {
|
|
this._api._composeDone(this._handle, 'die', null, null);
|
|
this._handle = null;
|
|
}
|
|
},
|
|
addAttachment: function (attachmentDef, callback) {
|
|
if (!this.hasDraft)
|
|
this.saveDraft();
|
|
this._api._composeAttach(this._handle, attachmentDef, callback);
|
|
var placeholderAttachment = {
|
|
name: attachmentDef.name,
|
|
blob: {
|
|
size: attachmentDef.blob.size,
|
|
type: attachmentDef.blob.type
|
|
}
|
|
};
|
|
this.attachments.push(placeholderAttachment);
|
|
return placeholderAttachment;
|
|
},
|
|
removeAttachment: function (attachmentDef, callback) {
|
|
var idx = this.attachments.indexOf(attachmentDef);
|
|
if (idx !== -1) {
|
|
this.attachments.splice(idx, 1);
|
|
this._api._composeDetach(this._handle, idx, callback);
|
|
}
|
|
},
|
|
_buildWireRep: function () {
|
|
return {
|
|
senderId: this.senderIdentity.id,
|
|
to: this.to,
|
|
cc: this.cc,
|
|
bcc: this.bcc,
|
|
subject: this.subject,
|
|
body: this.body,
|
|
referencesStr: this._references,
|
|
attachments: this.attachments
|
|
};
|
|
},
|
|
finishCompositionSendMessage: function (callback) {
|
|
this._api._composeDone(this._handle, 'send', this._buildWireRep(), callback);
|
|
},
|
|
saveDraft: function (callback) {
|
|
this.hasDraft = true;
|
|
this._api._composeDone(this._handle, 'save', this._buildWireRep(), callback);
|
|
},
|
|
abortCompositionDeleteDraft: function (callback) {
|
|
this._api._composeDone(this._handle, 'delete', null, callback);
|
|
}
|
|
};
|
|
var LEGAL_CONFIG_KEYS = [];
|
|
var RE_URL = /(^|[\s(,;])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
|
|
var RE_UNEAT_LAST_URL_CHARS = /(?:[),;.!?]|[.!?]\)|\)[.!?])$/;
|
|
var RE_HTTP = /^https?:/i;
|
|
var RE_MAIL = /(^|[\s(,;<>])([^(,;<>@\s]+@[a-z0-9.\-]{2,250}[.][a-z0-9\-]{2,32})/im;
|
|
var RE_MAILTO = /^mailto:/i;
|
|
var MailUtils = {
|
|
linkifyPlain: function (body, doc) {
|
|
var nodes = [];
|
|
var match = true, contentStart;
|
|
while (true) {
|
|
var url = RE_URL.exec(body);
|
|
var email = RE_MAIL.exec(body);
|
|
if (url && (!email || url.index < email.index)) {
|
|
contentStart = url.index + url[1].length;
|
|
if (contentStart > 0)
|
|
nodes.push(doc.createTextNode(body.substring(0, contentStart)));
|
|
var useUrl = url[2];
|
|
var uneat = RE_UNEAT_LAST_URL_CHARS.exec(useUrl);
|
|
if (uneat) {
|
|
useUrl = useUrl.substring(0, uneat.index);
|
|
}
|
|
var link = doc.createElement('a');
|
|
link.className = 'moz-external-link';
|
|
if (RE_HTTP.test(url[2]))
|
|
link.setAttribute('ext-href', useUrl);
|
|
else
|
|
link.setAttribute('ext-href', 'http://' + useUrl);
|
|
var text = doc.createTextNode(useUrl);
|
|
link.appendChild(text);
|
|
nodes.push(link);
|
|
body = body.substring(url.index + url[1].length + useUrl.length);
|
|
} else if (email) {
|
|
contentStart = email.index + email[1].length;
|
|
if (contentStart > 0)
|
|
nodes.push(doc.createTextNode(body.substring(0, contentStart)));
|
|
link = doc.createElement('a');
|
|
link.className = 'moz-external-link';
|
|
if (RE_MAILTO.test(email[2]))
|
|
link.setAttribute('ext-href', email[2]);
|
|
else
|
|
link.setAttribute('ext-href', 'mailto:' + email[2]);
|
|
text = doc.createTextNode(email[2]);
|
|
link.appendChild(text);
|
|
nodes.push(link);
|
|
body = body.substring(email.index + email[0].length);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (body.length > 0)
|
|
nodes.push(doc.createTextNode(body));
|
|
return nodes;
|
|
},
|
|
linkifyHTML: function (doc) {
|
|
function linkElem(elem) {
|
|
var children = elem.childNodes;
|
|
for (var i in children) {
|
|
var sub = children[i];
|
|
if (sub.nodeName == '#text') {
|
|
var nodes = MailUtils.linkifyPlain(sub.nodeValue, doc);
|
|
elem.replaceChild(nodes[nodes.length - 1], sub);
|
|
for (var iNode = nodes.length - 2; iNode >= 0; --iNode) {
|
|
elem.insertBefore(nodes[iNode], nodes[iNode + 1]);
|
|
}
|
|
} else if (sub.nodeName != 'A') {
|
|
linkElem(sub);
|
|
}
|
|
}
|
|
}
|
|
linkElem(doc.body);
|
|
}
|
|
};
|
|
function MailAPI() {
|
|
this._nextHandle = 1;
|
|
this._slices = {};
|
|
this._pendingRequests = {};
|
|
this._liveBodies = {};
|
|
this._spliceFireFuncs = [];
|
|
this._storedSends = [];
|
|
this._processingMessage = null;
|
|
this._deferredMessages = [];
|
|
this.config = {};
|
|
this.onbadlogin = null;
|
|
ContactCache.init();
|
|
}
|
|
exports.MailAPI = MailAPI;
|
|
MailAPI.prototype = {
|
|
toString: function () {
|
|
return '[MailAPI]';
|
|
},
|
|
toJSON: function () {
|
|
return { type: 'MailAPI' };
|
|
},
|
|
utils: MailUtils,
|
|
__bridgeSend: function (msg) {
|
|
this._storedSends.push(msg);
|
|
},
|
|
__bridgeReceive: function ma___bridgeReceive(msg) {
|
|
if (this._processingMessage && msg.type !== 'pong') {
|
|
this._deferredMessages.push(msg);
|
|
} else {
|
|
this._processMessage(msg);
|
|
}
|
|
},
|
|
_processMessage: function ma__processMessage(msg) {
|
|
var methodName = '_recv_' + msg.type;
|
|
if (!(methodName in this)) {
|
|
unexpectedBridgeDataError('Unsupported message type:', msg.type);
|
|
return;
|
|
}
|
|
try {
|
|
var done = this[methodName](msg);
|
|
if (!done) {
|
|
this._processingMessage = msg;
|
|
}
|
|
} catch (ex) {
|
|
internalError('Problem handling message type:', msg.type, ex, '\n', ex.stack);
|
|
return;
|
|
}
|
|
},
|
|
_doneProcessingMessage: function (msg) {
|
|
if (this._processingMessage && this._processingMessage !== msg)
|
|
throw new Error('Mismatched message completion!');
|
|
this._processingMessage = null;
|
|
while (this._processingMessage === null && this._deferredMessages.length) {
|
|
this._processMessage(this._deferredMessages.shift());
|
|
}
|
|
},
|
|
_recv_badLogin: function ma__recv_badLogin(msg) {
|
|
if (this.onbadlogin)
|
|
this.onbadlogin(new MailAccount(this, msg.account, null), msg.problem, msg.whichSide);
|
|
return true;
|
|
},
|
|
_fireAllSplices: function () {
|
|
for (var i = 0; i < this._spliceFireFuncs.length; i++) {
|
|
var fireSpliceData = this._spliceFireFuncs[i];
|
|
fireSpliceData();
|
|
}
|
|
this._spliceFireFuncs.length = 0;
|
|
},
|
|
_recv_batchSlice: function receiveBatchSlice(msg) {
|
|
var slice = this._slices[msg.handle];
|
|
if (!slice) {
|
|
unexpectedBridgeDataError('Received message about nonexistent slice:', msg.handle);
|
|
return true;
|
|
}
|
|
var updateStatus = this._updateSliceStatus(msg, slice);
|
|
for (var i = 0; i < msg.sliceUpdates.length; i++) {
|
|
var update = msg.sliceUpdates[i];
|
|
if (update.type === 'update') {
|
|
this._spliceFireFuncs.push(this._processSliceUpdate.bind(this, msg, update.updates, slice));
|
|
} else {
|
|
this._transformAndEnqueueSingleSplice(msg, update, slice);
|
|
}
|
|
}
|
|
if (ContactCache.pendingLookupCount) {
|
|
ContactCache.callbacks.push(function contactsResolved() {
|
|
this._fireAllSplices();
|
|
this._fireStatusNotifications(updateStatus, slice);
|
|
this._doneProcessingMessage(msg);
|
|
}.bind(this));
|
|
return false;
|
|
}
|
|
this._fireAllSplices();
|
|
this._fireStatusNotifications(updateStatus, slice);
|
|
return true;
|
|
},
|
|
_fireStatusNotifications: function (updateStatus, slice) {
|
|
if (updateStatus && slice.onstatus) {
|
|
slice.onstatus(slice.status);
|
|
}
|
|
},
|
|
_updateSliceStatus: function (msg, slice) {
|
|
slice.atTop = msg.atTop;
|
|
slice.atBottom = msg.atBottom;
|
|
slice.userCanGrowUpwards = msg.userCanGrowUpwards;
|
|
slice.userCanGrowDownwards = msg.userCanGrowDownwards;
|
|
var generatedStatusChange = msg.status && (slice.status !== msg.status || slice.syncProgress !== msg.progress);
|
|
if (msg.status) {
|
|
slice.status = msg.status;
|
|
slice.syncProgress = msg.syncProgress;
|
|
}
|
|
return generatedStatusChange;
|
|
},
|
|
_processSliceUpdate: function (msg, splice, slice) {
|
|
try {
|
|
for (var i = 0; i < splice.length; i += 2) {
|
|
var idx = splice[i], wireRep = splice[i + 1], itemObj = slice.items[idx];
|
|
itemObj.__update(wireRep);
|
|
if (slice.onchange) {
|
|
slice.onchange(itemObj, idx);
|
|
}
|
|
if (itemObj.onchange) {
|
|
itemObj.onchange(itemObj, idx);
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
reportClientCodeError('onchange notification error', ex, '\n', ex.stack);
|
|
}
|
|
},
|
|
_transformAndEnqueueSingleSplice: function (msg, splice, slice) {
|
|
var transformedItems = this._transform_sliceSplice(splice, slice);
|
|
var fake = false;
|
|
this._spliceFireFuncs.push(function singleSpliceUpdate() {
|
|
this._fireSplice(splice, slice, transformedItems, fake);
|
|
}.bind(this));
|
|
},
|
|
_fireSplice: function (splice, slice, transformedItems, fake) {
|
|
var i, stopIndex, items, tempMsg;
|
|
if (splice.headerCount !== undefined) {
|
|
slice.headerCount = splice.headerCount;
|
|
}
|
|
if (slice.onsplice) {
|
|
try {
|
|
slice.onsplice(splice.index, splice.howMany, transformedItems, splice.requested, splice.moreExpected, fake);
|
|
} catch (ex) {
|
|
reportClientCodeError('onsplice notification error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
if (splice.howMany) {
|
|
try {
|
|
stopIndex = splice.index + splice.howMany;
|
|
for (i = splice.index; i < stopIndex; i++) {
|
|
var item = slice.items[i];
|
|
if (slice.onremove)
|
|
slice.onremove(item, i);
|
|
if (item.onremove)
|
|
item.onremove(item, i);
|
|
item.__die();
|
|
}
|
|
} catch (ex) {
|
|
reportClientCodeError('onremove notification error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
slice.items.splice.apply(slice.items, [
|
|
splice.index,
|
|
splice.howMany
|
|
].concat(transformedItems));
|
|
if (slice.onadd) {
|
|
try {
|
|
stopIndex = splice.index + transformedItems.length;
|
|
for (i = splice.index; i < stopIndex; i++) {
|
|
slice.onadd(slice.items[i], i);
|
|
}
|
|
} catch (ex) {
|
|
reportClientCodeError('onadd notification error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
if (splice.requested && !splice.moreExpected) {
|
|
slice._growing = 0;
|
|
if (slice.pendingRequestCount)
|
|
slice.pendingRequestCount--;
|
|
if (slice.oncomplete) {
|
|
var completeFunc = slice.oncomplete;
|
|
slice.oncomplete = null;
|
|
try {
|
|
completeFunc(splice.newEmailCount);
|
|
} catch (ex) {
|
|
reportClientCodeError('oncomplete notification error', ex, '\n', ex.stack);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_transform_sliceSplice: function ma__transform_sliceSplice(splice, slice) {
|
|
var addItems = splice.addItems, transformedItems = [], i;
|
|
switch (slice._ns) {
|
|
case 'accounts':
|
|
for (i = 0; i < addItems.length; i++) {
|
|
transformedItems.push(new MailAccount(this, addItems[i], slice));
|
|
}
|
|
break;
|
|
case 'identities':
|
|
for (i = 0; i < addItems.length; i++) {
|
|
transformedItems.push(new MailSenderIdentity(this, addItems[i]));
|
|
}
|
|
break;
|
|
case 'folders':
|
|
for (i = 0; i < addItems.length; i++) {
|
|
transformedItems.push(new MailFolder(this, addItems[i]));
|
|
}
|
|
break;
|
|
case 'headers':
|
|
for (i = 0; i < addItems.length; i++) {
|
|
transformedItems.push(new MailHeader(slice, addItems[i]));
|
|
}
|
|
break;
|
|
case 'matchedHeaders':
|
|
for (i = 0; i < addItems.length; i++) {
|
|
transformedItems.push(new MailMatchedHeader(slice, addItems[i]));
|
|
}
|
|
break;
|
|
default:
|
|
console.error('Slice notification for unknown type:', slice._ns);
|
|
break;
|
|
}
|
|
return transformedItems;
|
|
},
|
|
_recv_sliceDead: function (msg) {
|
|
var slice = this._slices[msg.handle];
|
|
delete this._slices[msg.handle];
|
|
if (slice.ondead)
|
|
slice.ondead(slice);
|
|
slice.ondead = null;
|
|
return true;
|
|
},
|
|
_getBodyForMessage: function (header, options, callback) {
|
|
var downloadBodyReps = false, withBodyReps = false;
|
|
if (options && options.downloadBodyReps) {
|
|
downloadBodyReps = options.downloadBodyReps;
|
|
}
|
|
if (options && options.withBodyReps) {
|
|
withBodyReps = options.withBodyReps;
|
|
}
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'getBody',
|
|
suid: header.id,
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'getBody',
|
|
handle: handle,
|
|
suid: header.id,
|
|
date: header.date.valueOf(),
|
|
downloadBodyReps: downloadBodyReps,
|
|
withBodyReps: withBodyReps
|
|
});
|
|
},
|
|
_recv_gotBody: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for got body:', msg.handle);
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
var body = msg.bodyInfo ? new MailBody(this, req.suid, msg.bodyInfo, msg.handle) : null;
|
|
if (body) {
|
|
this._liveBodies[msg.handle] = body;
|
|
}
|
|
req.callback.call(null, body, req.suid);
|
|
return true;
|
|
},
|
|
_recv_requestBodiesComplete: function (msg) {
|
|
var slice = this._slices[msg.handle];
|
|
if (slice)
|
|
slice._notifyRequestBodiesComplete(msg.requestId);
|
|
return true;
|
|
},
|
|
_recv_bodyModified: function (msg) {
|
|
var body = this._liveBodies[msg.handle];
|
|
if (!body) {
|
|
unexpectedBridgeDataError('body modified for dead handle', msg.handle);
|
|
return true;
|
|
}
|
|
var wireRep = msg.bodyInfo;
|
|
body.__update(wireRep, msg.detail);
|
|
if (body.onchange) {
|
|
body.onchange(msg.detail, body);
|
|
}
|
|
return true;
|
|
},
|
|
_recv_bodyDead: function (msg) {
|
|
var body = this._liveBodies[msg.handle];
|
|
if (body && body.ondead) {
|
|
body.ondead();
|
|
}
|
|
delete this._liveBodies[msg.handle];
|
|
return true;
|
|
},
|
|
_downloadAttachments: function (body, relPartIndices, attachmentIndices, registerAttachments, callWhenDone, callOnProgress) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'downloadAttachments',
|
|
body: body,
|
|
relParts: relPartIndices.length > 0,
|
|
attachments: attachmentIndices.length > 0,
|
|
callback: callWhenDone,
|
|
progress: callOnProgress
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'downloadAttachments',
|
|
handle: handle,
|
|
suid: body.id,
|
|
date: body._date,
|
|
relPartIndices: relPartIndices,
|
|
attachmentIndices: attachmentIndices,
|
|
registerAttachments: registerAttachments
|
|
});
|
|
},
|
|
_recv_downloadedAttachments: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for got body:', msg.handle);
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
if (req.callback)
|
|
req.callback.call(null, req.body);
|
|
return true;
|
|
},
|
|
learnAboutAccount: function (details, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'learnAboutAccount',
|
|
details: details,
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'learnAboutAccount',
|
|
handle: handle,
|
|
details: details
|
|
});
|
|
},
|
|
_recv_learnAboutAccountResults: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle:', msg.handle);
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback.call(null, msg.data);
|
|
return true;
|
|
},
|
|
tryToCreateAccount: function ma_tryToCreateAccount(details, domainInfo, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'tryToCreateAccount',
|
|
details: details,
|
|
domainInfo: domainInfo,
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'tryToCreateAccount',
|
|
handle: handle,
|
|
details: details,
|
|
domainInfo: domainInfo
|
|
});
|
|
},
|
|
_recv_tryToCreateAccountResults: function ma__recv_tryToCreateAccountResults(msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for create account:', msg.handle);
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
var account;
|
|
if (msg.account) {
|
|
account = new MailAccount(this, msg.account, null);
|
|
}
|
|
req.callback.call(null, msg.error, msg.errorDetails, account);
|
|
return true;
|
|
},
|
|
_clearAccountProblems: function ma__clearAccountProblems(account, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'clearAccountProblems',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'clearAccountProblems',
|
|
accountId: account.id,
|
|
handle: handle
|
|
});
|
|
},
|
|
_recv_clearAccountProblems: function ma__recv_clearAccountProblems(msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback && req.callback();
|
|
return true;
|
|
},
|
|
_modifyAccount: function ma__modifyAccount(account, mods, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'modifyAccount',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'modifyAccount',
|
|
accountId: account.id,
|
|
mods: mods,
|
|
handle: handle
|
|
});
|
|
},
|
|
_recv_modifyAccount: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback && req.callback();
|
|
return true;
|
|
},
|
|
_deleteAccount: function ma__deleteAccount(account) {
|
|
this.__bridgeSend({
|
|
type: 'deleteAccount',
|
|
accountId: account.id
|
|
});
|
|
},
|
|
_modifyIdentity: function ma__modifyIdentity(identity, mods, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'modifyIdentity',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'modifyIdentity',
|
|
identityId: identity.id,
|
|
mods: mods,
|
|
handle: handle
|
|
});
|
|
},
|
|
_recv_modifyIdentity: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback && req.callback();
|
|
return true;
|
|
},
|
|
viewAccounts: function ma_viewAccounts(realAccountsOnly) {
|
|
var handle = this._nextHandle++, slice = new AccountsViewSlice(this, handle);
|
|
this._slices[handle] = slice;
|
|
this.__bridgeSend({
|
|
type: 'viewAccounts',
|
|
handle: handle
|
|
});
|
|
return slice;
|
|
},
|
|
viewSenderIdentities: function ma_viewSenderIdentities() {
|
|
var handle = this._nextHandle++, slice = new BridgedViewSlice(this, 'identities', handle);
|
|
this._slices[handle] = slice;
|
|
this.__bridgeSend({
|
|
type: 'viewSenderIdentities',
|
|
handle: handle
|
|
});
|
|
return slice;
|
|
},
|
|
viewFolders: function ma_viewFolders(mode, argument) {
|
|
var handle = this._nextHandle++, slice = new FoldersViewSlice(this, handle);
|
|
this._slices[handle] = slice;
|
|
this.__bridgeSend({
|
|
type: 'viewFolders',
|
|
mode: mode,
|
|
handle: handle,
|
|
argument: argument ? argument.id : null
|
|
});
|
|
return slice;
|
|
},
|
|
viewFolderMessages: function ma_viewFolderMessages(folder) {
|
|
var handle = this._nextHandle++, slice = new HeadersViewSlice(this, handle);
|
|
slice.folderId = folder.id;
|
|
slice.pendingRequestCount++;
|
|
this._slices[handle] = slice;
|
|
this.__bridgeSend({
|
|
type: 'viewFolderMessages',
|
|
folderId: folder.id,
|
|
handle: handle
|
|
});
|
|
return slice;
|
|
},
|
|
searchFolderMessages: function ma_searchFolderMessages(folder, text, whatToSearch) {
|
|
var handle = this._nextHandle++, slice = new HeadersViewSlice(this, handle, 'matchedHeaders');
|
|
slice.pendingRequestCount++;
|
|
this._slices[handle] = slice;
|
|
this.__bridgeSend({
|
|
type: 'searchFolderMessages',
|
|
folderId: folder.id,
|
|
handle: handle,
|
|
phrase: text,
|
|
whatToSearch: whatToSearch
|
|
});
|
|
return slice;
|
|
},
|
|
deleteMessages: function ma_deleteMessages(messages) {
|
|
var handle = this._nextHandle++;
|
|
var undoableOp = new UndoableOperation(this, 'delete', messages.length, handle), msgSuids = messages.map(serializeMessageName);
|
|
this._pendingRequests[handle] = {
|
|
type: 'mutation',
|
|
handle: handle,
|
|
undoableOp: undoableOp
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'deleteMessages',
|
|
handle: handle,
|
|
messages: msgSuids
|
|
});
|
|
return undoableOp;
|
|
},
|
|
moveMessages: function ma_moveMessages(messages, targetFolder, callback) {
|
|
var handle = this._nextHandle++;
|
|
var undoableOp = new UndoableOperation(this, 'move', messages.length, handle), msgSuids = messages.map(serializeMessageName);
|
|
this._pendingRequests[handle] = {
|
|
type: 'mutation',
|
|
handle: handle,
|
|
undoableOp: undoableOp,
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'moveMessages',
|
|
handle: handle,
|
|
messages: msgSuids,
|
|
targetFolder: targetFolder.id
|
|
});
|
|
return undoableOp;
|
|
},
|
|
markMessagesRead: function ma_markMessagesRead(messages, beRead) {
|
|
return this.modifyMessageTags(messages, beRead ? ['\\Seen'] : null, beRead ? null : ['\\Seen'], beRead ? 'read' : 'unread');
|
|
},
|
|
markMessagesStarred: function ma_markMessagesStarred(messages, beStarred) {
|
|
return this.modifyMessageTags(messages, beStarred ? ['\\Flagged'] : null, beStarred ? null : ['\\Flagged'], beStarred ? 'star' : 'unstar');
|
|
},
|
|
modifyMessageTags: function ma_modifyMessageTags(messages, addTags, removeTags, _opcode) {
|
|
var handle = this._nextHandle++;
|
|
if (!_opcode) {
|
|
if (addTags && addTags.length)
|
|
_opcode = 'addtag';
|
|
else if (removeTags && removeTags.length)
|
|
_opcode = 'removetag';
|
|
}
|
|
var undoableOp = new UndoableOperation(this, _opcode, messages.length, handle), msgSuids = messages.map(serializeMessageName);
|
|
this._pendingRequests[handle] = {
|
|
type: 'mutation',
|
|
handle: handle,
|
|
undoableOp: undoableOp
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'modifyMessageTags',
|
|
handle: handle,
|
|
opcode: _opcode,
|
|
addTags: addTags,
|
|
removeTags: removeTags,
|
|
messages: msgSuids
|
|
});
|
|
return undoableOp;
|
|
},
|
|
sendOutboxMessages: function (account, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'sendOutboxMessages',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'sendOutboxMessages',
|
|
accountId: account.id,
|
|
handle: handle
|
|
});
|
|
},
|
|
_recv_sendOutboxMessages: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback && req.callback();
|
|
return true;
|
|
},
|
|
setOutboxSyncEnabled: function (account, enabled, callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'setOutboxSyncEnabled',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'setOutboxSyncEnabled',
|
|
accountId: account.id,
|
|
outboxSyncEnabled: enabled,
|
|
handle: handle
|
|
});
|
|
},
|
|
_recv_setOutboxSyncEnabled: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback && req.callback();
|
|
return true;
|
|
},
|
|
parseMailbox: function (email) {
|
|
try {
|
|
var mailbox = addressparser.parse(email);
|
|
return mailbox.length >= 1 ? mailbox[0] : null;
|
|
} catch (ex) {
|
|
reportClientCodeError('parse mailbox error', ex, '\n', ex.stack);
|
|
return null;
|
|
}
|
|
},
|
|
_recv_mutationConfirmed: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for mutation:', msg.handle);
|
|
return true;
|
|
}
|
|
req.undoableOp._tempHandle = null;
|
|
req.undoableOp._longtermIds = msg.longtermIds;
|
|
if (req.undoableOp._undoRequested)
|
|
req.undoableOp.undo();
|
|
if (req.callback) {
|
|
req.callback(msg.result);
|
|
}
|
|
return true;
|
|
},
|
|
__undo: function undo(undoableOp) {
|
|
this.__bridgeSend({
|
|
type: 'undo',
|
|
longtermIds: undoableOp._longtermIds
|
|
});
|
|
},
|
|
resolveEmailAddressToPeep: function (emailAddress, callback) {
|
|
var peep = ContactCache.resolvePeep({
|
|
name: null,
|
|
address: emailAddress
|
|
});
|
|
if (ContactCache.pendingLookupCount)
|
|
ContactCache.callbacks.push(callback.bind(null, peep));
|
|
else
|
|
callback(peep);
|
|
},
|
|
beginMessageComposition: function (message, folder, options, callback) {
|
|
if (!callback)
|
|
throw new Error('A callback must be provided; you are using the API ' + 'wrong if you do not.');
|
|
if (!options)
|
|
options = {};
|
|
var handle = this._nextHandle++, composer = new MessageComposition(this, handle);
|
|
this._pendingRequests[handle] = {
|
|
type: 'compose',
|
|
composer: composer,
|
|
callback: callback
|
|
};
|
|
var msg = {
|
|
type: 'beginCompose',
|
|
handle: handle,
|
|
mode: null,
|
|
submode: null,
|
|
refSuid: null,
|
|
refDate: null,
|
|
refGuid: null,
|
|
refAuthor: null,
|
|
refSubject: null
|
|
};
|
|
if (options.hasOwnProperty('replyTo') && options.replyTo) {
|
|
msg.mode = 'reply';
|
|
msg.submode = options.replyMode;
|
|
msg.refSuid = options.replyTo.id;
|
|
msg.refDate = options.replyTo.date.valueOf();
|
|
msg.refGuid = options.replyTo.guid;
|
|
msg.refAuthor = options.replyTo.author.toWireRep();
|
|
msg.refSubject = options.replyTo.subject;
|
|
} else if (options.hasOwnProperty('forwardOf') && options.forwardOf) {
|
|
msg.mode = 'forward';
|
|
msg.submode = options.forwardMode;
|
|
msg.refSuid = options.forwardOf.id;
|
|
msg.refDate = options.forwardOf.date.valueOf();
|
|
msg.refGuid = options.forwardOf.guid;
|
|
msg.refAuthor = options.forwardOf.author.toWireRep();
|
|
msg.refSubject = options.forwardOf.subject;
|
|
} else {
|
|
msg.mode = 'new';
|
|
if (message) {
|
|
msg.submode = 'message';
|
|
msg.refSuid = message.id;
|
|
} else if (folder) {
|
|
msg.submode = 'folder';
|
|
msg.refSuid = folder.id;
|
|
}
|
|
}
|
|
this.__bridgeSend(msg);
|
|
return composer;
|
|
},
|
|
resumeMessageComposition: function (message, callback) {
|
|
if (!callback)
|
|
throw new Error('A callback must be provided; you are using the API ' + 'wrong if you do not.');
|
|
var handle = this._nextHandle++, composer = new MessageComposition(this, handle);
|
|
this._pendingRequests[handle] = {
|
|
type: 'compose',
|
|
composer: composer,
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'resumeCompose',
|
|
handle: handle,
|
|
messageNamer: serializeMessageName(message)
|
|
});
|
|
return composer;
|
|
},
|
|
_recv_composeBegun: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for compose begun:', msg.handle);
|
|
return true;
|
|
}
|
|
req.composer.senderIdentity = new MailSenderIdentity(this, msg.identity);
|
|
req.composer.subject = msg.subject;
|
|
req.composer.body = msg.body;
|
|
req.composer.to = msg.to;
|
|
req.composer.cc = msg.cc;
|
|
req.composer.bcc = msg.bcc;
|
|
req.composer._references = msg.referencesStr;
|
|
req.composer.attachments = msg.attachments;
|
|
req.composer.sendStatus = msg.sendStatus;
|
|
if (req.callback) {
|
|
var callback = req.callback;
|
|
req.callback = null;
|
|
callback.call(null, req.composer);
|
|
}
|
|
return true;
|
|
},
|
|
_composeAttach: function (draftHandle, attachmentDef, callback) {
|
|
if (!draftHandle) {
|
|
return;
|
|
}
|
|
var draftReq = this._pendingRequests[draftHandle];
|
|
if (!draftReq) {
|
|
return;
|
|
}
|
|
var callbackHandle = this._nextHandle++;
|
|
this._pendingRequests[callbackHandle] = {
|
|
type: 'attachBlobToDraft',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'attachBlobToDraft',
|
|
handle: callbackHandle,
|
|
draftHandle: draftHandle,
|
|
attachmentDef: attachmentDef
|
|
});
|
|
},
|
|
_recv_attachedBlobToDraft: function (msg) {
|
|
var callbackReq = this._pendingRequests[msg.handle];
|
|
var draftReq = this._pendingRequests[msg.draftHandle];
|
|
if (!callbackReq) {
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
if (callbackReq.callback && draftReq && draftReq.composer) {
|
|
callbackReq.callback(msg.err, draftReq.composer);
|
|
}
|
|
return true;
|
|
},
|
|
_composeDetach: function (draftHandle, attachmentIndex, callback) {
|
|
if (!draftHandle) {
|
|
return;
|
|
}
|
|
var draftReq = this._pendingRequests[draftHandle];
|
|
if (!draftReq) {
|
|
return;
|
|
}
|
|
var callbackHandle = this._nextHandle++;
|
|
this._pendingRequests[callbackHandle] = {
|
|
type: 'detachAttachmentFromDraft',
|
|
callback: callback
|
|
};
|
|
this.__bridgeSend({
|
|
type: 'detachAttachmentFromDraft',
|
|
handle: callbackHandle,
|
|
draftHandle: draftHandle,
|
|
attachmentIndex: attachmentIndex
|
|
});
|
|
},
|
|
_recv_detachedAttachmentFromDraft: function (msg) {
|
|
var callbackReq = this._pendingRequests[msg.handle];
|
|
var draftReq = this._pendingRequests[msg.draftHandle];
|
|
if (!callbackReq) {
|
|
return true;
|
|
}
|
|
delete this._pendingRequests[msg.handle];
|
|
if (callbackReq.callback && draftReq && draftReq.composer) {
|
|
callbackReq.callback(msg.err, draftReq.composer);
|
|
}
|
|
return true;
|
|
},
|
|
_composeDone: function (handle, command, state, callback) {
|
|
if (!handle)
|
|
return;
|
|
var req = this._pendingRequests[handle];
|
|
if (!req) {
|
|
return;
|
|
}
|
|
req.type = command;
|
|
if (callback)
|
|
req.callback = callback;
|
|
this.__bridgeSend({
|
|
type: 'doneCompose',
|
|
handle: handle,
|
|
command: command,
|
|
state: state
|
|
});
|
|
},
|
|
_recv_doneCompose: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
if (!req) {
|
|
unexpectedBridgeDataError('Bad handle for doneCompose:', msg.handle);
|
|
return true;
|
|
}
|
|
req.active = null;
|
|
if (req.type === 'die' || !msg.err && req.type !== 'save')
|
|
delete this._pendingRequests[msg.handle];
|
|
if (req.callback) {
|
|
req.callback.call(null, {
|
|
sentDate: msg.sentDate,
|
|
messageId: msg.messageId,
|
|
sendStatus: msg.sendStatus
|
|
});
|
|
req.callback = null;
|
|
}
|
|
return true;
|
|
},
|
|
setInteractive: function () {
|
|
this.__bridgeSend({ type: 'setInteractive' });
|
|
},
|
|
_recv_cronSyncStart: function ma__recv_cronSyncStart(msg) {
|
|
if (this.oncronsyncstart)
|
|
this.oncronsyncstart(msg.accountIds);
|
|
return true;
|
|
},
|
|
_recv_cronSyncStop: function ma__recv_cronSyncStop(msg) {
|
|
if (this.oncronsyncstop)
|
|
this.oncronsyncstop(msg.accountsResults);
|
|
return true;
|
|
},
|
|
_recv_backgroundSendStatus: function (msg) {
|
|
if (this.onbackgroundsendstatus) {
|
|
this.onbackgroundsendstatus(msg.data);
|
|
}
|
|
return true;
|
|
},
|
|
useLocalizedStrings: function (strings) {
|
|
this.__bridgeSend({
|
|
type: 'localizedStrings',
|
|
strings: strings
|
|
});
|
|
if (strings.folderNames)
|
|
this.l10n_folder_names = strings.folderNames;
|
|
},
|
|
l10n_folder_names: {},
|
|
l10n_folder_name: function (name, type) {
|
|
if (this.l10n_folder_names.hasOwnProperty(type)) {
|
|
var lowerName = name.toLowerCase();
|
|
if (type === lowerName || type === 'drafts' || type === 'junk' || type === 'queue')
|
|
return this.l10n_folder_names[type];
|
|
}
|
|
return name;
|
|
},
|
|
modifyConfig: function (mods) {
|
|
for (var key in mods) {
|
|
if (LEGAL_CONFIG_KEYS.indexOf(key) === -1)
|
|
throw new Error(key + ' is not a legal config key!');
|
|
}
|
|
this.__bridgeSend({
|
|
type: 'modifyConfig',
|
|
mods: mods
|
|
});
|
|
},
|
|
_recv_config: function (msg) {
|
|
this.config = msg.config;
|
|
return true;
|
|
},
|
|
ping: function (callback) {
|
|
var handle = this._nextHandle++;
|
|
this._pendingRequests[handle] = {
|
|
type: 'ping',
|
|
callback: callback
|
|
};
|
|
window.setZeroTimeout(function () {
|
|
this.__bridgeSend({
|
|
type: 'ping',
|
|
handle: handle
|
|
});
|
|
}.bind(this));
|
|
},
|
|
_recv_pong: function (msg) {
|
|
var req = this._pendingRequests[msg.handle];
|
|
delete this._pendingRequests[msg.handle];
|
|
req.callback();
|
|
return true;
|
|
},
|
|
debugSupport: function (command, argument) {
|
|
if (command === 'setLogging')
|
|
this.config.debugLogging = argument;
|
|
this.__bridgeSend({
|
|
type: 'debugSupport',
|
|
cmd: command,
|
|
arg: argument
|
|
});
|
|
}
|
|
};
|
|
});
|
|
;
|
|
define('ext/worker-support/main-router', [], function () {
|
|
'use strict';
|
|
var listeners = {};
|
|
var modules = [];
|
|
var worker = null;
|
|
function register(module) {
|
|
var action, name = module.name;
|
|
modules.push(module);
|
|
if (module.process) {
|
|
action = function (msg) {
|
|
module.process(msg.uid, msg.cmd, msg.args);
|
|
};
|
|
} else if (module.dispatch) {
|
|
action = function (msg) {
|
|
if (module.dispatch[msg.cmd]) {
|
|
module.dispatch[msg.cmd].apply(module.dispatch, msg.args);
|
|
}
|
|
};
|
|
}
|
|
listeners[name] = action;
|
|
module.sendMessage = function (uid, cmd, args, transferArgs) {
|
|
worker.postMessage({
|
|
type: name,
|
|
uid: uid,
|
|
cmd: cmd,
|
|
args: args
|
|
}, transferArgs);
|
|
};
|
|
}
|
|
function unregister(module) {
|
|
delete listeners['on' + module.name];
|
|
}
|
|
function shutdown() {
|
|
modules.forEach(function (module) {
|
|
if (module.shutdown)
|
|
module.shutdown();
|
|
});
|
|
}
|
|
function useWorker(_worker) {
|
|
worker = _worker;
|
|
worker.onmessage = function dispatchToListener(evt) {
|
|
var data = evt.data;
|
|
var listener = listeners[data.type];
|
|
if (listener)
|
|
listener(data);
|
|
};
|
|
}
|
|
return {
|
|
register: register,
|
|
unregister: unregister,
|
|
useWorker: useWorker,
|
|
shutdown: shutdown
|
|
};
|
|
});
|
|
;
|
|
define('ext/worker-support/configparser-main', [], function () {
|
|
'use strict';
|
|
function debug(str) {
|
|
}
|
|
function nsResolver(prefix) {
|
|
var baseUrl = 'http://schemas.microsoft.com/exchange/autodiscover/';
|
|
var ns = {
|
|
rq: baseUrl + 'mobilesync/requestschema/2006',
|
|
ad: baseUrl + 'responseschema/2006',
|
|
ms: baseUrl + 'mobilesync/responseschema/2006'
|
|
};
|
|
return ns[prefix] || null;
|
|
}
|
|
function parseAccountCommon(uid, cmd, text) {
|
|
var doc = new DOMParser().parseFromString(text, 'text/xml');
|
|
var getNode = function (xpath, rel) {
|
|
return doc.evaluate(xpath, rel || doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
};
|
|
var dictifyChildNodes = function (node) {
|
|
if (!node) {
|
|
return null;
|
|
}
|
|
var dict = {};
|
|
for (var kid = node.firstElementChild; kid; kid = kid.nextElementSibling) {
|
|
dict[kid.tagName] = kid.textContent;
|
|
}
|
|
return dict;
|
|
};
|
|
var provider = getNode('/clientConfig/emailProvider');
|
|
var incoming = getNode('incomingServer[@type="imap"] | ' + 'incomingServer[@type="activesync"] | ' + 'incomingServer[@type="pop3"]', provider);
|
|
var outgoing = getNode('outgoingServer[@type="smtp"]', provider);
|
|
var oauth2Settings = dictifyChildNodes(getNode('oauth2Settings', provider));
|
|
var config = null;
|
|
var status = null;
|
|
if (incoming) {
|
|
config = {
|
|
type: null,
|
|
incoming: {},
|
|
outgoing: {},
|
|
oauth2Settings: oauth2Settings
|
|
};
|
|
for (var iter in Iterator(incoming.children)) {
|
|
var child = iter[1];
|
|
config.incoming[child.tagName] = child.textContent;
|
|
}
|
|
if (incoming.getAttribute('type') === 'activesync') {
|
|
config.type = 'activesync';
|
|
} else if (outgoing) {
|
|
var isImap = incoming.getAttribute('type') === 'imap';
|
|
config.type = isImap ? 'imap+smtp' : 'pop3+smtp';
|
|
for (var iter in Iterator(outgoing.children)) {
|
|
var child = iter[1];
|
|
config.outgoing[child.tagName] = child.textContent;
|
|
}
|
|
var ALLOWED_SOCKET_TYPES = [
|
|
'SSL',
|
|
'STARTTLS'
|
|
];
|
|
if (ALLOWED_SOCKET_TYPES.indexOf(config.incoming.socketType) === -1 || ALLOWED_SOCKET_TYPES.indexOf(config.outgoing.socketType) === -1) {
|
|
config = null;
|
|
status = 'unsafe';
|
|
}
|
|
} else {
|
|
config = null;
|
|
status = 'no-outgoing';
|
|
}
|
|
} else {
|
|
status = 'no-incoming';
|
|
}
|
|
self.sendMessage(uid, cmd, [
|
|
config,
|
|
status
|
|
]);
|
|
}
|
|
function parseActiveSyncAccount(uid, cmd, text, aNoRedirect) {
|
|
var doc = new DOMParser().parseFromString(text, 'text/xml');
|
|
var getNode = function (xpath, rel) {
|
|
return doc.evaluate(xpath, rel, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
};
|
|
var getNodes = function (xpath, rel) {
|
|
return doc.evaluate(xpath, rel, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
|
};
|
|
var getString = function (xpath, rel) {
|
|
return doc.evaluate(xpath, rel, nsResolver, XPathResult.STRING_TYPE, null).stringValue;
|
|
};
|
|
var postResponse = function (error, config, redirectedEmail) {
|
|
self.sendMessage(uid, cmd, [
|
|
config,
|
|
error,
|
|
redirectedEmail
|
|
]);
|
|
};
|
|
var error = null;
|
|
if (doc.documentElement.tagName === 'parsererror') {
|
|
error = 'Error parsing autodiscover response';
|
|
return postResponse(error);
|
|
}
|
|
var responseNode = getNode('/ad:Autodiscover/ms:Response', doc) || getNode('/ms:Autodiscover/ms:Response', doc);
|
|
if (!responseNode) {
|
|
error = 'Missing Autodiscover Response node';
|
|
return postResponse(error);
|
|
}
|
|
var error = getNode('ms:Error', responseNode) || getNode('ms:Action/ms:Error', responseNode);
|
|
if (error) {
|
|
error = getString('ms:Message/text()', error);
|
|
return postResponse(error);
|
|
}
|
|
var redirect = getNode('ms:Action/ms:Redirect', responseNode);
|
|
if (redirect) {
|
|
if (aNoRedirect) {
|
|
error = 'Multiple redirects occurred during autodiscovery';
|
|
return postResponse(error);
|
|
}
|
|
var redirectedEmail = getString('text()', redirect);
|
|
return postResponse(null, null, redirectedEmail);
|
|
}
|
|
var user = getNode('ms:User', responseNode);
|
|
var config = {
|
|
culture: getString('ms:Culture/text()', responseNode),
|
|
user: {
|
|
name: getString('ms:DisplayName/text()', user),
|
|
email: getString('ms:EMailAddress/text()', user)
|
|
},
|
|
servers: []
|
|
};
|
|
var servers = getNodes('ms:Action/ms:Settings/ms:Server', responseNode);
|
|
var server;
|
|
while (server = servers.iterateNext()) {
|
|
config.servers.push({
|
|
type: getString('ms:Type/text()', server),
|
|
url: getString('ms:Url/text()', server),
|
|
name: getString('ms:Name/text()', server),
|
|
serverData: getString('ms:ServerData/text()', server)
|
|
});
|
|
}
|
|
for (var iter in Iterator(config.servers)) {
|
|
var server = iter[1];
|
|
if (server.type === 'MobileSync') {
|
|
config.mobileSyncServer = server;
|
|
break;
|
|
}
|
|
}
|
|
if (!config.mobileSyncServer) {
|
|
error = 'No MobileSync server found';
|
|
return postResponse(error, config);
|
|
}
|
|
postResponse(null, config);
|
|
}
|
|
;
|
|
var self = {
|
|
name: 'configparser',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
debug('process ' + cmd);
|
|
switch (cmd) {
|
|
case 'accountcommon':
|
|
parseAccountCommon(uid, cmd, args[0]);
|
|
break;
|
|
case 'accountactivesync':
|
|
parseActiveSyncAccount(uid, cmd, args[0], args[1]);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
return self;
|
|
});
|
|
define('ext/worker-support/cronsync-main', [
|
|
'require',
|
|
'evt'
|
|
], function (require) {
|
|
'use strict';
|
|
var evt = require('evt');
|
|
function debug(str) {
|
|
console.log('cronsync-main: ' + str);
|
|
}
|
|
function makeData(accountIds, interval, date) {
|
|
return {
|
|
type: 'sync',
|
|
accountIds: accountIds,
|
|
interval: interval,
|
|
timestamp: date.getTime()
|
|
};
|
|
}
|
|
function makeAccountKey(accountIds) {
|
|
return 'id' + accountIds.join(' ');
|
|
}
|
|
var prefixLength = 'interval'.length;
|
|
function toInterval(intervalKey) {
|
|
return parseInt(intervalKey.substring(prefixLength), 10);
|
|
}
|
|
function hasSameValues(ary1, ary2) {
|
|
if (ary1.length !== ary2.length) {
|
|
return false;
|
|
}
|
|
var hasMismatch = ary1.some(function (item, i) {
|
|
return item !== ary2[i];
|
|
});
|
|
return !hasMismatch;
|
|
}
|
|
var dispatcher = {
|
|
_routeReady: false,
|
|
_routeQueue: [],
|
|
_sendMessage: function (type, args) {
|
|
if (this._routeReady) {
|
|
routeRegistration.sendMessage(null, type, args);
|
|
} else {
|
|
this._routeQueue.push([
|
|
type,
|
|
args
|
|
]);
|
|
}
|
|
},
|
|
hello: function () {
|
|
this._routeReady = true;
|
|
if (this._routeQueue.length) {
|
|
var queue = this._routeQueue;
|
|
this._routeQueue = [];
|
|
queue.forEach(function (args) {
|
|
this._sendMessage(args[0], args[1]);
|
|
}.bind(this));
|
|
}
|
|
},
|
|
clearAll: function () {
|
|
var mozAlarms = navigator.mozAlarms;
|
|
if (!mozAlarms) {
|
|
return;
|
|
}
|
|
var r = mozAlarms.getAll();
|
|
r.onsuccess = function (event) {
|
|
var alarms = event.target.result;
|
|
if (!alarms) {
|
|
return;
|
|
}
|
|
alarms.forEach(function (alarm) {
|
|
if (alarm.data && alarm.data.type === 'sync') {
|
|
mozAlarms.remove(alarm.id);
|
|
}
|
|
});
|
|
}.bind(this);
|
|
r.onerror = function (err) {
|
|
console.error('cronsync-main clearAll mozAlarms.getAll: error: ' + err);
|
|
}.bind(this);
|
|
},
|
|
ensureSync: function (syncData) {
|
|
var mozAlarms = navigator.mozAlarms;
|
|
if (!mozAlarms) {
|
|
console.warn('no mozAlarms support!');
|
|
return;
|
|
}
|
|
debug('ensureSync called');
|
|
var request = mozAlarms.getAll();
|
|
request.onsuccess = function (event) {
|
|
debug('success!');
|
|
var alarms = event.target.result;
|
|
if (!alarms) {
|
|
alarms = [];
|
|
}
|
|
var expiredAlarmIds = [], okAlarmIntervals = {}, uniqueAlarms = {};
|
|
alarms.forEach(function (alarm) {
|
|
if (!alarm.data || !alarm.data.type || alarm.data.type !== 'sync') {
|
|
return;
|
|
}
|
|
var intervalKey = 'interval' + alarm.data.interval, wantedAccountIds = syncData[intervalKey];
|
|
if (!wantedAccountIds || !hasSameValues(wantedAccountIds, alarm.data.accountIds)) {
|
|
debug('account array mismatch, canceling existing alarm');
|
|
expiredAlarmIds.push(alarm.id);
|
|
} else {
|
|
var interval = toInterval(intervalKey), now = Date.now(), alarmTime = alarm.data.timestamp, accountKey = makeAccountKey(wantedAccountIds);
|
|
if (interval && !uniqueAlarms.hasOwnProperty(accountKey) && alarmTime > now && alarmTime < now + interval) {
|
|
debug('existing alarm is OK: ' + accountKey);
|
|
uniqueAlarms[accountKey] = true;
|
|
okAlarmIntervals[intervalKey] = true;
|
|
} else {
|
|
debug('existing alarm is out of interval range, canceling');
|
|
expiredAlarmIds.push(alarm.id);
|
|
}
|
|
}
|
|
});
|
|
expiredAlarmIds.forEach(function (alarmId) {
|
|
mozAlarms.remove(alarmId);
|
|
});
|
|
var alarmMax = 0, alarmCount = 0, self = this;
|
|
function done() {
|
|
alarmCount += 1;
|
|
if (alarmCount < alarmMax) {
|
|
return;
|
|
}
|
|
debug('ensureSync completed');
|
|
self._sendMessage('syncEnsured');
|
|
}
|
|
Object.keys(syncData).forEach(function (intervalKey) {
|
|
if (okAlarmIntervals.hasOwnProperty(intervalKey)) {
|
|
return;
|
|
}
|
|
var interval = toInterval(intervalKey), accountIds = syncData[intervalKey], date = new Date(Date.now() + interval);
|
|
if (!interval) {
|
|
return;
|
|
}
|
|
alarmMax += 1;
|
|
var alarmRequest = mozAlarms.add(date, 'ignoreTimezone', makeData(accountIds, interval, date));
|
|
alarmRequest.onsuccess = function () {
|
|
debug('success: mozAlarms.add for ' + 'IDs: ' + accountIds + ' at ' + interval + 'ms');
|
|
done();
|
|
};
|
|
alarmRequest.onerror = function (err) {
|
|
console.error('cronsync-main mozAlarms.add for IDs: ' + accountIds + ' failed: ' + err);
|
|
};
|
|
});
|
|
if (!alarmMax) {
|
|
done();
|
|
}
|
|
}.bind(this);
|
|
request.onerror = function (err) {
|
|
console.error('cronsync-main ensureSync mozAlarms.getAll: error: ' + err);
|
|
};
|
|
}
|
|
};
|
|
if (navigator.mozSetMessageHandler) {
|
|
navigator.mozSetMessageHandler('alarm', function onAlarm(alarm) {
|
|
console.log('mozSetMessageHandler: received an alarm');
|
|
if (window.hasOwnProperty('appDispatchedMessage')) {
|
|
window.appDispatchedMessage = true;
|
|
}
|
|
var data = alarm.data;
|
|
if (!data || data.type !== 'sync') {
|
|
return;
|
|
}
|
|
if (navigator.requestWakeLock) {
|
|
var locks = [navigator.requestWakeLock('cpu')];
|
|
debug('wake locks acquired: ' + locks + ' for account IDs: ' + data.accountIds);
|
|
evt.emitWhenListener('cronSyncWakeLocks', makeAccountKey(data.accountIds), locks);
|
|
}
|
|
debug('alarm dispatch started at ' + new Date());
|
|
dispatcher._sendMessage('alarm', [
|
|
data.accountIds,
|
|
data.interval
|
|
]);
|
|
});
|
|
}
|
|
var routeRegistration = {
|
|
name: 'cronsync',
|
|
sendMessage: null,
|
|
dispatch: dispatcher
|
|
};
|
|
return routeRegistration;
|
|
});
|
|
define('ext/worker-support/devicestorage-main', [], function () {
|
|
'use strict';
|
|
function debug(str) {
|
|
dump('DeviceStorage: ' + str + '\n');
|
|
}
|
|
function save(uid, cmd, storage, blob, filename, registerDownload) {
|
|
var deviceStorage = navigator.getDeviceStorage(storage);
|
|
if (!deviceStorage) {
|
|
self.sendMessage(uid, cmd, [
|
|
false,
|
|
'no-device-storage',
|
|
null,
|
|
false
|
|
]);
|
|
return;
|
|
}
|
|
var req = deviceStorage.addNamed(blob, filename);
|
|
req.onerror = function () {
|
|
self.sendMessage(uid, cmd, [
|
|
false,
|
|
req.error.name,
|
|
null,
|
|
false
|
|
]);
|
|
};
|
|
req.onsuccess = function (e) {
|
|
var prefix = '';
|
|
if (typeof window.IS_GELAM_TEST !== 'undefined') {
|
|
prefix = 'TEST_PREFIX/';
|
|
}
|
|
var savedPath = prefix + e.target.result;
|
|
var registering = false;
|
|
if (registerDownload) {
|
|
var downloadManager = navigator.mozDownloadManager;
|
|
console.warn('have downloadManager?', !!downloadManager, 'have adoptDownload?', downloadManager && !!downloadManager.adoptDownload);
|
|
if (downloadManager && downloadManager.adoptDownload) {
|
|
try {
|
|
var fullPath = e.target.result;
|
|
var firstSlash = fullPath.indexOf('/', 2);
|
|
var storageName = fullPath.substring(1, firstSlash);
|
|
var storagePath = fullPath.substring(firstSlash + 1);
|
|
console.log('adopting download', deviceStorage.storageName, e.target.result);
|
|
registering = true;
|
|
downloadManager.adoptDownload({
|
|
totalBytes: blob.size,
|
|
url: '',
|
|
storageName: storageName,
|
|
storagePath: storagePath,
|
|
contentType: blob.type,
|
|
startTime: new Date(Date.now())
|
|
}).then(function () {
|
|
console.log('registered download with download manager');
|
|
self.sendMessage(uid, cmd, [
|
|
true,
|
|
null,
|
|
savedPath,
|
|
true
|
|
]);
|
|
}, function () {
|
|
console.warn('failed to register download with download manager');
|
|
self.sendMessage(uid, cmd, [
|
|
true,
|
|
null,
|
|
savedPath,
|
|
false
|
|
]);
|
|
});
|
|
} catch (ex) {
|
|
console.error('Problem adopting download!:', ex, '\n', ex.stack);
|
|
}
|
|
} else {
|
|
console.log('download manager not available, not registering.');
|
|
}
|
|
} else {
|
|
console.log('do not want to register download');
|
|
}
|
|
if (!registering) {
|
|
self.sendMessage(uid, cmd, [
|
|
true,
|
|
null,
|
|
savedPath,
|
|
false
|
|
]);
|
|
}
|
|
};
|
|
}
|
|
var self = {
|
|
name: 'devicestorage',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
debug('process ' + cmd);
|
|
switch (cmd) {
|
|
case 'save':
|
|
save(uid, cmd, args[0], args[1], args[2], args[3]);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
return self;
|
|
});
|
|
define('ext/worker-support/maildb-main', [], function () {
|
|
'use strict';
|
|
function debug(str) {
|
|
dump('MailDB: ' + str + '\n');
|
|
}
|
|
var db = null;
|
|
function open(uid, cmd, args) {
|
|
db = self._debugDB = new MailDB(args[0], function () {
|
|
self.sendMessage(uid, cmd, Array.prototype.slice.call(arguments));
|
|
});
|
|
}
|
|
function others(uid, cmd, args) {
|
|
if (!Array.isArray(args))
|
|
args = [];
|
|
args.push(function () {
|
|
self.sendMessage(uid, cmd, Array.prototype.slice.call(arguments));
|
|
});
|
|
if (!db._db)
|
|
console.warn('trying to call', cmd, 'on apparently dead db. skipping.');
|
|
else
|
|
db[cmd].apply(db, args);
|
|
}
|
|
var self = {
|
|
name: 'maildb',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
switch (cmd) {
|
|
case 'open':
|
|
open(uid, cmd, args);
|
|
break;
|
|
default:
|
|
others(uid, cmd, args);
|
|
break;
|
|
}
|
|
},
|
|
_debugDB: null
|
|
};
|
|
var IndexedDB;
|
|
if ('indexedDB' in window && window.indexedDB) {
|
|
IndexedDB = window.indexedDB;
|
|
} else if ('mozIndexedDB' in window && window.mozIndexedDB) {
|
|
IndexedDB = window.mozIndexedDB;
|
|
} else if ('webkitIndexedDB' in window && window.webkitIndexedDB) {
|
|
IndexedDB = window.webkitIndexedDB;
|
|
} else {
|
|
console.error('No IndexedDB!');
|
|
throw new Error('I need IndexedDB; load me in a content page universe!');
|
|
}
|
|
var CUR_VERSION = 22;
|
|
var FRIENDLY_LAZY_DB_UPGRADE_VERSION = 5;
|
|
var TBL_CONFIG = 'config', CONFIG_KEY_ROOT = 'config', CONFIG_KEYPREFIX_ACCOUNT_DEF = 'accountDef:';
|
|
var TBL_FOLDER_INFO = 'folderInfo';
|
|
var TBL_HEADER_BLOCKS = 'headerBlocks';
|
|
var TBL_BODY_BLOCKS = 'bodyBlocks';
|
|
function MailDB(testOptions, successCb, errorCb, upgradeCb) {
|
|
this._db = null;
|
|
this._lazyConfigCarryover = null;
|
|
this._fatalError = function (event) {
|
|
function explainSource(source) {
|
|
if (!source)
|
|
return 'unknown source';
|
|
if (source instanceof IDBObjectStore)
|
|
return 'object store "' + source.name + '"';
|
|
if (source instanceof IDBIndex)
|
|
return 'index "' + source.name + '" on object store "' + source.objectStore.name + '"';
|
|
if (source instanceof IDBCursor)
|
|
return 'cursor on ' + explainSource(source.source);
|
|
return 'unexpected source';
|
|
}
|
|
var explainedSource, target = event.target;
|
|
if (target instanceof IDBTransaction) {
|
|
explainedSource = 'transaction (' + target.mode + ')';
|
|
} else if (target instanceof IDBRequest) {
|
|
explainedSource = 'request as part of ' + (target.transaction ? target.transaction.mode : 'NO') + ' transaction on ' + explainSource(target.source);
|
|
} else {
|
|
explainedSource = target.toString();
|
|
}
|
|
console.error('indexedDB error:', target.error.name, 'from', explainedSource);
|
|
};
|
|
var dbVersion = CUR_VERSION;
|
|
if (testOptions && testOptions.dbDelta)
|
|
dbVersion += testOptions.dbDelta;
|
|
if (testOptions && testOptions.dbVersion)
|
|
dbVersion = testOptions.dbVersion;
|
|
var openRequest = IndexedDB.open('b2g-email', dbVersion), self = this;
|
|
openRequest.onsuccess = function (event) {
|
|
self._db = openRequest.result;
|
|
successCb();
|
|
};
|
|
openRequest.onupgradeneeded = function (event) {
|
|
console.log('MailDB in onupgradeneeded');
|
|
var db = openRequest.result;
|
|
if (event.oldVersion < FRIENDLY_LAZY_DB_UPGRADE_VERSION || testOptions && testOptions.nukeDb) {
|
|
self._nukeDB(db);
|
|
} else {
|
|
var trans = openRequest.transaction;
|
|
self.getConfig(function (configObj, accountInfos) {
|
|
if (configObj)
|
|
self._lazyConfigCarryover = {
|
|
oldVersion: event.oldVersion,
|
|
config: configObj,
|
|
accountInfos: accountInfos
|
|
};
|
|
self._nukeDB(db);
|
|
}, trans);
|
|
}
|
|
};
|
|
openRequest.onerror = this._fatalError;
|
|
}
|
|
MailDB.prototype = {
|
|
_nukeDB: function (db) {
|
|
var existingNames = db.objectStoreNames;
|
|
for (var i = 0; i < existingNames.length; i++) {
|
|
db.deleteObjectStore(existingNames[i]);
|
|
}
|
|
db.createObjectStore(TBL_CONFIG);
|
|
db.createObjectStore(TBL_FOLDER_INFO);
|
|
db.createObjectStore(TBL_HEADER_BLOCKS);
|
|
db.createObjectStore(TBL_BODY_BLOCKS);
|
|
},
|
|
close: function () {
|
|
if (this._db) {
|
|
this._db.close();
|
|
this._db = null;
|
|
}
|
|
},
|
|
getConfig: function (callback, trans) {
|
|
var transaction = trans || this._db.transaction([
|
|
TBL_CONFIG,
|
|
TBL_FOLDER_INFO
|
|
], 'readonly');
|
|
var configStore = transaction.objectStore(TBL_CONFIG), folderInfoStore = transaction.objectStore(TBL_FOLDER_INFO);
|
|
var configReq = configStore.mozGetAll(), folderInfoReq = folderInfoStore.mozGetAll();
|
|
configReq.onerror = this._fatalError;
|
|
folderInfoReq.onerror = this._fatalError;
|
|
var self = this;
|
|
folderInfoReq.onsuccess = function (event) {
|
|
var configObj = null, accounts = [], i, obj;
|
|
if (self._lazyConfigCarryover) {
|
|
var lazyCarryover = self._lazyConfigCarryover;
|
|
self._lazyConfigCarryover = null;
|
|
callback(configObj, accounts, lazyCarryover);
|
|
return;
|
|
}
|
|
for (i = 0; i < configReq.result.length; i++) {
|
|
obj = configReq.result[i];
|
|
if (obj.id === 'config')
|
|
configObj = obj;
|
|
else
|
|
accounts.push({
|
|
def: obj,
|
|
folderInfo: null
|
|
});
|
|
}
|
|
for (i = 0; i < folderInfoReq.result.length; i++) {
|
|
accounts[i].folderInfo = folderInfoReq.result[i];
|
|
}
|
|
try {
|
|
callback(configObj, accounts);
|
|
} catch (ex) {
|
|
console.error('Problem in configCallback', ex, '\n', ex.stack);
|
|
}
|
|
};
|
|
},
|
|
saveConfig: function (config) {
|
|
var req = this._db.transaction(TBL_CONFIG, 'readwrite').objectStore(TBL_CONFIG).put(config, 'config');
|
|
req.onerror = this._fatalError;
|
|
},
|
|
saveAccountDef: function (config, accountDef, folderInfo, callback) {
|
|
var trans = this._db.transaction([
|
|
TBL_CONFIG,
|
|
TBL_FOLDER_INFO
|
|
], 'readwrite');
|
|
var configStore = trans.objectStore(TBL_CONFIG);
|
|
configStore.put(config, 'config');
|
|
configStore.put(accountDef, CONFIG_KEYPREFIX_ACCOUNT_DEF + accountDef.id);
|
|
if (folderInfo) {
|
|
trans.objectStore(TBL_FOLDER_INFO).put(folderInfo, accountDef.id);
|
|
}
|
|
trans.onerror = this._fatalError;
|
|
if (callback) {
|
|
trans.oncomplete = function () {
|
|
callback();
|
|
};
|
|
}
|
|
},
|
|
loadHeaderBlock: function (folderId, blockId, callback) {
|
|
var req = this._db.transaction(TBL_HEADER_BLOCKS, 'readonly').objectStore(TBL_HEADER_BLOCKS).get(folderId + ':' + blockId);
|
|
req.onerror = this._fatalError;
|
|
req.onsuccess = function () {
|
|
callback(req.result);
|
|
};
|
|
},
|
|
loadBodyBlock: function (folderId, blockId, callback) {
|
|
var req = this._db.transaction(TBL_BODY_BLOCKS, 'readonly').objectStore(TBL_BODY_BLOCKS).get(folderId + ':' + blockId);
|
|
req.onerror = this._fatalError;
|
|
req.onsuccess = function () {
|
|
callback(req.result);
|
|
};
|
|
},
|
|
saveAccountFolderStates: function (accountId, folderInfo, perFolderStuff, deletedFolderIds, callback) {
|
|
var trans = this._db.transaction([
|
|
TBL_FOLDER_INFO,
|
|
TBL_HEADER_BLOCKS,
|
|
TBL_BODY_BLOCKS
|
|
], 'readwrite');
|
|
trans.onerror = this._fatalError;
|
|
trans.objectStore(TBL_FOLDER_INFO).put(folderInfo, accountId);
|
|
var headerStore = trans.objectStore(TBL_HEADER_BLOCKS), bodyStore = trans.objectStore(TBL_BODY_BLOCKS), i;
|
|
var operationQueue = [];
|
|
function addToQueue() {
|
|
var args = Array.slice(arguments);
|
|
var store = args.shift();
|
|
var type = args.shift();
|
|
operationQueue.push({
|
|
store: store,
|
|
type: type,
|
|
args: args
|
|
});
|
|
}
|
|
function workQueue() {
|
|
var pendingRequest = operationQueue.shift();
|
|
if (!pendingRequest)
|
|
return;
|
|
var store = pendingRequest.store;
|
|
var type = pendingRequest.type;
|
|
var request = store[type].apply(store, pendingRequest.args);
|
|
request.onsuccess = request.onerror = workQueue;
|
|
}
|
|
for (i = 0; i < perFolderStuff.length; i++) {
|
|
var pfs = perFolderStuff[i], block;
|
|
for (var headerBlockId in pfs.headerBlocks) {
|
|
block = pfs.headerBlocks[headerBlockId];
|
|
if (block)
|
|
addToQueue(headerStore, 'put', block, pfs.id + ':' + headerBlockId);
|
|
else
|
|
addToQueue(headerStore, 'delete', pfs.id + ':' + headerBlockId);
|
|
}
|
|
for (var bodyBlockId in pfs.bodyBlocks) {
|
|
block = pfs.bodyBlocks[bodyBlockId];
|
|
if (block)
|
|
addToQueue(bodyStore, 'put', block, pfs.id + ':' + bodyBlockId);
|
|
else
|
|
addToQueue(bodyStore, 'delete', pfs.id + ':' + bodyBlockId);
|
|
}
|
|
}
|
|
if (deletedFolderIds) {
|
|
for (i = 0; i < deletedFolderIds.length; i++) {
|
|
var folderId = deletedFolderIds[i], range = IDBKeyRange.bound(folderId + ':', folderId + ':\uFFF0', false, false);
|
|
addToQueue(headerStore, 'delete', range);
|
|
addToQueue(bodyStore, 'delete', range);
|
|
}
|
|
}
|
|
if (callback) {
|
|
trans.addEventListener('complete', function () {
|
|
callback();
|
|
});
|
|
}
|
|
workQueue();
|
|
return trans;
|
|
},
|
|
deleteAccount: function (accountId) {
|
|
var trans = this._db.transaction([
|
|
TBL_CONFIG,
|
|
TBL_FOLDER_INFO,
|
|
TBL_HEADER_BLOCKS,
|
|
TBL_BODY_BLOCKS
|
|
], 'readwrite');
|
|
trans.onerror = this._fatalError;
|
|
trans.objectStore(TBL_CONFIG).delete('accountDef:' + accountId);
|
|
trans.objectStore(TBL_FOLDER_INFO).delete(accountId);
|
|
var range = IDBKeyRange.bound(accountId + '/', accountId + '/\uFFF0', false, false);
|
|
trans.objectStore(TBL_HEADER_BLOCKS).delete(range);
|
|
trans.objectStore(TBL_BODY_BLOCKS).delete(range);
|
|
}
|
|
};
|
|
return self;
|
|
});
|
|
define('ext/async_blob_fetcher', ['exports'], function (exports) {
|
|
function asyncFetchBlobAsUint8Array(blob, callback) {
|
|
var blobUrl = URL.createObjectURL(blob);
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', blobUrl, true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.onload = function () {
|
|
if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {
|
|
callback(xhr.status);
|
|
return;
|
|
}
|
|
callback(null, new Uint8Array(xhr.response));
|
|
};
|
|
xhr.onerror = function () {
|
|
callback('error');
|
|
};
|
|
try {
|
|
xhr.send();
|
|
} catch (ex) {
|
|
console.error('XHR send() failure on blob');
|
|
callback('error');
|
|
}
|
|
URL.revokeObjectURL(blobUrl);
|
|
}
|
|
return { asyncFetchBlobAsUint8Array: asyncFetchBlobAsUint8Array };
|
|
});
|
|
;
|
|
define('ext/worker-support/net-main', [
|
|
'require',
|
|
'../async_blob_fetcher'
|
|
], function (require) {
|
|
'use strict';
|
|
var asyncFetchBlobAsUint8Array = require('../async_blob_fetcher').asyncFetchBlobAsUint8Array;
|
|
var sockInfoByUID = {};
|
|
function open(uid, host, port, options) {
|
|
var socket = navigator.mozTCPSocket;
|
|
var sock = socket.open(host, port, options);
|
|
var sockInfo = sockInfoByUID[uid] = {
|
|
uid: uid,
|
|
sock: sock,
|
|
activeBlob: null,
|
|
blobOffset: 0,
|
|
queuedData: null,
|
|
backlog: []
|
|
};
|
|
sock.onopen = function (evt) {
|
|
self.sendMessage(uid, 'onopen');
|
|
};
|
|
sock.onerror = function (evt) {
|
|
var err = evt.data;
|
|
var wrappedErr;
|
|
if (err && typeof err === 'object') {
|
|
wrappedErr = {
|
|
name: err.name,
|
|
type: err.type,
|
|
message: err.message
|
|
};
|
|
} else {
|
|
wrappedErr = err;
|
|
}
|
|
self.sendMessage(uid, 'onerror', wrappedErr);
|
|
};
|
|
sock.ondata = function (evt) {
|
|
var buf = evt.data;
|
|
self.sendMessage(uid, 'ondata', buf, [buf]);
|
|
};
|
|
sock.ondrain = function (evt) {
|
|
if (sockInfo.activeBlob && sockInfo.queuedData) {
|
|
console.log('net-main(' + sockInfo.uid + '): Socket drained, sending.');
|
|
sock.send(sockInfo.queuedData.buffer, 0, sockInfo.queuedData.byteLength);
|
|
sockInfo.queuedData = null;
|
|
fetchNextBlobChunk(sockInfo);
|
|
} else {
|
|
self.sendMessage(uid, 'ondrain');
|
|
}
|
|
};
|
|
sock.onclose = function (evt) {
|
|
self.sendMessage(uid, 'onclose');
|
|
delete sockInfoByUID[uid];
|
|
};
|
|
}
|
|
function beginBlobSend(sockInfo, blob) {
|
|
console.log('net-main(' + sockInfo.uid + '): Blob send of', blob.size, 'bytes');
|
|
sockInfo.activeBlob = blob;
|
|
sockInfo.blobOffset = 0;
|
|
sockInfo.queuedData = null;
|
|
fetchNextBlobChunk(sockInfo);
|
|
}
|
|
function fetchNextBlobChunk(sockInfo) {
|
|
if (sockInfo.blobOffset >= sockInfo.activeBlob.size) {
|
|
console.log('net-main(' + sockInfo.uid + '): Blob send completed.', 'backlog length:', sockInfo.backlog.length);
|
|
sockInfo.activeBlob = null;
|
|
var backlog = sockInfo.backlog;
|
|
while (backlog.length) {
|
|
var sendArgs = backlog.shift();
|
|
var data = sendArgs[0];
|
|
if (data instanceof Blob) {
|
|
beginBlobSend(sockInfo, data);
|
|
return;
|
|
}
|
|
sockInfo.sock.send(data, sendArgs[1], sendArgs[2]);
|
|
}
|
|
return;
|
|
}
|
|
var nextOffset = Math.min(sockInfo.blobOffset + self.BLOB_BLOCK_READ_SIZE, sockInfo.activeBlob.size);
|
|
console.log('net-main(' + sockInfo.uid + '): Fetching bytes', sockInfo.blobOffset, 'through', nextOffset, 'of', sockInfo.activeBlob.size);
|
|
var blobSlice = sockInfo.activeBlob.slice(sockInfo.blobOffset, nextOffset);
|
|
sockInfo.blobOffset = nextOffset;
|
|
function gotChunk(err, binaryDataU8) {
|
|
console.log('net-main(' + sockInfo.uid + '): Retrieved chunk');
|
|
if (err) {
|
|
sockInfo.sock.close();
|
|
return;
|
|
}
|
|
if (sockInfo.sock.bufferedAmount === 0) {
|
|
console.log('net-main(' + sockInfo.uid + '): Sending chunk immediately.');
|
|
sockInfo.sock.send(binaryDataU8.buffer, 0, binaryDataU8.byteLength);
|
|
fetchNextBlobChunk(sockInfo);
|
|
return;
|
|
}
|
|
sockInfo.queuedData = binaryDataU8;
|
|
}
|
|
;
|
|
asyncFetchBlobAsUint8Array(blobSlice, gotChunk);
|
|
}
|
|
function close(uid) {
|
|
var sockInfo = sockInfoByUID[uid];
|
|
if (!sockInfo)
|
|
return;
|
|
var sock = sockInfo.sock;
|
|
sock.close();
|
|
sock.onopen = null;
|
|
sock.onerror = null;
|
|
sock.ondata = null;
|
|
sock.ondrain = null;
|
|
sock.onclose = null;
|
|
self.sendMessage(uid, 'onclose');
|
|
delete sockInfoByUID[uid];
|
|
}
|
|
function write(uid, data, offset, length) {
|
|
var sockInfo = sockInfoByUID[uid];
|
|
if (!sockInfo) {
|
|
return;
|
|
}
|
|
if (sockInfo.activeBlob) {
|
|
sockInfo.backlog.push([
|
|
data,
|
|
offset,
|
|
length
|
|
]);
|
|
return;
|
|
}
|
|
self.sendMessage(uid, 'onprogress', []);
|
|
if (data instanceof Blob) {
|
|
beginBlobSend(sockInfo, data);
|
|
} else {
|
|
sockInfo.sock.send(data, offset, length);
|
|
}
|
|
}
|
|
function upgradeToSecure(uid) {
|
|
var sockInfo = sockInfoByUID[uid];
|
|
if (!sockInfo)
|
|
return;
|
|
sockInfo.sock.upgradeToSecure();
|
|
}
|
|
var self = {
|
|
name: 'netsocket',
|
|
sendMessage: null,
|
|
BLOB_BLOCK_READ_SIZE: 96 * 1024,
|
|
process: function (uid, cmd, args) {
|
|
switch (cmd) {
|
|
case 'open':
|
|
open(uid, args[0], args[1], args[2]);
|
|
break;
|
|
case 'close':
|
|
close(uid);
|
|
break;
|
|
case 'write':
|
|
write(uid, args[0], args[1], args[2]);
|
|
break;
|
|
case 'upgradeToSecure':
|
|
upgradeToSecure(uid);
|
|
break;
|
|
default:
|
|
console.error('Unhandled net-main command:', cmd);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
return self;
|
|
});
|
|
define('ext/worker-support/wakelocks-main', [], function () {
|
|
'use strict';
|
|
function debug(str) {
|
|
dump('WakeLocks: ' + str + '\n');
|
|
}
|
|
var nextId = 1;
|
|
var locks = {};
|
|
function requestWakeLock(type) {
|
|
var lock = navigator.requestWakeLock(type);
|
|
var id = nextId++;
|
|
locks[id] = lock;
|
|
return id;
|
|
}
|
|
var self = {
|
|
name: 'wakelocks',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
debug('process ' + cmd + ' ' + JSON.stringify(args));
|
|
switch (cmd) {
|
|
case 'requestWakeLock':
|
|
var type = args[0];
|
|
self.sendMessage(uid, cmd, [requestWakeLock(type)]);
|
|
break;
|
|
case 'unlock':
|
|
var id = args[0];
|
|
var lock = locks[id];
|
|
if (lock) {
|
|
lock.unlock();
|
|
delete locks[id];
|
|
}
|
|
self.sendMessage(uid, cmd, []);
|
|
}
|
|
}
|
|
};
|
|
return self;
|
|
});
|
|
window.setZeroTimeout = function (fn) {
|
|
setTimeout(function () {
|
|
fn();
|
|
}, 0);
|
|
};
|
|
define('ext/main-frame-setup', [
|
|
'./mailapi',
|
|
'./worker-support/main-router',
|
|
'./worker-support/configparser-main',
|
|
'./worker-support/cronsync-main',
|
|
'./worker-support/devicestorage-main',
|
|
'./worker-support/maildb-main',
|
|
'./worker-support/net-main',
|
|
'./worker-support/wakelocks-main',
|
|
'require'
|
|
], function ($mailapi, $router, $configparser, $cronsync, $devicestorage, $maildb, $net, $wakelocks, require) {
|
|
var control = {
|
|
name: 'control',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
var online = navigator.onLine;
|
|
control.sendMessage(uid, 'hello', [online]);
|
|
window.addEventListener('online', function (evt) {
|
|
control.sendMessage(uid, evt.type, [true]);
|
|
});
|
|
window.addEventListener('offline', function (evt) {
|
|
control.sendMessage(uid, evt.type, [false]);
|
|
});
|
|
$router.unregister(control);
|
|
}
|
|
};
|
|
var MailAPI = new $mailapi.MailAPI();
|
|
var bridge = {
|
|
name: 'bridge',
|
|
sendMessage: null,
|
|
process: function (uid, cmd, args) {
|
|
var msg = args;
|
|
if (msg.type === 'hello') {
|
|
delete MailAPI._fake;
|
|
MailAPI.__bridgeSend = function (msg) {
|
|
worker.postMessage({
|
|
uid: uid,
|
|
type: 'bridge',
|
|
msg: msg
|
|
});
|
|
};
|
|
MailAPI.config = msg.config;
|
|
MailAPI._storedSends.forEach(function (msg) {
|
|
MailAPI.__bridgeSend(msg);
|
|
});
|
|
MailAPI._storedSends = [];
|
|
} else {
|
|
MailAPI.__bridgeReceive(msg);
|
|
}
|
|
}
|
|
};
|
|
var worker = new Worker(require.toUrl('./worker-bootstrap.js'));
|
|
$router.useWorker(worker);
|
|
$router.register(control);
|
|
$router.register(bridge);
|
|
$router.register($configparser);
|
|
$router.register($cronsync);
|
|
$router.register($devicestorage);
|
|
$router.register($maildb);
|
|
$router.register($net);
|
|
$router.register($wakelocks);
|
|
return MailAPI;
|
|
});
|
|
; |