54 changed files with 2386 additions and 1554 deletions
Split View
Diff Options
-
1.jshintrc
-
3bower.json
-
18config.example.js
-
16customize.dist/404.html
-
78customize.dist/four-oh-four.js
-
14customize.dist/messages.js
-
40customize.dist/src/less2/404.less
-
37customize.dist/src/less2/pages/page-404.less
-
8customize.dist/translations/messages.fr.js
-
9customize.dist/translations/messages.js
-
27rpc.js
-
16server.js
-
54www/assert/main.js
-
15www/auth/main.js
-
1www/common/common-constants.js
-
1www/common/common-language.js
-
66www/common/common-messaging.js
-
25www/common/common-messenger.js
-
47www/common/common-realtime.js
-
19www/common/common-ui-elements.js
-
37www/common/common-util.js
-
4www/common/cryptget.js
-
1218www/common/cryptpad-common.js
-
10www/common/dom-ready.js
-
2www/common/flat-dom.js
-
359www/common/fsStore.js
-
14www/common/mergeDrive.js
-
22www/common/metadata-manager.js
-
924www/common/outer/async-store.js
-
159www/common/outer/store-rpc.js
-
5www/common/outer/upload.js
-
14www/common/pinpad.js
-
6www/common/rpc.js
-
10www/common/sframe-app-outer.js
-
4www/common/sframe-common-file.js
-
224www/common/sframe-common-outer.js
-
8www/common/sframe-common.js
-
2www/common/toolbar3.js
-
30www/common/userObject.js
-
94www/common/wire.js
-
8www/contacts/main.js
-
11www/debug/inner.js
-
20www/drive/main.js
-
9www/drive/tests.js
-
2www/file/inner.js
-
8www/file/main.js
-
41www/filepicker/main.js
-
8www/poll/main.js
-
3www/profile/app-profile.less
-
63www/profile/inner.js
-
69www/profile/main.js
-
29www/settings/main.js
-
20www/todo/main.js
-
8www/whiteboard/main.js
@ -0,0 +1,16 @@ |
|||
<!DOCTYPE html> |
|||
<html class="cp" id="four-oh-four"> |
|||
<!-- If this file is not called customize.dist/src/template.html, it is generated --> |
|||
<head> |
|||
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title> |
|||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
|||
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/> |
|||
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script> |
|||
</head> |
|||
<body class="html"> |
|||
<noscript> |
|||
<h1>404</h1> |
|||
<h3>We couldn't find the page you were looking for</h3> |
|||
|
|||
</noscript> |
|||
@ -0,0 +1,78 @@ |
|||
define([ |
|||
'/api/config', |
|||
'/common/hyperscript.js', |
|||
'/common/outer/local-store.js', |
|||
'/customize/messages.js', |
|||
|
|||
'less!/customize/src/less2/pages/page-404.less', |
|||
], function (Config, h, LocalStore, Messages) { |
|||
var urlArgs = Config.requireConf.urlArgs; |
|||
var img = h('img#cp-logo', { |
|||
src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs |
|||
}); |
|||
|
|||
var brand = h('h1#cp-brand', 'CryptPad'); |
|||
var message = h('h2#cp-scramble', Messages.four04_pageNotFound); |
|||
var title = h('h2#cp-title', "404"); |
|||
|
|||
var loggedIn = LocalStore.isLoggedIn(); |
|||
var link = h('a#cp-link', { |
|||
href: loggedIn? '/drive/': '/', |
|||
}, loggedIn? Messages.header_logoTitle: Messages.header_homeTitle); |
|||
|
|||
var content = h('div#cp-main', [ |
|||
img, |
|||
brand, |
|||
title, |
|||
message, |
|||
link, |
|||
]); |
|||
document.body.appendChild(content); |
|||
|
|||
var die = function (n) { return Math.floor(Math.random() * n); }; |
|||
var randomChar = function () { |
|||
return String.fromCharCode(die(94) + 34); |
|||
}; |
|||
var mutate = function (S, i, c) { |
|||
var A = S.split(""); |
|||
A[i] = c; |
|||
return A.join(""); |
|||
}; |
|||
|
|||
var take = function (A) { |
|||
var n = die(A.length); |
|||
var choice = A[n]; |
|||
A.splice(n, 1); |
|||
return choice; |
|||
}; |
|||
|
|||
var makeDecryptor = function (el, t, difficulty, cb) { |
|||
var Orig = el.innerText; |
|||
var options = []; |
|||
el.innerText = el.innerText.split("").map(function (c, i) { |
|||
Orig[i] = c; |
|||
options.push(i); |
|||
return randomChar(); |
|||
}).join(""); |
|||
|
|||
return function f () { |
|||
if (die(difficulty) === 0) { |
|||
var choice = take(options); |
|||
el.innerText = mutate(el.innerText, choice, Orig.charAt(choice)); |
|||
} else { // make a superficial change
|
|||
el.innerText = mutate(el.innerText, |
|||
options[die(options.length)], |
|||
randomChar()); |
|||
} |
|||
setTimeout(options.length > 0? f: cb, t); |
|||
}; |
|||
}; |
|||
|
|||
makeDecryptor(brand, 70, 2, function () { })(); |
|||
makeDecryptor(title, 50, 14, function () { })(); |
|||
makeDecryptor(link, 20, 4, function () {})(); |
|||
makeDecryptor(message, 12, 3, function () { |
|||
console.log('done'); |
|||
})(); |
|||
}); |
|||
|
|||
@ -0,0 +1,40 @@ |
|||
@import (once) './include/font.less'; |
|||
.font_neuropolitical(); |
|||
.font_open-sans(); |
|||
|
|||
body.cp-page-index { @import "./pages/page-index.less"; } |
|||
body.cp-page-contact { @import "./pages/page-contact.less"; } |
|||
body.cp-page-login { @import "./pages/page-login.less"; } |
|||
body.cp-page-register { @import "./pages/page-register.less"; } |
|||
body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; } |
|||
body.cp-page-about { @import "./pages/page-about.less"; } |
|||
body.cp-page-privacy { @import "./pages/page-privacy.less"; } |
|||
body.cp-page-terms { @import "./pages/page-terms.less"; } |
|||
|
|||
// Set the HTML style for the apps which shouldn't have a body scrollbar |
|||
html.cp-app-noscroll { |
|||
@import "./include/app-noscroll.less"; |
|||
.app-noscroll_main(); |
|||
} |
|||
// Set the HTML style for printing slides |
|||
html.cp-app-print { |
|||
@import "./include/app-print.less"; |
|||
.app-print_main(); |
|||
} |
|||
|
|||
body.cp-readonly .cp-hidden-if-readonly { display:none !important; } |
|||
|
|||
body.cp-app-drive { @import "../../../drive/app-drive.less"; } |
|||
body.cp-app-pad { @import "../../../pad/app-pad.less"; } |
|||
body.cp-app-code { @import "../../../code/app-code.less"; } |
|||
body.cp-app-slide { @import "../../../slide/app-slide.less"; } |
|||
body.cp-app-file { @import "../../../file/app-file.less"; } |
|||
body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; } |
|||
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; } |
|||
body.cp-app-poll { @import "../../../poll/app-poll.less"; } |
|||
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; } |
|||
body.cp-app-todo { @import "../../../todo/app-todo.less"; } |
|||
body.cp-app-profile { @import "../../../profile/app-profile.less"; } |
|||
body.cp-app-settings { @import "../../../settings/app-settings.less"; } |
|||
body.cp-app-debug { @import "../../../debug/app-debug.less"; } |
|||
|
|||
@ -0,0 +1,37 @@ |
|||
@import (once) "../include/colortheme.less"; |
|||
@import (once) "../include/font.less"; |
|||
.font_neuropolitical(); |
|||
.font_open-sans(); |
|||
|
|||
html, body { |
|||
margin: 0px; |
|||
padding: 0px; |
|||
#cp-main { |
|||
|
|||
height: 100vh; |
|||
margin: 0px; |
|||
width: 100%; |
|||
padding-top: 5%; |
|||
text-align: center; |
|||
#cp-logo { |
|||
display: block; |
|||
max-width: 15%; |
|||
margin: auto; |
|||
} |
|||
#cp-brand { |
|||
font-family: neuropolitical; |
|||
font-size: 40px; |
|||
} |
|||
#cp-title { |
|||
font-size: 30px; |
|||
} |
|||
#cp-scramble, #cp-link { |
|||
font-size: 20px; |
|||
} |
|||
#cp-title, #cp-scramble, #cp-link { |
|||
//font-family: 'Open Sans'; |
|||
font-family: monospace; |
|||
} |
|||
} |
|||
} |
|||
|
|||
1218
www/common/cryptpad-common.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,10 @@ |
|||
define(function () { |
|||
return { |
|||
onReady: function (cb) { |
|||
if (document.readyState === 'complete') { return void cb(); } |
|||
document.onreadystatechange = function () { |
|||
if (document.readyState === 'complete') { cb(); } |
|||
}; |
|||
} |
|||
}; |
|||
}); |
|||
@ -1,359 +0,0 @@ |
|||
define([ |
|||
'jquery', |
|||
'/bower_components/chainpad-listmap/chainpad-listmap.js', |
|||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5', |
|||
'/common/userObject.js', |
|||
'/common/common-interface.js', |
|||
'/common/common-hash.js', |
|||
'/common/common-util.js', |
|||
'/common/common-constants.js', |
|||
'/common/migrate-user-object.js', |
|||
'/bower_components/chainpad/chainpad.dist.js', |
|||
'/common/outer/network-config.js', |
|||
'/common/outer/local-store.js', |
|||
], function ($, Listmap, Crypto, FO, UI, Hash, Util, Constants, Migrate, ChainPad, NetConfig, |
|||
LocalStore) { |
|||
/* |
|||
This module uses localStorage, which is synchronous, but exposes an |
|||
asyncronous API. This is so that we can substitute other storage |
|||
methods. |
|||
|
|||
To override these methods, create another file at: |
|||
/customize/storage.js |
|||
*/ |
|||
|
|||
var Store = {}; |
|||
var store; |
|||
|
|||
var initStore = function (filesOp, storeObj, exp) { |
|||
var ret = {}; |
|||
|
|||
var safeSet = function (key, val) { |
|||
storeObj[key] = val; |
|||
}; |
|||
|
|||
// Store uses nodebacks...
|
|||
ret.set = function (key, val, cb) { |
|||
safeSet(key, val); |
|||
cb(); |
|||
}; |
|||
|
|||
// implement in alternative store
|
|||
ret.setBatch = function (map, cb) { |
|||
Object.keys(map).forEach(function (key) { |
|||
safeSet(key, map[key]); |
|||
}); |
|||
cb(void 0, map); |
|||
}; |
|||
|
|||
ret.setDrive = function (key, val, cb) { |
|||
storeObj.drive[key] = val; |
|||
cb(); |
|||
}; |
|||
|
|||
var safeGet = function (key) { |
|||
return storeObj[key]; |
|||
}; |
|||
|
|||
ret.get = function (key, cb) { |
|||
cb(void 0, safeGet(key)); |
|||
}; |
|||
|
|||
// implement in alternative store
|
|||
ret.getBatch = function (keys, cb) { |
|||
var res = {}; |
|||
keys.forEach(function (key) { |
|||
res[key] = safeGet(key); |
|||
}); |
|||
cb(void 0, res); |
|||
}; |
|||
|
|||
var getAttributeObject = function (attr) { |
|||
if (typeof attr === "string") { |
|||
console.error('DEPRECATED: use setAttribute with an array, not a string'); |
|||
return { |
|||
obj: storeObj.settings, |
|||
key: attr |
|||
}; |
|||
} |
|||
if (!Array.isArray(attr)) { throw new Error("Attribute must be string or array"); } |
|||
if (attr.length === 0) { throw new Error("Attribute can't be empty"); } |
|||
var obj = storeObj.settings; |
|||
attr.forEach(function (el, i) { |
|||
if (i === attr.length-1) { return; } |
|||
if (!obj[el]) { |
|||
obj[el] = {}; |
|||
} |
|||
else if (typeof obj[el] !== "object") { throw new Error("Wrong attribute"); } |
|||
obj = obj[el]; |
|||
}); |
|||
return { |
|||
obj: obj, |
|||
key: attr[attr.length-1] |
|||
}; |
|||
}; |
|||
ret.setAttribute = function (attr, value, cb) { |
|||
try { |
|||
var object = getAttributeObject(attr); |
|||
object.obj[object.key] = value; |
|||
} catch (e) { return void cb(e); } |
|||
cb(); |
|||
}; |
|||
ret.getAttribute = function (attr, cb) { |
|||
var object; |
|||
try { |
|||
object = getAttributeObject(attr); |
|||
} catch (e) { return void cb(e); } |
|||
cb(null, object.obj[object.key]); |
|||
}; |
|||
ret.setPadAttribute = filesOp.setPadAttribute; |
|||
ret.getPadAttribute = filesOp.getPadAttribute; |
|||
ret.getIdFromHref = filesOp.getIdFromHref; |
|||
|
|||
ret.getDrive = function (key, cb) { |
|||
cb(void 0, storeObj.drive[key]); |
|||
}; |
|||
|
|||
var safeRemove = function (key) { |
|||
delete storeObj[key]; |
|||
}; |
|||
|
|||
ret.remove = function (key, cb) { |
|||
safeRemove(key); |
|||
cb(); |
|||
}; |
|||
|
|||
// implement in alternative store
|
|||
ret.removeBatch = function (keys, cb) { |
|||
keys.forEach(function (key) { |
|||
safeRemove(key); |
|||
}); |
|||
cb(); |
|||
}; |
|||
|
|||
ret.keys = function (cb) { |
|||
cb(void 0, Object.keys(storeObj)); |
|||
}; |
|||
|
|||
ret.removeData = filesOp.removeData; |
|||
ret.pushData = filesOp.pushData; |
|||
ret.addPad = filesOp.add; |
|||
|
|||
ret.forgetPad = function (href, cb) { |
|||
filesOp.forget(href); |
|||
cb(); |
|||
}; |
|||
|
|||
ret.listTemplates = function () { |
|||
var templateFiles = filesOp.getFiles(['template']); |
|||
var res = []; |
|||
templateFiles.forEach(function (f) { |
|||
var data = filesOp.getFileData(f); |
|||
res.push(JSON.parse(JSON.stringify(data))); |
|||
}); |
|||
return res; |
|||
}; |
|||
|
|||
ret.getProxy = function () { |
|||
return exp; |
|||
}; |
|||
|
|||
ret.getLoginName = function () { |
|||
return storeObj.login_name; |
|||
}; |
|||
|
|||
ret.repairDrive = function () { |
|||
filesOp.fixFiles(); |
|||
}; |
|||
|
|||
ret.getEmptyObject = function () { |
|||
return filesOp.getStructure(); |
|||
}; |
|||
|
|||
ret.replace = filesOp.replace; |
|||
|
|||
ret.restoreHref = filesOp.restoreHref; |
|||
|
|||
ret.changeHandlers = []; |
|||
|
|||
ret.change = function () {}; |
|||
|
|||
ret.getProfile = function () { |
|||
return storeObj.profile; |
|||
}; |
|||
|
|||
return ret; |
|||
}; |
|||
|
|||
var tryParsing = function (x) { |
|||
try { return JSON.parse(x); } |
|||
catch (e) { |
|||
console.error(e); |
|||
return null; |
|||
} |
|||
}; |
|||
|
|||
var onReady = function (f, proxy, Cryptpad, exp) { |
|||
var fo = exp.fo = FO.init(proxy.drive, { |
|||
Cryptpad: Cryptpad, |
|||
loggedIn: LocalStore.isLoggedIn() |
|||
}); |
|||
var todo = function () { |
|||
fo.fixFiles(); |
|||
|
|||
Migrate(proxy, Cryptpad); |
|||
|
|||
store = initStore(fo, proxy, exp); |
|||
if (typeof(f) === 'function') { |
|||
f(void 0, store); |
|||
} |
|||
//storeObj = proxy;
|
|||
|
|||
var requestLogin = function () { |
|||
// log out so that you don't go into an endless loop...
|
|||
LocalStore.logout(); |
|||
|
|||
// redirect them to log in, and come back when they're done.
|
|||
sessionStorage.redirectTo = window.location.href; |
|||
window.location.href = '/login/'; |
|||
}; |
|||
|
|||
var tokenKey = 'loginToken'; |
|||
if (LocalStore.isLoggedIn()) { |
|||
/* This isn't truly secure, since anyone who can read the user's object can |
|||
set their local loginToken to match that in the object. However, it exposes |
|||
a UI that will work most of the time. */ |
|||
|
|||
// every user object should have a persistent, random number
|
|||
if (typeof(proxy.loginToken) !== 'number') { |
|||
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); |
|||
} |
|||
|
|||
// copy User_hash into sessionStorage because cross-domain iframes
|
|||
// on safari replaces localStorage with sessionStorage or something
|
|||
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); } |
|||
|
|||
var localToken = tryParsing(localStorage.getItem(tokenKey)); |
|||
if (localToken === null) { |
|||
// if that number hasn't been set to localStorage, do so.
|
|||
localStorage.setItem(tokenKey, proxy.loginToken); |
|||
} else if (localToken !== proxy[tokenKey]) { |
|||
// if it has been, and the local number doesn't match that in
|
|||
// the user object, request that they reauthenticate.
|
|||
return void requestLogin(); |
|||
} |
|||
} |
|||
|
|||
if (!proxy.settings || !proxy.settings.general || |
|||
typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') { |
|||
proxy.settings = proxy.settings || {}; |
|||
proxy.settings.general = proxy.settings.general || {}; |
|||
proxy.settings.general.allowUserFeedback = true; |
|||
} |
|||
|
|||
if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { |
|||
// even anonymous users should have a persistent, unique-ish id
|
|||
console.log('generating a persistent identifier'); |
|||
proxy.uid = Hash.createChannelId(); |
|||
} |
|||
|
|||
// if the user is logged in, but does not have signing keys...
|
|||
if (LocalStore.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) || |
|||
!Cryptpad.hasCurveKeys(proxy))) { |
|||
return void requestLogin(); |
|||
} |
|||
|
|||
proxy.on('change', [Constants.displayNameKey], function (o, n) { |
|||
if (typeof(n) !== "string") { return; } |
|||
Cryptpad.changeDisplayName(n); |
|||
}); |
|||
proxy.on('change', ['profile'], function () { |
|||
// Trigger userlist update when the avatar has changed
|
|||
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]); |
|||
}); |
|||
proxy.on('change', ['friends'], function () { |
|||
// Trigger userlist update when the avatar has changed
|
|||
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]); |
|||
}); |
|||
proxy.on('change', [tokenKey], function () { |
|||
var localToken = tryParsing(localStorage.getItem(tokenKey)); |
|||
if (localToken !== proxy[tokenKey]) { |
|||
return void requestLogin(); |
|||
} |
|||
}); |
|||
}; |
|||
fo.migrate(todo); |
|||
}; |
|||
|
|||
var initialized = false; |
|||
|
|||
var init = function (f, Cryptpad) { |
|||
if (!Cryptpad || initialized) { return; } |
|||
initialized = true; |
|||
|
|||
var hash = LocalStore.getUserHash() || LocalStore.getFSHash() || Hash.createRandomHash(); |
|||
if (!hash) { |
|||
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); |
|||
} |
|||
var secret = Hash.getSecrets('drive', hash); |
|||
var listmapConfig = { |
|||
data: {}, |
|||
websocketURL: NetConfig.getWebsocketURL(), |
|||
channel: secret.channel, |
|||
readOnly: false, |
|||
validateKey: secret.keys.validateKey || undefined, |
|||
crypto: Crypto.createEncryptor(secret.keys), |
|||
userName: 'fs', |
|||
logLevel: 1, |
|||
ChainPad: ChainPad, |
|||
classic: true, |
|||
}; |
|||
|
|||
var exp = {}; |
|||
|
|||
var rt = window.rt = Listmap.create(listmapConfig); |
|||
|
|||
exp.realtime = rt.realtime; |
|||
exp.proxy = rt.proxy; |
|||
rt.proxy.on('create', function (info) { |
|||
exp.info = info; |
|||
if (!LocalStore.getUserHash()) { |
|||
LocalStore.setFSHash(Hash.getEditHashFromKeys(info.channel, secret.keys)); |
|||
} |
|||
}).on('ready', function () { |
|||
if (store) { return; } // the store is already ready, it is a reconnection
|
|||
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } |
|||
var drive = rt.proxy.drive; |
|||
// Creating a new anon drive: import anon pads from localStorage
|
|||
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey])) |
|||
&& !drive['filesData']) { |
|||
drive[Constants.oldStorageKey] = []; |
|||
onReady(f, rt.proxy, Cryptpad, exp); |
|||
return; |
|||
} |
|||
// Drive already exist: return the existing drive, don't load data from legacy store
|
|||
onReady(f, rt.proxy, Cryptpad, exp); |
|||
}) |
|||
.on('change', ['drive', 'migrate'], function () { |
|||
var path = arguments[2]; |
|||
var value = arguments[1]; |
|||
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { |
|||
rt.network.disconnect(); |
|||
rt.realtime.abort(); |
|||
UI.alert(Cryptpad.Messages.fs_migration, null, true); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
Store.ready = function (f, Cryptpad) { |
|||
if (store) { // Store.ready probably called twice, store already ready
|
|||
if (typeof(f) === 'function') { |
|||
f(void 0, store); |
|||
} |
|||
} else { |
|||
init(f, Cryptpad); |
|||
} |
|||
}; |
|||
|
|||
return Store; |
|||
}); |
|||
@ -0,0 +1,924 @@ |
|||
define([ |
|||
'/common/userObject.js', |
|||
'/common/migrate-user-object.js', |
|||
'/common/common-hash.js', |
|||
'/common/common-util.js', |
|||
'/common/common-constants.js', |
|||
'/common/common-feedback.js', |
|||
'/common/common-realtime.js', |
|||
'/common/common-messaging.js', |
|||
'/common/common-messenger.js', |
|||
'/common/outer/network-config.js', |
|||
|
|||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5', |
|||
'/bower_components/chainpad/chainpad.dist.js', |
|||
'/bower_components/chainpad-listmap/chainpad-listmap.js', |
|||
], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, |
|||
NetConfig, |
|||
Crypto, ChainPad, Listmap) { |
|||
var Store = {}; |
|||
|
|||
var postMessage = function () {}; |
|||
|
|||
var storeHash; |
|||
|
|||
var store = {}; |
|||
|
|||
|
|||
var onSync = function (cb) { |
|||
Realtime.whenRealtimeSyncs(store.realtime, cb); |
|||
}; |
|||
|
|||
|
|||
Store.get = function (key, cb) { |
|||
cb(Util.find(store.proxy, key)); |
|||
}; |
|||
Store.set = function (data, cb) { |
|||
var path = data.key.slice(); |
|||
var key = path.pop(); |
|||
var obj = Util.find(store.proxy, path); |
|||
if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); } |
|||
if (typeof data.value === "undefined") { |
|||
delete obj[key]; |
|||
} else { |
|||
obj[key] = data.value; |
|||
} |
|||
onSync(cb); |
|||
}; |
|||
|
|||
Store.hasSigningKeys = function () { |
|||
if (!store.proxy) { return; } |
|||
return typeof(store.proxy.edPrivate) === 'string' && |
|||
typeof(store.proxy.edPublic) === 'string'; |
|||
}; |
|||
|
|||
Store.hasCurveKeys = function () { |
|||
if (!store.proxy) { return; } |
|||
return typeof(store.proxy.curvePrivate) === 'string' && |
|||
typeof(store.proxy.curvePublic) === 'string'; |
|||
}; |
|||
|
|||
var getUserChannelList = function () { |
|||
// start with your userHash...
|
|||
var userHash = storeHash; |
|||
if (!userHash) { return null; } |
|||
|
|||
var userParsedHash = Hash.parseTypeHash('drive', userHash); |
|||
var userChannel = userParsedHash && userParsedHash.channel; |
|||
if (!userChannel) { return null; } |
|||
|
|||
var list = store.userObject.getFiles([store.userObject.FILES_DATA]).map(function (id) { |
|||
return Hash.hrefToHexChannelId(store.userObject.getFileData(id).href); |
|||
}) |
|||
.filter(function (x) { return x; }); |
|||
|
|||
// Get the avatar
|
|||
var profile = store.proxy.profile; |
|||
if (profile) { |
|||
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null; |
|||
if (profileChan) { list.push(profileChan); } |
|||
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null; |
|||
if (avatarChan) { list.push(avatarChan); } |
|||
} |
|||
|
|||
if (store.proxy.friends) { |
|||
var fList = Messaging.getFriendChannelsList(store.proxy); |
|||
list = list.concat(fList); |
|||
} |
|||
|
|||
list.push(Util.base64ToHex(userChannel)); |
|||
list.sort(); |
|||
|
|||
return list; |
|||
}; |
|||
|
|||
var getCanonicalChannelList = function () { |
|||
return Util.deduplicateString(getUserChannelList()).sort(); |
|||
}; |
|||
|
|||
//////////////////////////////////////////////////////////////////
|
|||
/////////////////////// RPC //////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////
|
|||
|
|||
Store.pinPads = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
if (typeof(cb) !== 'function') { |
|||
console.error('expected a callback'); |
|||
} |
|||
|
|||
store.rpc.pin(data, function (e, hash) { |
|||
if (e) { return void cb({error: e}); } |
|||
cb({hash: hash}); |
|||
}); |
|||
}; |
|||
|
|||
Store.unpinPads = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
|
|||
store.rpc.unpin(data, function (e, hash) { |
|||
if (e) { return void cb({error: e}); } |
|||
cb({hash: hash}); |
|||
}); |
|||
}; |
|||
|
|||
var account = {}; |
|||
|
|||
Store.getPinnedUsage = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
|
|||
store.rpc.getFileListSize(function (err, bytes) { |
|||
if (typeof(bytes) === 'number') { |
|||
account.usage = bytes; |
|||
} |
|||
cb({bytes: bytes}); |
|||
}); |
|||
}; |
|||
|
|||
// Update for all users from accounts and return current user limits
|
|||
Store.updatePinLimit = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
store.rpc.updatePinLimits(function (e, limit, plan, note) { |
|||
if (e) { return void cb({error: e}); } |
|||
account.limit = limit; |
|||
account.plan = plan; |
|||
account.note = note; |
|||
cb(account); |
|||
}); |
|||
}; |
|||
// Get current user limits
|
|||
Store.getPinLimit = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
|
|||
var ALWAYS_REVALIDATE = true; |
|||
if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' || |
|||
typeof(account.plan) !== 'string' || |
|||
typeof(account.note) !== 'string') { |
|||
return void store.rpc.getLimit(function (e, limit, plan, note) { |
|||
if (e) { return void cb({error: e}); } |
|||
account.limit = limit; |
|||
account.plan = plan; |
|||
account.note = note; |
|||
cb(account); |
|||
}); |
|||
} |
|||
cb(account); |
|||
}; |
|||
|
|||
Store.clearOwnedChannel = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
store.rpc.clearOwnedChannel(data, function (err) { |
|||
cb({error:err}); |
|||
}); |
|||
}; |
|||
|
|||
Store.uploadComplete = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
store.rpc.uploadComplete(function (err, res) { |
|||
if (err) { return void cb({error:err}); } |
|||
cb(res); |
|||
}); |
|||
}; |
|||
|
|||
Store.uploadStatus = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
store.rpc.uploadStatus(data.size, function (err, res) { |
|||
if (err) { return void cb({error:err}); } |
|||
cb(res); |
|||
}); |
|||
}; |
|||
|
|||
Store.uploadCancel = function (data, cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
store.rpc.uploadCancel(function (err, res) { |
|||
if (err) { return void cb({error:err}); } |
|||
cb(res); |
|||
}); |
|||
}; |
|||
|
|||
var arePinsSynced = function (cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
|
|||
var list = getCanonicalChannelList(); |
|||
var local = Hash.hashChannelList(list); |
|||
store.rpc.getServerHash(function (e, hash) { |
|||
if (e) { return void cb(e); } |
|||
cb(null, hash === local); |
|||
}); |
|||
}; |
|||
|
|||
var resetPins = function (cb) { |
|||
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } |
|||
|
|||
var list = getCanonicalChannelList(); |
|||
store.rpc.reset(list, function (e, hash) { |
|||
if (e) { return void cb(e); } |
|||
cb(null, hash); |
|||
}); |
|||
}; |
|||
|
|||
Store.uploadChunk = function (data, cb) { |
|||
store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { |
|||
cb({ |
|||
error: e, |
|||
msg: msg |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
Store.initRpc = function (data, cb) { |
|||
require(['/common/pinpad.js'], function (Pinpad) { |
|||
Pinpad.create(store.network, store.proxy, function (e, call) { |
|||
if (e) { return void cb({error: e}); } |
|||
|
|||
store.rpc = call; |
|||
|
|||
Store.getPinLimit(null, function (obj) { |
|||
if (obj.error) { console.error(obj.error); } |
|||
account.limit = obj.limit; |
|||
account.plan = obj.plan; |
|||
account.note = obj.note; |
|||
cb(obj); |
|||
}); |
|||
|
|||
arePinsSynced(function (err, yes) { |
|||
if (!yes) { |
|||
resetPins(function (err) { |
|||
if (err) { return console.error(err); } |
|||
console.log('RESET DONE'); |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
//////////////////////////////////////////////////////////////////
|
|||
////////////////// ANON RPC //////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////
|
|||
Store.anonRpcMsg = function (data, cb) { |
|||
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } |
|||
store.anon_rpc.send(data.msg, data.data, function (err, res) { |
|||
if (err) { return void cb({error: err}); } |
|||
cb(res); |
|||
}); |
|||
}; |
|||
|
|||
Store.getFileSize = function (data, cb) { |
|||
console.log(data, cb); |
|||
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } |
|||
|
|||
var channelId = Hash.hrefToHexChannelId(data.href); |
|||
store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { |
|||
if (e) { return void cb({error: e}); } |
|||
if (response && response.length && typeof(response[0]) === 'number') { |
|||
return void cb({size: response[0]}); |
|||
} else { |
|||
cb({error: 'INVALID_RESPONSE'}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
Store.getMultipleFileSize = function (data, cb) { |
|||
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } |
|||
if (!Array.isArray(data.files)) { |
|||
return void cb({error: 'INVALID_FILE_LIST'}); |
|||
} |
|||
|
|||
store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) { |
|||
if (e) { return void cb({error: e}); } |
|||
if (res && res.length && typeof(res[0]) === 'object') { |
|||
cb({size: res[0]}); |
|||
} else { |
|||
cb({error: 'UNEXPECTED_RESPONSE'}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
Store.initAnonRpc = function (data, cb) { |
|||
require([ |
|||
'/common/rpc.js', |
|||
], function (Rpc) { |
|||
Rpc.createAnonymous(store.network, function (e, call) { |
|||
if (e) { return void cb({error: e}); } |
|||
store.anon_rpc = call; |
|||
cb(); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////
|
|||
/////////////////////// Store ////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////
|
|||
|
|||
// Get the metadata for sframe-common-outer
|
|||
Store.getMetadata = function (data, cb) { |
|||
var metadata = { |
|||
// "user" is shared with everybody via the userlist
|
|||
user: { |
|||
name: store.proxy[Constants.displayNameKey], |
|||
uid: store.proxy.uid, |
|||
avatar: Util.find(store.proxy, ['profile', 'avatar']), |
|||
profile: Util.find(store.proxy, ['profile', 'view']), |
|||
curvePublic: store.proxy.curvePublic, |
|||
}, |
|||
// "priv" is not shared with other users but is needed by the apps
|
|||
priv: { |
|||
edPublic: store.proxy.edPublic, |
|||
friends: store.proxy.friends, |
|||
settings: store.proxy.settings, |
|||
thumbnails: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']) |
|||
} |
|||
}; |
|||
cb(JSON.parse(JSON.stringify(metadata))); |
|||
}; |
|||
|
|||
var makePad = function (href, title) { |
|||
var now = +new Date(); |
|||
return { |
|||
href: href, |
|||
atime: now, |
|||
ctime: now, |
|||
title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), |
|||
}; |
|||
}; |
|||
|
|||
Store.addPad = function (data, cb) { |
|||
if (!data.href) { return void cb({error:'NO_HREF'}); } |
|||
var pad = makePad(data.href, data.title); |
|||
store.userObject.pushData(pad, function (e, id) { |
|||
if (e) { return void cb({error: "Error while adding a template:"+ e}); } |
|||
var path = data.path || ['root']; |
|||
store.userObject.add(id, path); |
|||
onSync(cb); |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* add a "What is CryptPad?" pad in the drive |
|||
* data |
|||
* - driveReadme |
|||
* - driveReadmeTitle |
|||
*/ |
|||
Store.createReadme = function (data, cb) { |
|||
require(['/common/cryptget.js'], function (Crypt) { |
|||
var hash = Hash.createRandomHash(); |
|||
Crypt.put(hash, data.driveReadme, function (e) { |
|||
if (e) { |
|||
return void cb({ error: "Error while creating the default pad:"+ e}); |
|||
} |
|||
var href = '/pad/#' + hash; |
|||
var fileData = { |
|||
href: href, |
|||
title: data.driveReadmeTitle, |
|||
atime: +new Date(), |
|||
ctime: +new Date() |
|||
}; |
|||
store.userObject.pushData(fileData, function (e, id) { |
|||
if (e) { |
|||
return void cb({ error: "Error while creating the default pad:"+ e}); |
|||
} |
|||
store.userObject.add(id); |
|||
onSync(cb); |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
/** |
|||
* Merge the anonymous drive into the user drive at registration |
|||
* data |
|||
* - anonHash |
|||
*/ |
|||
Store.migrateAnonDrive = function (data, cb) { |
|||
require(['/common/mergeDrive.js'], function (Merge) { |
|||
var hash = data.anonHash; |
|||
Merge.anonDriveIntoUser(store, hash, cb); |
|||
}); |
|||
}; |
|||
|
|||
var getAttributeObject = function (attr) { |
|||
if (typeof attr === "string") { |
|||
console.error('DEPRECATED: use setAttribute with an array, not a string'); |
|||
return { |
|||
obj: store.proxy.settings, |
|||
key: attr |
|||
}; |
|||
} |
|||
if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); } |
|||
if (attr.length === 0) { return void console.error("Attribute can't be empty"); } |
|||
var obj = store.proxy.settings; |
|||
attr.forEach(function (el, i) { |
|||
if (i === attr.length-1) { return; } |
|||
if (!obj[el]) { |
|||
obj[el] = {}; |
|||
} |
|||
else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); } |
|||
obj = obj[el]; |
|||
}); |
|||
return { |
|||
obj: obj, |
|||
key: attr[attr.length-1] |
|||
}; |
|||
}; |
|||
|
|||
// Set the display name (username) in the proxy
|
|||
Store.setDisplayName = function (value, cb) { |
|||
store.proxy[Constants.displayNameKey] = value; |
|||
onSync(cb); |
|||
}; |
|||
|
|||
// Reset the drive part of the userObject (from settings)
|
|||
Store.resetDrive = function (data, cb) { |
|||
store.proxy.drive = store.fo.getStructure(); |
|||
onSync(cb); |
|||
}; |
|||
|
|||
/** |
|||
* Settings & pad attributes |
|||
* data |
|||
* - href (String) |
|||
* - attr (Array) |
|||
* - value (String) |
|||
*/ |
|||
Store.setPadAttribute = function (data, cb) { |
|||
store.userObject.setPadAttribute(data.href, data.attr, data.value, function () { |
|||
onSync(cb); |
|||
}); |
|||
}; |
|||
Store.getPadAttribute = function (data, cb) { |
|||
store.userObject.getPadAttribute(data.href, data.attr, function (err, val) { |
|||
if (err) { return void cb({error: err}); } |
|||
cb(val); |
|||
}); |
|||
}; |
|||
Store.setAttribute = function (data, cb) { |
|||
try { |
|||
var object = getAttributeObject(data.attr); |
|||
object.obj[object.key] = data.value; |
|||
} catch (e) { return void cb({error: e}); } |
|||
onSync(cb); |
|||
}; |
|||
Store.getAttribute = function (data, cb) { |
|||
var object; |
|||
try { |
|||
object = getAttributeObject(data.attr); |
|||
} catch (e) { return void cb({error: e}); } |
|||
cb(object.obj[object.key]); |
|||
}; |
|||
|
|||
// Tags
|
|||
Store.listAllTags = function (data, cb) { |
|||
var all = []; |
|||
var files = Util.find(store.proxy, ['drive', 'filesData']); |
|||
|
|||
if (typeof(files) !== 'object') { return cb({error: 'invalid_drive'}); } |
|||
Object.keys(files).forEach(function (k) { |
|||
var file = files[k]; |
|||
if (!Array.isArray(file.tags)) { return; } |
|||
file.tags.forEach(function (tag) { |
|||
if (all.indexOf(tag) === -1) { all.push(tag); } |
|||
}); |
|||
}); |
|||
cb(all); |
|||
}; |
|||
|
|||
// Templates
|
|||
Store.getTemplates = function (data, cb) { |
|||
var templateFiles = store.userObject.getFiles(['template']); |
|||
var res = []; |
|||
templateFiles.forEach(function (f) { |
|||
var data = store.userObject.getFileData(f); |
|||
res.push(JSON.parse(JSON.stringify(data))); |
|||
}); |
|||
cb(res); |
|||
}; |
|||
|
|||
// Pads
|
|||
Store.moveToTrash = function (data, cb) { |
|||
var href = Hash.getRelativeHref(data.href); |
|||
store.userObject.forget(href); |
|||
onSync(cb); |
|||
}; |
|||
Store.setPadTitle = function (data, cb) { |
|||
var title = data.title; |
|||
var href = data.href; |
|||
var p = Hash.parsePadUrl(href); |
|||
var h = p.hashData; |
|||
|
|||
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; |
|||
var isStronger; |
|||
|
|||
// If we don't find the new channel in our existing pads, we'll have to add the pads
|
|||
// to filesData
|
|||
var contains; |
|||
|
|||
// Update all pads that use the same channel but with a weaker hash
|
|||
// Edit > Edit (present) > View > View (present)
|
|||
for (var id in allPads) { |
|||
var pad = allPads[id]; |
|||
if (!pad.href) { continue; } |
|||
|
|||
var p2 = Hash.parsePadUrl(pad.href); |
|||
var h2 = p2.hashData; |
|||
|
|||
// Different types, proceed to the next one
|
|||
// No hash data: corrupted pad?
|
|||
if (p.type !== p2.type || !h2) { continue; } |
|||
|
|||
var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); |
|||
|
|||
// If the hash is different but represents the same channel, check if weaker or stronger
|
|||
if (!shouldUpdate && |
|||
h.version === 1 && h2.version === 1 && |
|||
h.channel === h2.channel) { |
|||
// We had view & now we have edit, update
|
|||
if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } |
|||
// Same mode and we had present URL, update
|
|||
else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; } |
|||
// If we're here it means we have a weaker URL:
|
|||
// update the date but keep the existing hash
|
|||
else { |
|||
pad.atime = +new Date(); |
|||
contains = true; |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
if (shouldUpdate) { |
|||
contains = true; |
|||
pad.atime = +new Date(); |
|||
pad.title = title; |
|||
|
|||
// If the href is different, it means we have a stronger one
|
|||
if (href !== pad.href) { isStronger = true; } |
|||
pad.href = href; |
|||
} |
|||
} |
|||
|
|||
if (isStronger) { |
|||
// If we have a stronger url, remove the possible weaker from the trash.
|
|||
// If all of the weaker ones were in the trash, add the stronger to ROOT
|
|||
store.userObject.restoreHref(href); |
|||
} |
|||
|
|||
// Add the pad if it does not exist in our drive
|
|||
if (!contains) { |
|||
Store.addPad({ |
|||
href: href, |
|||
title: title, |
|||
path: data.path || (store.data && store.data.initialPath) |
|||
}, cb); |
|||
return; |
|||
} |
|||
onSync(cb); |
|||
}; |
|||
|
|||
// Filepicker app
|
|||
Store.getSecureFilesList = function (query, cb) { |
|||
var list = {}; |
|||
var hashes = []; |
|||
var types = query.types; |
|||
var where = query.where; |
|||
var filter = query.filter || {}; |
|||
var isFiltered = function (type, data) { |
|||
var filtered; |
|||
var fType = filter.fileType || []; |
|||
if (type === 'file' && fType.length) { |
|||
if (!data.fileType) { return true; } |
|||
filtered = !fType.some(function (t) { |
|||
return data.fileType.indexOf(t) === 0; |
|||
}); |
|||
} |
|||
return filtered; |
|||
}; |
|||
store.userObject.getFiles(where).forEach(function (id) { |
|||
var data = store.userObject.getFileData(id); |
|||
var parsed = Hash.parsePadUrl(data.href); |
|||
if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && |
|||
hashes.indexOf(parsed.hash) === -1 && |
|||
!isFiltered(parsed.type, data)) { |
|||
hashes.push(parsed.hash); |
|||
list[id] = data; |
|||
} |
|||
}); |
|||
cb(list); |
|||
}; |
|||
|
|||
// Messaging (manage friends from the userlist)
|
|||
var getMessagingCfg = function () { |
|||
return { |
|||
proxy: store.proxy, |
|||
realtime: store.realtime, |
|||
network: store.network, |
|||
updateMetadata: function () { |
|||
postMessage("UPDATE_METADATA"); |
|||
}, |
|||
pinPads: Store.pinPads, |
|||
friendComplete: function (data, cb) { |
|||
postMessage("Q_FRIEND_COMPLETE", data, cb); |
|||
}, |
|||
friendRequest: function (data) { |
|||
postMessage("EV_FRIEND_REQUEST", data); |
|||
}, |
|||
}; |
|||
}; |
|||
Store.inviteFromUserlist = function (data, cb) { |
|||
var messagingCfg = getMessagingCfg(); |
|||
Messaging.inviteFromUserlist(messagingCfg, data, cb); |
|||
}; |
|||
|
|||
// Messenger
|
|||
|
|||
// Get hashes for the share button
|
|||
Store.getStrongerHash = function (data, cb) { |
|||
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; |
|||
|
|||
// If we have a stronger version in drive, add it and add a redirect button
|
|||
var stronger = Hash.findStronger(data.href, allPads); |
|||
if (stronger) { |
|||
var parsed2 = Hash.parsePadUrl(stronger); |
|||
return void cb(parsed2.hash); |
|||
} |
|||
cb(); |
|||
}; |
|||
|
|||
Store.messenger = { |
|||
getFriendList: function (data, cb) { |
|||
store.messenger.getFriendList(function (e, keys) { |
|||
cb({ |
|||
error: e, |
|||
data: keys, |
|||
}); |
|||
}); |
|||
}, |
|||
getMyInfo: function (data, cb) { |
|||
store.messenger.getMyInfo(function (e, info) { |
|||
cb({ |
|||
error: e, |
|||
data: info, |
|||
}); |
|||
}); |
|||
}, |
|||
getFriendInfo: function (data, cb) { |
|||
store.messenger.getFriendInfo(data, function (e, info) { |
|||
cb({ |
|||
error: e, |
|||
data: info, |
|||
}); |
|||
}); |
|||
}, |
|||
removeFriend: function (data, cb) { |
|||
store.messenger.removeFriend(data, function (e, info) { |
|||
cb({ |
|||
error: e, |
|||
data: info, |
|||
}); |
|||
}); |
|||
}, |
|||
openFriendChannel: function (data, cb) { |
|||
store.messenger.openFriendChannel(data, function (e) { |
|||
cb({ error: e, }); |
|||
}); |
|||
}, |
|||
getFriendStatus: function (data, cb) { |
|||
store.messenger.getStatus(data, function (e, online) { |
|||
cb({ |
|||
error: e, |
|||
data: online, |
|||
}); |
|||
}); |
|||
}, |
|||
getMoreHistory: function (data, cb) { |
|||
store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) { |
|||
cb({ |
|||
error: e, |
|||
data: history, |
|||
}); |
|||
}); |
|||
}, |
|||
sendMessage: function (data, cb) { |
|||
store.messenger.sendMessage(data.curvePublic, data.content, function (e) { |
|||
cb({ |
|||
error: e, |
|||
}); |
|||
}); |
|||
}, |
|||
setChannelHead: function (data, cb) { |
|||
store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) { |
|||
cb({ |
|||
error: e |
|||
}); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
var onReady = function (returned, cb) { |
|||
var proxy = store.proxy; |
|||
var userObject = store.userObject = UserObject.init(proxy.drive, { |
|||
pinPads: Store.pinPads, |
|||
loggedIn: store.loggedIn |
|||
}); |
|||
var todo = function () { |
|||
userObject.fixFiles(); |
|||
|
|||
Migrate(proxy); |
|||
|
|||
var requestLogin = function () { |
|||
postMessage("REQUEST_LOGIN"); |
|||
}; |
|||
|
|||
if (store.loggedIn) { |
|||
/* This isn't truly secure, since anyone who can read the user's object can |
|||
set their local loginToken to match that in the object. However, it exposes |
|||
a UI that will work most of the time. */ |
|||
|
|||
// every user object should have a persistent, random number
|
|||
if (typeof(proxy.loginToken) !== 'number') { |
|||
proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); |
|||
} |
|||
returned[Constants.tokenKey] = proxy[Constants.tokenKey]; |
|||
|
|||
if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) { |
|||
// the local number doesn't match that in
|
|||
// the user object, request that they reauthenticate.
|
|||
return void requestLogin(); |
|||
} |
|||
} |
|||
|
|||
if (!proxy.settings || !proxy.settings.general || |
|||
typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') { |
|||
proxy.settings = proxy.settings || {}; |
|||
proxy.settings.general = proxy.settings.general || {}; |
|||
proxy.settings.general.allowUserFeedback = true; |
|||
} |
|||
returned.feedback = proxy.settings.general.allowUserFeedback; |
|||
|
|||
if (typeof(cb) === 'function') { cb(returned); } |
|||
|
|||
if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { |
|||
// even anonymous users should have a persistent, unique-ish id
|
|||
console.log('generating a persistent identifier'); |
|||
proxy.uid = Hash.createChannelId(); |
|||
} |
|||
|
|||
// if the user is logged in, but does not have signing keys...
|
|||
if (store.loggedIn && (!Store.hasSigningKeys() || |
|||
!Store.hasCurveKeys())) { |
|||
return void requestLogin(); |
|||
} |
|||
|
|||
proxy.on('change', [Constants.displayNameKey], function (o, n) { |
|||
if (typeof(n) !== "string") { return; } |
|||
postMessage("UPDATE_METADATA"); |
|||
}); |
|||
proxy.on('change', ['profile'], function () { |
|||
// Trigger userlist update when the avatar has changed
|
|||
postMessage("UPDATE_METADATA"); |
|||
}); |
|||
proxy.on('change', ['friends'], function () { |
|||
// Trigger userlist update when the friendlist has changed
|
|||
postMessage("UPDATE_METADATA"); |
|||
}); |
|||
proxy.on('change', ['settings'], function () { |
|||
postMessage("UPDATE_METADATA"); |
|||
}); |
|||
proxy.on('change', [Constants.tokenKey], function () { |
|||
postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); |
|||
}); |
|||
}; |
|||
userObject.migrate(todo); |
|||
}; |
|||
|
|||
var connect = function (data, cb) { |
|||
var hash = data.userHash || data.anonHash || Hash.createRandomHash(); |
|||
storeHash = hash; |
|||
if (!hash) { |
|||
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); |
|||
} |
|||
var secret = Hash.getSecrets('drive', hash); |
|||
var listmapConfig = { |
|||
data: {}, |
|||
websocketURL: NetConfig.getWebsocketURL(), |
|||
channel: secret.channel, |
|||
readOnly: false, |
|||
validateKey: secret.keys.validateKey || undefined, |
|||
crypto: Crypto.createEncryptor(secret.keys), |
|||
userName: 'fs', |
|||
logLevel: 1, |
|||
ChainPad: ChainPad, |
|||
classic: true, |
|||
}; |
|||
var rt = Listmap.create(listmapConfig); |
|||
store.proxy = rt.proxy; |
|||
store.loggedIn = typeof(data.userHash) !== "undefined"; |
|||
|
|||
var returned = {}; |
|||
rt.proxy.on('create', function (info) { |
|||
store.realtime = info.realtime; |
|||
store.network = info.network; |
|||
if (!data.userHash) { |
|||
returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys); |
|||
} |
|||
}).on('ready', function () { |
|||
if (store.userObject) { return; } // the store is already ready, it is a reconnection
|
|||
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } |
|||
var drive = rt.proxy.drive; |
|||
// Creating a new anon drive: import anon pads from localStorage
|
|||
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey])) |
|||
&& !drive['filesData']) { |
|||
drive[Constants.oldStorageKey] = []; |
|||
} |
|||
// Drive already exist: return the existing drive, don't load data from legacy store
|
|||
onReady(returned, cb); |
|||
}) |
|||
.on('change', ['drive', 'migrate'], function () { |
|||
var path = arguments[2]; |
|||
var value = arguments[1]; |
|||
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { |
|||
rt.network.disconnect(); |
|||
rt.realtime.abort(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* Data: |
|||
* - userHash or anonHash |
|||
* Todo in cb |
|||
* - LocalStore.setFSHash if needed |
|||
* - sessionStorage.User_Hash |
|||
* - stuff with tokenKey |
|||
* Event to outer |
|||
* - requestLogin |
|||
*/ |
|||
var initialized = false; |
|||
Store.init = function (data, callback) { |
|||
if (initialized) { |
|||
return void callback({ |
|||
error: 'ALREADY_INIT' |
|||
}); |
|||
} |
|||
initialized = true; |
|||
postMessage = function (cmd, d, cb) { |
|||
setTimeout(function () { |
|||
data.query(cmd, d, cb); // TODO temporary, will be replaced by webworker channel
|
|||
}); |
|||
}; |
|||
|
|||
store.data = data; |
|||
connect(data, function (ret) { |
|||
if (Object.keys(store.proxy).length === 1) { |
|||
Feedback.send("FIRST_APP_USE", true); |
|||
} |
|||
|
|||
callback(ret); |
|||
|
|||
var messagingCfg = getMessagingCfg(); |
|||
Messaging.addDirectMessageHandler(messagingCfg); |
|||
|
|||
if (data.messenger) { |
|||
var messenger = store.messenger = Messenger.messenger(store); // TODO
|
|||
messenger.on('message', function (message) { |
|||
postMessage('CONTACTS_MESSAGE', message); |
|||
}); |
|||
messenger.on('join', function (curvePublic, channel) { |
|||
postMessage('CONTACTS_JOIN', { |
|||
curvePublic: curvePublic, |
|||
channel: channel, |
|||
}); |
|||
}); |
|||
messenger.on('leave', function (curvePublic, channel) { |
|||
postMessage('CONTACTS_LEAVE', { |
|||
curvePublic: curvePublic, |
|||
channel: channel, |
|||
}); |
|||
}); |
|||
messenger.on('update', function (info, curvePublic) { |
|||
postMessage('CONTACTS_UPDATE', { |
|||
curvePublic: curvePublic, |
|||
info: info, |
|||
}); |
|||
}); |
|||
messenger.on('friend', function (curvePublic) { |
|||
postMessage('CONTACTS_FRIEND', { |
|||
curvePublic: curvePublic, |
|||
}); |
|||
}); |
|||
messenger.on('unfriend', function (curvePublic) { |
|||
postMessage('CONTACTS_UNFRIEND', { |
|||
curvePublic: curvePublic, |
|||
}); |
|||
}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
Store.disconnect = function () { |
|||
if (!store.network) { return; } |
|||
store.network.disconnect(); |
|||
}; |
|||
return Store; |
|||
}); |
|||
@ -0,0 +1,159 @@ |
|||
define([ |
|||
'/common/outer/async-store.js' |
|||
], function (Store) { |
|||
var Rpc = {}; |
|||
|
|||
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; |
|||
} |
|||
// 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 '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_PINNED_USAGE': { |
|||
Store.getPinnedUsage(data, cb); break; |
|||
} |
|||
// 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; |
|||
} |
|||
// 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_STRONGER_HASH': { |
|||
Store.getStrongerHash(data, cb); break; |
|||
} |
|||
// Messaging
|
|||
case 'INVITE_FROM_USERLIST': { |
|||
Store.inviteFromUserlist(data, cb); break; |
|||
} |
|||
// 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; |
|||
} |
|||
default: { |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
}; |
|||
|
|||
return Rpc; |
|||
}); |
|||
|
|||
@ -0,0 +1,94 @@ |
|||
define([ |
|||
|
|||
], function () { |
|||
var Wire = {}; |
|||
|
|||
/* MISSION: write a generic RPC framework |
|||
|
|||
Requirements |
|||
|
|||
* some transmission methods can be interrupted |
|||
* handle disconnects and reconnects |
|||
* handle callbacks |
|||
* configurable timeout |
|||
* Service should expose 'addClient' method |
|||
* and handle broadcast |
|||
|
|||
|
|||
* |
|||
|
|||
*/ |
|||
|
|||
var uid = function () { |
|||
return Number(Math.floor(Math.random () * |
|||
Number.MAX_SAFE_INTEGER)).toString(32); |
|||
}; |
|||
|
|||
/* |
|||
opt = { |
|||
send: function () { |
|||
|
|||
}, |
|||
receive: function () { |
|||
|
|||
}, |
|||
constructor: function (cb) { |
|||
cb(void 0 , { |
|||
send: function (content, cb) { |
|||
|
|||
}, |
|||
receive: function () { |
|||
|
|||
} |
|||
}); |
|||
}, |
|||
}; |
|||
*/ |
|||
|
|||
Wire.create = function (opt, cb) { |
|||
var ctx = {}; |
|||
var pending = ctx.pending = {}; |
|||
ctx.connected = false; |
|||
|
|||
var rpc = {}; |
|||
|
|||
opt.constructor(function (e, service) { |
|||
if (e) { return setTimeout(function () { cb(e); }); } |
|||
|
|||
rpc.send = function (type, data, cb) { |
|||
var txid = uid(); |
|||
if (typeof(cb) !== 'function') { |
|||
throw new Error('expected callback'); |
|||
} |
|||
|
|||
ctx.pending[txid] = function (err, response) { |
|||
cb(err, response); |
|||
}; |
|||
|
|||
service.send(JSON.stringify({ |
|||
txid: txid, |
|||
message: { |
|||
command: type, |
|||
content: data, |
|||
}, |
|||
})); |
|||
}; |
|||
|
|||
service.receive(function (raw) { |
|||
try { |
|||
var data = JSON.parse(raw); |
|||
var txid = data.txid; |
|||
if (!txid) { throw new Error('NO_TXID'); } |
|||
var cb = pending[txid]; |
|||
if (data.error) { return void cb(data.error); } |
|||
cb(void 0, data.content); |
|||
} catch (e) { console.error("UNHANDLED_MESSAGE", raw); } |
|||
}); |
|||
|
|||
cb(void 0, rpc); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
return Wire; |
|||
}); |
|||
Write
Preview
Loading…
Cancel
Save