Merge branch 'staging' into oo2

This commit is contained in:
yflory
2018-09-03 10:39:34 +02:00
197 changed files with 20203 additions and 8239 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,10 @@ define([], function () {
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
var removeCp = function (str) {
return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
};
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
@@ -68,7 +72,11 @@ define([], function () {
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, '');
// NOTE: Hash version 0 contains a 32 characters nonce followed by a pipe
// at the beginning of each message on the server.
// We have to make sure our regex ignores this nonce using {0,20} (our IDs
// should only be 8 characters long)
return removeCp(msg);
};
var msgOut = function (msg) {
@@ -120,6 +128,8 @@ define([], function () {
lastKnownHash = msg.slice(0,64);
var isCp = /^cp\|/.test(msg);
var message = msgIn(peer, msg);
verbose(message);
@@ -130,7 +140,7 @@ define([], function () {
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
onMessage(message);
onMessage(peer, message, validateKey, isCp);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
@@ -233,7 +243,6 @@ define([], function () {
};
network.on('disconnect', function (reason) {
console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
@@ -256,7 +265,8 @@ define([], function () {
};
return {
start: start
start: start,
removeCp: removeCp
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);

View File

@@ -58,6 +58,14 @@ define([
localStorage[Constants.userHashKey] = sHash;
};
LocalStore.getBlockHash = function () {
return localStorage[Constants.blockHashKey];
};
LocalStore.setBlockHash = function (hash) {
localStorage[Constants.blockHashKey] = hash;
};
LocalStore.getAccountName = function () {
return localStorage[Constants.userNameKey];
};
@@ -66,10 +74,6 @@ define([
return typeof getUserHash() === "string";
};
LocalStore.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }
@@ -92,10 +96,11 @@ define([
});
};
var logoutHandlers = [];
LocalStore.logout = function (cb) {
LocalStore.logout = function (cb, isDeletion) {
[
Constants.userNameKey,
Constants.userHashKey,
Constants.blockHashKey,
'loginToken',
'plan',
].forEach(function (k) {
@@ -108,13 +113,15 @@ define([
// Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes
if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash());
LocalStore.setFSHash(Hash.createRandomHash('drive'));
}
eraseTempSessionValues();
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
if (!isDeletion) {
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb);

View File

@@ -0,0 +1,147 @@
define([
'/common/common-util.js',
'/api/config',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util, ApiConfig) {
var Nacl = window.nacl;
var Block = {};
Block.join = Util.uint8ArrayJoin;
// publickey <base64 string>
// signature <base64 string>
// block <base64 string>
// [b64_public, b64_sig, b64_block [version, nonce, content]]
Block.seed = function () {
return Nacl.hash(Nacl.util.decodeUTF8('pewpewpew'));
};
// should be deterministic from a seed...
Block.genkeys = function (seed) {
if (!(seed instanceof Uint8Array)) {
throw new Error('INVALID_SEED_FORMAT');
}
if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
throw new Error('INVALID_SEED_LENGTH');
}
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, // 32 bytes ...
};
};
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Nacl.util.decodeUTF8(content);
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
return Block.join([
[0],
nonce,
Nacl.secretbox(u8, nonce, keys.symmetric)
]);
};
// (uint8Array block) => payload object
Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
try {
return JSON.parse(Nacl.util.encodeUTF8(plaintext));
} catch (e) {
console.error(e);
return;
}
};
// (Uint8Array block) => signature
Block.sign = function (ciphertext, keys) {
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
};
Block.serialize = function (content, keys) {
// encrypt the content
var ciphertext = Block.encrypt(0, content, keys);
// generate a detached signature
var sig = Block.sign(ciphertext, keys);
// serialize {publickey, sig, ciphertext}
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
ciphertext: Nacl.util.encodeBase64(ciphertext),
};
};
Block.remove = function (keys) {
// sign the hash of the text 'DELETE_BLOCK'
var sig = Nacl.sign.detached(Nacl.hash(
Nacl.util.decodeUTF8('DELETE_BLOCK')), keys.sign.secretKey);
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
};
};
var urlSafeB64 = function (u8) {
return Nacl.util.encodeBase64(u8).replace(/\//g, '-');
};
Block.getBlockUrl = function (keys) {
var publicKey = urlSafeB64(keys.sign.publicKey);
// 'block/' here is hardcoded because it's hardcoded on the server
// if we want to make CryptPad work in server subfolders, we'll need
// to update this path derivation
return (ApiConfig.fileHost || window.location.origin)
+ '/block/' + publicKey.slice(0, 2) + '/' + publicKey;
};
Block.getBlockHash = function (keys) {
var absolute = Block.getBlockUrl(keys);
var symmetric = urlSafeB64(keys.symmetric);
return absolute + '#' + symmetric;
};
var decodeSafeB64 = function (b64) {
try {
return Nacl.util.decodeBase64(b64.replace(/\-/g, '/'));
} catch (e) {
console.error(e);
return;
}
};
Block.parseBlockHash = function (hash) {
if (typeof(hash) !== 'string') { return; }
var parts = hash.split('#');
if (parts.length !== 2) { return; }
try {
return {
href: parts[0],
keys: {
symmetric: decodeSafeB64(parts[1]),
}
};
} catch (e) {
console.error(e);
return;
}
};
return Block;
});

View File

@@ -0,0 +1,103 @@
define([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js',
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var sendMsg = Util.mkEvent();
var create = function () {
var Rpc = SRpc();
var postMessage = function (data) {
sendMsg.fire(data);
};
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
};
return {
query: function (data) {
msgEv.fire({data: data});
},
onMessage: function (cb) {
sendMsg.reg(function (data) {
setTimeout(function () {
cb(data);
});
});
},
create: create
};
});

View File

@@ -0,0 +1,178 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SW INIT');
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SW Channel created');
var clientId = client.id;
self.tabs[clientId].chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SW Connect callback');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
self.tabs[clientId].channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: self.tabs[clientId].channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
self.tabs[client.id].msgEv = msgEv;
self.tabs[client.id].close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
self.addEventListener('message', function (e) {
var cId = e.source.id;
if (e.data === "INIT") {
if (tabs[cId]) { return; }
tabs[cId] = {
client: e.source
};
init(e.source, function () {
postMsg(e.source, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (tabs[cId] && tabs[cId].close) {
console.log('leave');
tabs[cId].close();
}
} else if (self.tabs[cId] && self.tabs[cId].msgEv) {
self.tabs[cId].msgEv.fire(e);
}
});
self.addEventListener('install', function (e) {
debug('V1 installing…');
self.skipWaiting();
});
self.addEventListener('activate', function (e) {
debug('V1 now ready to handle fetches!');
});

View File

@@ -0,0 +1,180 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.port.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SharedW INIT');
require.config({
waitSeconds: 600
});
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SharedW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SharedW Channel created');
var clientId = client.id;
client.chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SharedW connecting to store...');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
self.store = data.returned;
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
client.channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: client.channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
client.msgEv = msgEv;
client.close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
onconnect = function(e) {
debug('New SharedWorker client');
var port = e.ports[0];
var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
var client = self.tabs[cId] = {
id: cId,
port: port
};
port.onmessage = function (e) {
if (e.data === "INIT") {
if (client.init) { return; }
client.init = true;
init(client, function () {
postMsg(client, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (client && client.close) {
console.log('leave');
client.close();
}
} else if (client && client.msgEv) {
client.msgEv.fire(e);
}
};
};

View File

@@ -1,193 +1,103 @@
define([
'/common/outer/async-store.js'
], function (Store) {
var Rpc = {};
], function (AStore) {
Rpc.query = function (cmd, data, cb) {
switch (cmd) {
// READY
case 'CONNECT': {
Store.init(data, cb); break;
}
case 'DISCONNECT': {
Store.disconnect(data, cb); break;
}
case 'CREATE_README': {
Store.createReadme(data, cb); break;
}
case 'MIGRATE_ANON_DRIVE': {
Store.migrateAnonDrive(data, cb); break;
}
var create = function () {
var Store = AStore.create();
var Rpc = {};
var queries = Rpc.queries = {
// Ready
CONNECT: Store.init,
DISCONNECT: Store.disconnect,
CREATE_README: Store.createReadme,
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
// RPC
case 'INIT_RPC': {
Store.initRpc(data, cb); break;
}
case 'UPDATE_PIN_LIMIT': {
Store.updatePinLimit(data, cb); break;
}
case 'GET_PIN_LIMIT': {
Store.getPinLimit(data, cb); break;
}
case 'CLEAR_OWNED_CHANNEL': {
Store.clearOwnedChannel(data, cb); break;
}
case 'REMOVE_OWNED_CHANNEL': {
Store.removeOwnedChannel(data, cb); break;
}
case 'UPLOAD_CHUNK': {
Store.uploadChunk(data, cb); break;
}
case 'UPLOAD_COMPLETE': {
Store.uploadComplete(data, cb); break;
}
case 'UPLOAD_STATUS': {
Store.uploadStatus(data, cb); break;
}
case 'UPLOAD_CANCEL': {
Store.uploadCancel(data, cb); break;
}
case 'PIN_PADS': {
Store.pinPads(data, cb); break;
}
case 'UNPIN_PADS': {
Store.unpinPads(data, cb); break;
}
case 'GET_DELETED_PADS': {
Store.getDeletedPads(data, cb); break;
}
case 'GET_PINNED_USAGE': {
Store.getPinnedUsage(data, cb); break;
}
INIT_RPC: Store.initRpc,
UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,
CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
UPLOAD_CHUNK: Store.uploadChunk,
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
REMOVE_LOGIN_BLOCK: Store.removeLoginBlock,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
GET_PINNED_USAGE: Store.getPinnedUsage,
// ANON RPC
case 'INIT_ANON_RPC': {
Store.initAnonRpc(data, cb); break;
}
case 'ANON_RPC_MESSAGE': {
Store.anonRpcMsg(data, cb); break;
}
case 'GET_FILE_SIZE': {
Store.getFileSize(data, cb); break;
}
case 'GET_MULTIPLE_FILE_SIZE': {
Store.getMultipleFileSize(data, cb); break;
}
INIT_ANON_RPC: Store.initAnonRpc,
ANON_RPC_MESSAGE: Store.anonRpcMsg,
GET_FILE_SIZE: Store.getFileSize,
GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize,
// Store
case 'GET': {
Store.get(data, cb); break;
}
case 'SET': {
Store.set(data, cb); break;
}
case 'ADD_PAD': {
Store.addPad(data, cb); break;
}
case 'SET_PAD_TITLE': {
Store.setPadTitle(data, cb); break;
}
case 'MOVE_TO_TRASH': {
Store.moveToTrash(data, cb); break;
}
case 'RESET_DRIVE': {
Store.resetDrive(data, cb); break;
}
case 'GET_METADATA': {
Store.getMetadata(data, cb); break;
}
case 'SET_DISPLAY_NAME': {
Store.setDisplayName(data, cb); break;
}
case 'SET_PAD_ATTRIBUTE': {
Store.setPadAttribute(data, cb); break;
}
case 'GET_PAD_ATTRIBUTE': {
Store.getPadAttribute(data, cb); break;
}
case 'SET_ATTRIBUTE': {
Store.setAttribute(data, cb); break;
}
case 'GET_ATTRIBUTE': {
Store.getAttribute(data, cb); break;
}
case 'LIST_ALL_TAGS': {
Store.listAllTags(data, cb); break;
}
case 'GET_TEMPLATES': {
Store.getTemplates(data, cb); break;
}
case 'GET_SECURE_FILES_LIST': {
Store.getSecureFilesList(data, cb); break;
}
case 'GET_PAD_DATA': {
Store.getPadData(data, cb); break;
}
case 'SET_INITIAL_PATH': {
Store.setInitialPath(data); break;
}
case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break;
}
GET: Store.get,
SET: Store.set,
ADD_PAD: Store.addPad,
SET_PAD_TITLE: Store.setPadTitle,
MOVE_TO_TRASH: Store.moveToTrash,
RESET_DRIVE: Store.resetDrive,
GET_METADATA: Store.getMetadata,
IS_ONLY_IN_SHARED_FOLDER: Store.isOnlyInSharedFolder,
SET_DISPLAY_NAME: Store.setDisplayName,
SET_PAD_ATTRIBUTE: Store.setPadAttribute,
GET_PAD_ATTRIBUTE: Store.getPadAttribute,
SET_ATTRIBUTE: Store.setAttribute,
GET_ATTRIBUTE: Store.getAttribute,
LIST_ALL_TAGS: Store.listAllTags,
GET_TEMPLATES: Store.getTemplates,
GET_SECURE_FILES_LIST: Store.getSecureFilesList,
GET_PAD_DATA: Store.getPadData,
GET_STRONGER_HASH: Store.getStrongerHash,
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
GET_SHARED_FOLDER: Store.getSharedFolder,
ADD_SHARED_FOLDER: Store.addSharedFolder,
// Messaging
case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break;
}
INVITE_FROM_USERLIST: Store.inviteFromUserlist,
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
// Messenger
case 'CONTACTS_GET_FRIEND_LIST': {
Store.messenger.getFriendList(data, cb); break;
}
case 'CONTACTS_GET_MY_INFO': {
Store.messenger.getMyInfo(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_INFO': {
Store.messenger.getFriendInfo(data, cb); break;
}
case 'CONTACTS_REMOVE_FRIEND': {
Store.messenger.removeFriend(data, cb); break;
}
case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
Store.messenger.openFriendChannel(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_STATUS': {
Store.messenger.getFriendStatus(data, cb); break;
}
case 'CONTACTS_GET_MORE_HISTORY': {
Store.messenger.getMoreHistory(data, cb); break;
}
case 'CONTACTS_SEND_MESSAGE': {
Store.messenger.sendMessage(data, cb); break;
}
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList,
CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo,
CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo,
CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend,
CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel,
CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus,
CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Pad
case 'SEND_PAD_MSG': {
Store.sendPadMsg(data, cb); break;
}
case 'JOIN_PAD': {
Store.joinPad(data, cb); break;
}
case 'GET_FULL_HISTORY': {
Store.getFullHistory(data, cb); break;
}
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
IS_NEW_CHANNEL: Store.isNewChannel,
// Drive
case 'DRIVE_USEROBJECT': {
Store.userObjectCommand(data, cb); break;
}
// Settings
case 'DELETE_ACCOUNT': {
Store.deleteAccount(data, cb); break;
}
case 'IS_NEW_CHANNEL': {
Store.isNewChannel(data, cb); break;
}
default: {
console.error("UNHANDLED_STORE_RPC");
DRIVE_USEROBJECT: Store.userObjectCommand,
// Settings,
DELETE_ACCOUNT: Store.deleteAccount,
};
break;
Rpc.query = function (cmd, data, cb) {
if (queries[cmd]) {
queries[cmd]('0', data, cb);
} else {
console.error('UNHANDLED_STORE_RPC');
}
}
};
// Internal calls
Rpc._removeClient = Store._removeClient;
Rpc._subscribeToDrive = Store._subscribeToDrive;
Rpc._subscribeToMessenger = Store._subscribeToMessenger;
return Rpc;
};
return Rpc;
return create;
});

View File

@@ -0,0 +1,4 @@
if (!self.crypto && !self.msCrypto) {
throw new Error("E_NOCRYPTO");
}
self.postMessage("OK");

View File

@@ -1,8 +1,9 @@
define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash) {
], function (FileCrypto, Hash, nThen) {
var Nacl = window.nacl;
var module = {};
@@ -10,83 +11,123 @@ define([
var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata;
var owned = file.owned;
// if it exists, path contains the new pad location in the drive
var path = file.path;
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var password = file.password;
var forceSave = file.forceSave;
var hash, secret, key, id, href;
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var getNewHash = function () {
hash = Hash.createRandomHash('file', password);
secret = Hash.getSecrets('file', hash, password);
key = secret.keys.cryptKey;
id = secret.channel;
href = '/file/#' + hash;
};
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
var getValidHash = function (cb) {
getNewHash();
common.getFileSize(href, password, function (err, size) {
if (err || typeof(size) !== "number") { throw new Error(err || "Invalid size!"); }
if (size === 0) { return void cb(); }
getValidHash();
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
var edPublic;
nThen(function (waitFor) {
// Generate a hash and check if the resulting id is valid (not already used)
getValidHash(waitFor());
}).nThen(function (waitFor) {
if (!owned) { return; }
common.getMetadata(waitFor(function (err, m) {
edPublic = m.priv.edPublic;
metadata.owners = [edPublic];
}));
}).nThen(function () {
var next = FileCrypto.encrypt(u8, metadata, key);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
});
}
};
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = Hash.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
common.setPadTitle(title || "", href, path, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(id, owned, function (e) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var title = metadata.name;
if (noStore) { return void onComplete(href); }
var data = {
title: title || "",
href: href,
path: path,
password: password,
channel: id,
owners: metadata.owners,
forceSave: forceSave
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
common.setPadAttribute('owners', metadata.owners, null, href);
});
});
}
next(again);
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(estimate, function (e) {
if (e) {
return void console.error(e);
}
next(again);
});
});
}
next(again);
});
});
};
return module;
});

View File

@@ -14,15 +14,11 @@ define([
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
console.error("unpinPads was not provided");
};
var pinPads = config.pinPads;
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn;
var workgroup = config.workgroup;
var sharedFolder = config.sharedFolder;
var edPublic = config.edPublic;
var ROOT = exp.ROOT;
@@ -31,6 +27,7 @@ define([
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
var debug = exp.debug;
@@ -50,35 +47,31 @@ define([
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
exp.debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
// Check if we already have this shared folder in our drive
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
return files[SHARED_FOLDERS][k].channel === data.channel;
})) {
return void cb ('EEXISTS');
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
// Add the folder
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void cb("EAUTH");
}
var id = Util.createRandomInteger();
files[SHARED_FOLDERS][id] = data;
cb(null, id);
};
// FILES DATA
@@ -87,21 +80,21 @@ define([
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells
// us they're already removed
exp.checkDeletedFiles = function (isOwnPadRemoved) {
// Nothing in FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
// FILES_DATA. If there are owned pads, remove them from server too.
exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
// If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
if (!sharedFolder && fd.owners && fd.owners.indexOf(edPublic) !== -1
&& channelId) {
if (channelId) { ownedRemoved.push(channelId); }
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
@@ -111,20 +104,21 @@ define([
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
}
});
}
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (channelId) { toClean.push(channelId); }
spliceFileData(id);
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
} else {
spliceFileData(id);
}
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved);
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
@@ -138,7 +132,7 @@ define([
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
@@ -146,14 +140,11 @@ define([
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
var id = path[1];
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
return void cb();
}
var ids = [];
@@ -193,11 +184,69 @@ define([
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
// FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(cb); }
else { cb(); }
};
// Move
// From another drive
exp.copyFromOtherDrive = function (path, element, data, key) {
// Copy files data
// We have to remove pads that are already in the current proxy to make sure
// we won't create duplicates
var toRemove = [];
Object.keys(data).forEach(function (id) {
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
var found = false;
for (var i in files[FILES_DATA]) {
if (files[FILES_DATA][i].channel === d.channel) {
// Update href?
if (!files[FILES_DATA][i].href) { files[FILES_DATA][i].href = d.href; }
found = true;
break;
}
}
if (found) {
toRemove.push(id);
return;
}
files[FILES_DATA][id] = data[id];
});
// Remove existing pads from the "element" variable
if (exp.isFile(element) && toRemove.indexOf(element) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
return;
} else if (exp.isFolder(element)) {
var _removeExisting = function (root) {
for (var k in root) {
if (exp.isFile(root[k])) {
if (toRemove.indexOf(root[k]) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
delete root[k];
}
} else if (exp.isFolder(root[k])) {
_removeExisting(root[k]);
}
}
};
_removeExisting(element);
}
// Copy file or folder
var newParent = exp.find(path);
var tempName = exp.isFile(element) ? Hash.createChannelId() : key;
var newName = exp.getAvailableName(newParent, tempName);
newParent[newName] = element;
};
// From the same drive
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
@@ -260,7 +309,6 @@ define([
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
@@ -269,14 +317,7 @@ define([
};
// REPLACE
exp.replace = function (o, n) {
var idO = exp.getIdFromHref(o);
if (!idO || !exp.isFile(idO)) { return; }
var data = exp.getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// If all the occurences of an href are in the trash, remove them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = exp.getIdFromHref(href);
@@ -301,9 +342,9 @@ define([
};
exp.add = function (id, path) {
// TODO WW
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
id = Number(id);
var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
@@ -407,7 +448,6 @@ define([
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
@@ -427,7 +467,7 @@ define([
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
exp.fixFiles = function (silent) {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
@@ -436,6 +476,9 @@ define([
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
if (silent) { debug = function () {}; }
debug("Cleaning file system...");
var before = JSON.stringify(files);
@@ -471,6 +514,7 @@ define([
}
};
var fixTrashRoot = function () {
if (sharedFolder) { return; }
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
@@ -514,6 +558,7 @@ define([
}
};
var fixTemplate = function () {
if (sharedFolder) { return; }
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
@@ -545,7 +590,7 @@ define([
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
@@ -553,32 +598,73 @@ define([
for (var id in fd) {
id = Number(id);
var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
// Clean missing href
if (!el.href && !el.roHref) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
var parsed = Hash.parsePadUrl(el.href || el.roHref);
var secret;
// Clean invalid hash
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
// Clean invalid type
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
// If we have an edit link, check the view link
if (el.href && parsed.hashData.type === "pad") {
if (parsed.hashData.mode === "view") {
el.roHref = el.href;
delete el.href;
} else if (!el.roHref) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
} else {
var parsed2 = Hash.parsePadUrl(el.roHref);
if (!parsed2.hash || !parsed2.type) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
}
// Fix href
if (el.href && /^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
try {
if (!secret) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
}
el.channel = secret.channel;
console.log(el);
debug('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
@@ -590,6 +676,21 @@ define([
spliceFileData(id);
});
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; }
var sf = files[SHARED_FOLDERS];
var rootFiles = exp.getFiles([ROOT]);
var root = exp.find([ROOT]);
for (var id in sf) {
id = Number(id);
if (rootFiles.indexOf(id) === -1) {
console.log('missing' + id);
var newName = Hash.createChannelId();
root[newName] = id;
}
}
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
@@ -599,11 +700,10 @@ define([
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixTemplate();
fixFilesData();
fixDrive();
fixSharedFolders();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");

View File

@@ -0,0 +1,103 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var Rpc = SRpc();
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
onmessage = function (e) {
msgEv.fire(e);
};
});
});
});

View File

@@ -0,0 +1,151 @@
// This file provides the API for the channel for talking to and from the sandbox iframe.
define([
//'/common/sframe-protocol.js',
'/common/common-util.js'
], function (/*SFrameProtocol,*/ Util) {
var mkTxid = function () {
return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
};
var create = function (onMsg, postMsg, cb, isWorker) {
var chanLoaded;
var waitingData;
if (!isWorker) {
chanLoaded = false;
waitingData = [];
onMsg.reg(function (data) {
if (chanLoaded) { return; }
waitingData.push(data);
});
}
var evReady = Util.mkEvent(true);
var handlers = {};
var queries = {};
// list of handlers which are registered from the other side...
var insideHandlers = [];
var callWhenRegistered = {};
var chan = {};
// Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
chan.query = function (q, content, cb, opts) {
var txid = mkTxid();
opts = opts || {};
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
//console.log("Timeout making query " + q);
}, to);
queries[txid] = function (data, msg) {
clearTimeout(timeout);
delete queries[txid];
cb(undefined, data.content, msg);
};
evReady.reg(function () {
postMsg(JSON.stringify({
txid: txid,
content: content,
q: q
}));
});
};
// Fire an event. channel.event('EV_SOMETHING', { args: "whatever" });
var event = chan.event = function (e, content) {
evReady.reg(function () {
postMsg(JSON.stringify({ content: content, q: e }));
});
};
// Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... });
// If the type is a query, your handler will be invoked with a reply function that takes
// one argument (the content to reply with).
chan.on = function (queryType, handler, quiet) {
(handlers[queryType] = handlers[queryType] || []).push(function (data, msg) {
handler(data.content, function (replyContent) {
postMsg(JSON.stringify({
txid: data.txid,
content: replyContent
}));
}, msg);
});
if (!quiet) {
event('EV_REGISTER_HANDLER', queryType);
}
};
// If a particular handler is registered, call the callback immediately, otherwise it will be called
// when that handler is first registered.
// channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... });
chan.whenReg = function (queryType, cb, always) {
var reg = always;
if (insideHandlers.indexOf(queryType) > -1) {
cb();
} else {
reg = true;
}
if (reg) {
(callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb);
}
};
// Same as whenReg except it will invoke every time there is another registration, not just once.
chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); };
chan.on('EV_REGISTER_HANDLER', function (content) {
if (callWhenRegistered[content]) {
callWhenRegistered[content].forEach(function (f) { f(); });
delete callWhenRegistered[content];
}
insideHandlers.push(content);
});
chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
// Make sure both iframes are ready
var isReady =false;
chan.onReady = function (h) {
if (isReady) {
return void h();
}
if (typeof(h) !== "function") { return; }
chan.on('EV_RPC_READY', function () { isReady = true; h(); });
};
chan.ready = function () {
chan.whenReg('EV_RPC_READY', function () {
chan.event('EV_RPC_READY');
});
};
onMsg.reg(function (msg) {
var data = JSON.parse(msg.data);
if (typeof(data.q) === 'string' && handlers[data.q]) {
handlers[data.q].forEach(function (f) {
f(data || JSON.parse(msg.data), msg);
data = undefined;
});
} else if (typeof(data.q) === 'undefined' && queries[data.txid]) {
queries[data.txid](data, msg);
} else {
console.log("DROP Unhandled message");
console.log(msg.data, isWorker);
console.log(msg);
}
});
if (isWorker) {
evReady.fire();
} else {
chanLoaded = true;
waitingData.forEach(function (d) {
onMsg.fire(d);
});
waitingData = [];
}
cb(chan);
};
return { create: create };
});