Initial work on the 'pad will expire' logic

pull/1/head
Caleb James DeLisle 7 years ago
parent 7544bb1ced
commit c702a23c77

@ -42,42 +42,54 @@
@colortheme_pad-bg: #1c4fa0;
@colortheme_pad-color: #fff;
@colortheme_pad-toolbar-bg: #c1e7ff;
@colortheme_pad-warn: #F83A3A;
@colortheme_slide-bg: #e57614;
@colortheme_slide-color: #fff;
@colortheme_slide-warn: #7bccd1;
@colortheme_code-bg: #ffae00;
@colortheme_code-color: #000;
@colortheme_code-warn: #005bef;
@colortheme_poll-bg: #006304;
@colortheme_poll-color: #fff;
@colortheme_poll-help-bg: #bbffbb;
@colortheme_poll-th-bg: #005bef;
@colortheme_poll-th-fg: #fff;
@colortheme_poll-warn: #cd2532;
@colortheme_whiteboard-bg: #800080;
@colortheme_whiteboard-color: #fff;
@colortheme_whiteboard-warn: #ffae00;
@colortheme_drive-bg: #0087ff;
@colortheme_drive-color: #fff;
@colortheme_drive-warn: #cd2532;
@colortheme_file-bg: #cd2532;
@colortheme_file-color: #fff;
@colortheme_file-warn: #ffae00;
@colortheme_friends-bg: #607b8d;
@colortheme_friends-color: #fff;
@colortheme_friends-warn: #cd2532;
@colortheme_default-bg: #ddd;
@colortheme_default-color: #000;
@colortheme_default-warn: #cd2532;
@colortheme_settings-bg: #0087ff;
@colortheme_settings-color: #fff;
@colortheme_settings-warn: #cd2532;
@colortheme_profile-bg: #0087ff;
@colortheme_profile-color: #fff;
@colortheme_profile-warn: #cd2532;
@colortheme_todo-bg: #7bccd1;
@colortheme_todo-color: #000;
@colortheme_todo-warn: #cd2532;
// Sidebar layout (profile / settings)
@colortheme_sidebar-active: #fff;

@ -7,6 +7,7 @@
@import (once) "./icon-colors.less";
@import (once) "./tools.less";
@_cp-toolbar-color-warn: black;
.toolbar_main () {
@ -224,6 +225,7 @@
}
.cp-toolbar-limit {
text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color;
color: @_cp-toolbar-color-warn;
}
.cp-toolbar-leftside, .cp-toolbar-rightside {
background-color: lighten(@bgcolor, 8%);
@ -238,6 +240,19 @@
width: 100%;
}
}
.cp-pad-not-pinned {
padding-left: 20px;
font-size: @colortheme_app-font-size;
color: @_cp-toolbar-color-warn;
a {
font-size: @colortheme_app-font-size;
font-weight: bold;
color: @_cp-toolbar-color-warn;
&:hover {
text-decoration: underline;
}
}
}
.cp-toolbar-title-hoverable:hover {
.cp-toolbar-title-editable, .cp-toolbar-title-edit {
cursor: text;
@ -383,7 +398,6 @@
vertical-align: middle;
line-height: @toolbar_top-height;
span {
color: red;
cursor: pointer;
margin: auto;
font-size: 20px;

@ -31,6 +31,7 @@ define(function () {
out.typeError = "This pad is not compatible with the selected application";
out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire in 3 months, <a href="{0}" target="blank">login</a> or <a href="{1}" target="blank">register</a> to preserve it.';
out.loading = "Loading...";
out.error = "Error";

@ -0,0 +1,74 @@
/* jshint esversion: 6, node: true */
const Fs = require('fs');
const Semaphore = require('saferphore');
const nThen = require('nthen');
const sema = Semaphore.create(20);
let dirList;
const fileList = [];
const pinned = {};
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
//jshint -W086
// fallthrough
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
break;
}
case 'UNPIN': {
l[1].forEach((x) => { delete pins[x]; });
break;
}
default: throw new Error(JSON.stringify(l) + ' ' + fileName);
}
});
return Object.keys(pins);
};
module.exports.load = function (cb) {
nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
fileList.splice(0, fileList.length);
dirList.forEach((f) => {
sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
})));
});
});
}).nThen((waitFor) => {
fileList.forEach((f) => {
sema.take((returnAfter) => {
Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; }
const hashes = hashesFromPinFile(content.toString('utf8'), f);
hashes.forEach((x) => {
(pinned[x] = pinned[x] || {})[f.replace(/.*\/([^/]*).ndjson$/, (x, y)=>y)] = 1;
});
})));
});
});
}).nThen(() => {
cb(pinned);
});
};
if (!module.parent) {
module.exports.load(function (data) {
Object.keys(data).forEach(function (x) {
console.log(x + ' ' + JSON.stringify(data[x]));
});
});
}

118
rpc.js

@ -10,6 +10,7 @@ var Fs = require("fs");
var Path = require("path");
var Https = require("https");
const Package = require('./package.json');
const Pinned = require('./pinned');
var RPC = module.exports;
@ -212,7 +213,6 @@ var checkSignature = function (signedMsg, signature, publicKey) {
};
var loadUserPins = function (Env, publicKey, cb) {
var pinStore = Env.pinStore;
var session = beginSession(Env.Sessions, publicKey);
if (session.channels) {
@ -230,7 +230,7 @@ var loadUserPins = function (Env, publicKey, cb) {
pins[channel] = false;
};
pinStore.getMessages(publicKey, function (msg) {
Env.pinStore.getMessages(publicKey, function (msg) {
// handle messages...
var parsed;
try {
@ -325,9 +325,8 @@ var getFileSize = function (Env, channel, cb) {
};
var getMultipleFileSize = function (Env, channels, cb) {
var msgStore = Env.msgStore;
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(msgStore.getChannelSize) !== 'function') {
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
}
@ -499,6 +498,54 @@ var sumChannelSizes = function (sizes) {
.reduce(function (a, b) { return a + b; }, 0);
};
// inform that the
var loadChannelPins = function (Env) {
Pinned.load(function (data) {
Env.pinnedPads = data;
Env.evPinnedPadsReady.fire();
});
};
var addPinned = function (
Env,
publicKey /*:string*/,
channelList /*Array<string>*/,
cb /*:()=>void*/)
{
Env.evPinnedPadsReady.reg(() => {
channelList.forEach((c) => {
const x = Env.pinnedPads[c];
if (!x) { return; }
delete x[publicKey];
});
cb();
});
};
var removePinned = function (
Env,
publicKey /*:string*/,
channelList /*Array<string>*/,
cb /*:()=>void*/)
{
Env.evPinnedPadsReady.reg(() => {
channelList.forEach((c) => {
const x = Env.pinnedPads[c];
if (!x) { return; }
delete x[publicKey];
});
cb();
});
};
var isChannelPinned = function (Env, channel, cb) {
Env.evPinnedPadsReady.reg(() => {
if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) {
cb(true);
} else {
delete Env.pinnedPads[channel];
cb(false);
}
});
};
var pinChannel = function (Env, publicKey, channels, cb) {
if (!channels && channels.filter) {
return void cb('INVALID_PIN_LIST');
@ -534,6 +581,7 @@ var pinChannel = function (Env, publicKey, channels, cb) {
toStore.forEach(function (channel) {
session.channels[channel] = true;
});
addPinned(Env, publicKey, toStore, () => {});
getHash(Env, publicKey, cb);
});
});
@ -542,7 +590,6 @@ var pinChannel = function (Env, publicKey, channels, cb) {
};
var unpinChannel = function (Env, publicKey, channels, cb) {
var pinStore = Env.pinStore;
if (!channels && channels.filter) {
// expected array
return void cb('INVALID_PIN_LIST');
@ -560,13 +607,13 @@ var unpinChannel = function (Env, publicKey, channels, cb) {
return void getHash(Env, publicKey, cb);
}
pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]),
Env.pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]),
function (e) {
if (e) { return void cb(e); }
toStore.forEach(function (channel) {
delete session.channels[channel];
});
removePinned(Env, publicKey, toStore, () => {});
getHash(Env, publicKey, cb);
});
});
@ -574,7 +621,6 @@ var unpinChannel = function (Env, publicKey, channels, cb) {
var resetUserPins = function (Env, publicKey, channelList, cb) {
if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); }
var pinStore = Env.pinStore;
var session = beginSession(Env.Sessions, publicKey);
if (!channelList.length) {
@ -605,13 +651,18 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
They will not be able to pin additional pads until they upgrade
or delete enough files to go back under their limit. */
if (pinSize > limit[0] && session.hasPinned) { return void(cb('E_OVER_LIMIT')); }
pinStore.message(publicKey, JSON.stringify(['RESET', channelList]),
Env.pinStore.message(publicKey, JSON.stringify(['RESET', channelList]),
function (e) {
if (e) { return void cb(e); }
channelList.forEach(function (channel) {
pins[channel] = true;
});
var oldChannels = Object.keys(session.channels);
removePinned(Env, publicKey, oldChannels, () => {
addPinned(Env, publicKey, channelList, ()=>{});
});
// update in-memory cache IFF the reset was allowed.
session.channels = pins;
getHash(Env, publicKey, function (e, hash) {
@ -879,6 +930,7 @@ var isUnauthenticatedCall = function (call) {
return [
'GET_FILE_SIZE',
'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED',
].indexOf(call) !== -1;
};
@ -894,10 +946,31 @@ var isAuthenticatedCall = function (call) {
'GET_LIMIT',
'UPLOAD_COMPLETE',
'UPLOAD_CANCEL',
'EXPIRE_SESSION',
'EXPIRE_SESSION'
].indexOf(call) !== -1;
};
const mkEvent = function (once) {
var handlers = [];
var fired = false;
return {
reg: function (cb) {
if (once && fired) { return void setTimeout(cb); }
handlers.push(cb);
},
unreg: function (cb) {
if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); }
handlers.splice(handlers.indexOf(cb), 1);
},
fire: function () {
if (once && fired) { return; }
fired = true;
var args = Array.prototype.slice.call(arguments);
handlers.forEach(function (h) { h.apply(null, args); });
}
};
};
/*::const ConfigType = require('./config.example.js');*/
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
// load pin-store...
@ -909,14 +982,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return typeof(config[key]) === 'string'? config[key]: def;
};
var Env = {};
Env.defaultStorageLimit = config.defaultStorageLimit;
Env.maxUploadSize = config.maxUploadSize || (20 * 1024 * 1024);
var Sessions = Env.Sessions = {};
var Env = {
defaultStorageLimit: config.defaultStorageLimit,
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
Sessions: {},
paths: {},
msgStore: (undefined /*:any*/),
pinStore: (undefined /*:any*/),
pinnedPads: {},
evPinnedPadsReady: mkEvent(true)
};
var paths = Env.paths = {};
var Sessions = Env.Sessions;
var paths = Env.paths;
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
@ -943,6 +1021,10 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
}
respond(e, [null, dict, null]);
});
case 'IS_CHANNEL_PINNED':
return void isChannelPinned(Env, msg[1], function (isPinned) {
respond(null, [null, isPinned, null]);
});
default:
console.error("unsupported!");
return respond('UNSUPPORTED_RPC_CALL', msg);
@ -1174,6 +1256,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
updateLimitDaily();
setInterval(updateLimitDaily, 24*3600*1000);
loadChannelPins(Env);
Store.create({
filePath: pinPath,
}, function (s) {

@ -102,6 +102,7 @@ app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
app.use("/customize.dist", Express.static(__dirname + '/customize.dist'));
app.use(/^\/[^\/]*$/, Express.static('customize'));
app.use(/^\/[^\/]*$/, Express.static('customize.dist'));

@ -5,6 +5,8 @@
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@_cp-toolbar-color-warn: @colortheme_code-warn;
.toolbar_main();
.fileupload_main();
.alertify_main();

@ -461,7 +461,16 @@ define([
getHeadingText: function () { return titleRecommender(); }
}, onLocal);
var configTb = {
displayed: ['userlist', 'title', 'useradmin', 'spinner', 'newpad', 'share', 'limit'],
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: title.getTitleConfig(),
metadataMgr: cpNfInner.metadataMgr,
readOnly: readOnly,

@ -719,6 +719,26 @@ define([
return $titleContainer;
};
var createUnpinnedWarning = function (toolbar, config) {
if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData();
var o = pd.origin;
var hashes = pd.availableHashes;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) { return; }
var msg = $('<span>', {
'class': 'cp-pad-not-pinned',
}).append(
Messages._getKey('padNotPinned', [o + '/login', o + '/register'])
);
$('.cp-toolbar-title').append(msg);
console.log("This pad is not pinned");
});
};
var createPageTitle = function (toolbar, config) {
if (config.title || !config.pageTitle) { return; }
var $titleContainer = $('<span>', {
@ -1087,6 +1107,7 @@ define([
tb['upgrade'] = $.noop;
tb['newpad'] = createNewPad;
tb['useradmin'] = createUserAdmin;
tb['unpinnedWarning'] = createUnpinnedWarning;
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }

@ -8,6 +8,7 @@
@import (once) '../../customize/src/less2/include/avatar.less';
@_cp-toolbar-color-warn: @colortheme_friends-warn;;
.toolbar_main();
.fileupload_main();

@ -8,6 +8,8 @@
@import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/tokenfield.less";
@_cp-toolbar-color-warn: @colortheme_drive-warn;
.toolbar_main();
.fileupload_main();
.alertify_main();

@ -5,6 +5,8 @@
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@_cp-toolbar-color-warn: @colortheme_file-warn;
.toolbar_main();
.fileupload_main();
.alertify_main();

@ -6,6 +6,8 @@
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@_cp-toolbar-color-warn: @colortheme_pad-warn;
.toolbar_main();
.alertify_main();

@ -13,6 +13,8 @@
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/avatar.less';
@_cp-toolbar-color-warn: @colortheme_poll-warn;
.toolbar_main();
.fileupload_main();
.alertify_main();

@ -4,7 +4,9 @@
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
@import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/limit-bar.less";
@_cp-toolbar-color-warn: @colortheme_settings-warn;
.toolbar_main();
.alertify_main();

@ -6,6 +6,8 @@
@import (once) "../../customize/src/less2/include/mediatag.less";
@import (once) '../../customize/src/less2/include/tokenfield.less';
@_cp-toolbar-color-warn: @colortheme_slide-warn;
.mediatag_base();
.toolbar_main();
.fileupload_main();

@ -6,6 +6,8 @@
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@_cp-toolbar-color-warn: @colortheme_whiteboard-warn;
.toolbar_main();
.fileupload_main();
.alertify_main();

@ -393,7 +393,16 @@ define([
Title = common.createTitle({});
var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'share', 'userlist', 'newpad', 'limit'],
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: Title.getTitleConfig(),
metadataMgr: metadataMgr,
readOnly: readOnly,

Loading…
Cancel
Save