solid-ui
Version:
UI library for writing Solid read-write-web applications
1,625 lines (1,351 loc) • 57.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
/**
* signin.js
*
* Signing in, signing up, profile and preferences reloading
* Type index management
*
* Many functions in this module take a context object, add to it, and return a promise of it.
*/
/* global localStorage confirm alert */
// const Solid = require('solid-client')
var SolidTls = require('solid-auth-tls');
var $rdf = require('rdflib'); // const error = require('./widgets/error')
var widgets = require('./widgets/index'); // const utils = require('./utils')
var solidAuthClient = require('solid-auth-client');
var UI = {
authn: require('./signin'),
icons: require('./iconBase'),
log: require('./log'),
ns: require('./ns'),
store: require('./store'),
style: require('./style'),
utils: require('./utils'),
widgets: require('./widgets') // 2018-07-31
};
var ns = UI.ns;
var kb = UI.store;
module.exports = {
checkUser: checkUser,
// Async
currentUser: currentUser,
// Sync
defaultTestUser: defaultTestUser,
// Sync
filterAvailablePanes: filterAvailablePanes,
// Async
findAppInstances: findAppInstances,
findOriginOwner: findOriginOwner,
getUserRoles: getUserRoles,
// Async
loadTypeIndexes: loadTypeIndexes,
logIn: logIn,
logInLoadProfile: logInLoadProfile,
logInLoadPreferences: logInLoadPreferences,
loginStatusBox: loginStatusBox,
newAppInstance: newAppInstance,
offlineTestID: offlineTestID,
registerInTypeIndex: registerInTypeIndex,
registrationControl: registrationControl,
registrationList: registrationList,
selectWorkspace: selectWorkspace,
setACLUserPublic: setACLUserPublic,
saveUser: saveUser,
solidAuthClient: solidAuthClient
}; // const userCheckSite = 'https://databox.me/'
// Look for and load the User who has control over it
function findOriginOwner(doc, callback) {
var uri = doc.uri || doc;
var i = uri.indexOf('://');
if (i < 0) return false;
var j = uri.indexOf('/', i + 3);
if (j < 0) return false;
var origin = uri.slice(0, j + 1); // @@ TBC
return origin;
} // Promises versions
//
// These pass a context object which hold various RDF symbols
// as they become available
//
// me RDF symbol for the users' webid
// publicProfile The user's public profile, iff loaded
// preferencesFile The user's personal preferences file, iff loaded
// index.public The user's public type index file
// index.private The user's private type index file
// not RDF symbols:
// noun A string in english for the type of thing -- like "address book"
// instance An array of nodes which are existing instances
// containers An array of nodes of containers of instances
// div A DOM element where UI can be displayed
// statusArea A DOM element (opt) progress stuff can be displayed, or error messages
/**
* @param webId {NamedNode}
* @param context {Object}
*
* @returns {NamedNode|null} Returns the Web ID, after setting it
*/
function saveUser(webId, context) {
var webIdUri, me;
if (webId) {
webIdUri = webId.uri || webId;
var _me = $rdf.namedNode(webIdUri);
if (context) {
context.me = _me;
}
return _me;
}
return me || null;
}
/**
* @returns {NamedNode|null}
*/
function defaultTestUser() {
// Check for offline override
var offlineId = offlineTestID();
if (offlineId) {
return offlineId;
}
return null;
}
/** Checks syncronously whether user is logged in
*
* @returns Named Node or null
*/
function currentUser() {
var str = localStorage['solid-auth-client'];
if (str) {
var da = JSON.parse(str);
if (da.session && da.session.webId) {
// @@ check has not expired
return $rdf.sym(da.session.webId);
}
}
return offlineTestID(); // null unless testing
// JSON.parse(localStorage['solid-auth-client']).session.webId
}
/**
* Resolves with the logged in user's Web ID
*
* @param context
*
* @returns {Promise<context>}
*/
function logIn(context) {
var me = defaultTestUser(); // me is a NamedNode or null
if (me) {
context.me = me;
return Promise.resolve(context);
}
return new Promise(function (resolve) {
checkUser().then(function (webId) {
// Already logged in?
if (webId) {
context.me = $rdf.sym(webId);
console.log('logIn: Already logged in as ' + context.me);
return resolve(context);
}
if (!context.div || !context.dom) {
return resolve(context);
}
var box = loginStatusBox(context.dom, function (webIdUri) {
saveUser(webIdUri, context);
resolve(context); // always pass growing context
});
context.div.appendChild(box);
});
});
}
/**
* Logs the user in and loads their WebID profile document into the store
*
* @private
*
* @param context {Object}
*
* @returns {Promise<Object>} Resolves with the context after login / fetch
*/
function logInLoadProfile(context) {
if (context.publicProfile) {
return Promise.resolve(context);
} // already done
var fetcher = UI.store.fetcher;
var profileDocument;
return new Promise(function (resolve, reject) {
return logIn(context).then(function (context) {
var webID = context.me;
if (!webID) {
return reject(new Error('Could not log in'));
}
profileDocument = webID.doc(); // Load the profile into the knowledge base (fetcher.store)
// withCredentials: Web arch should let us just load by turning off creds helps CORS
// reload: Gets around a specifc old Chrome bug caching/origin/cors
fetcher.load(profileDocument, {
withCredentials: false,
cache: 'reload'
}).then(function (response) {
context.publicProfile = profileDocument;
resolve(context);
})["catch"](function (err) {
var message = 'Logged in but cannot load profile ' + profileDocument + ' : ' + err;
if (context.div && context.dom) {
context.div.appendChild(UI.widgets.errorMessageBlock(context.dom, message));
}
reject(message);
});
})["catch"](function (err) {
reject(new Error("Can't log in: " + err));
});
});
}
/**
* Loads preferences file
* Do this after having done log in and load profile
*
* @private
*
* @param context
*
* @returns {Promise<context>}
*/
function logInLoadPreferences(context) {
if (context.preferencesFile) return Promise.resolve(context); // already done
var kb = UI.store;
var statusArea = context.statusArea || context.div || null;
var progressDisplay;
return new Promise(function (resolve, reject) {
return logInLoadProfile(context).then(function (context) {
var preferencesFile = kb.any(context.me, UI.ns.space('preferencesFile'));
function complain(message) {
message = 'logInLoadPreferences: ' + message;
if (statusArea) {
// statusArea.innerHTML = ''
statusArea.appendChild(UI.widgets.errorMessageBlock(context.dom, message));
}
console.log(message);
reject(new Error(message));
}
/** Are we working cross-origin?
*
* @returns {Boolean} True if we are in a webapp at an origin, and the file origin is different
*/
function differentOrigin() {
return window.location && window.location.origin + '/' !== preferencesFile.site().uri;
}
if (!preferencesFile) {
var message = "Can't find a preferences file pointer in profile " + context.publicProfile;
return reject(new Error(message));
} // //// Load preferences file
return kb.fetcher.load(preferencesFile, {
withCredentials: true
}).then(function () {
if (progressDisplay) {
progressDisplay.parentNode.removeChild(progressDisplay);
}
context.preferencesFile = preferencesFile;
return resolve(context);
})["catch"](function (err) {
// Really important to look at why
var status = err.status;
var message = err.message;
console.log('HTTP status ' + status + ' for pref file ' + preferencesFile);
var m2;
if (status === 401) {
m2 = 'Strange - you are not authenticated (properly logged on) to read preferences file.';
alert(m2);
} else if (status === 403) {
if (differentOrigin()) {
m2 = 'Unauthorized: Assuming prefs file blocked for origin ' + window.location.origin;
context.preferencesFileError = m2;
return resolve(context);
}
m2 = 'You are not authorized to read your preferences file. This may be because you are using an untrusted web app.';
console.warn(m2);
} else if (status === 404) {
if (confirm('You do not currently have a Preferences file. Ok for me to create an empty one? ' + preferencesFile)) {
// @@@ code me ... weird to have a name o fthe file but no file
alert('Sorry; I am not prepared to do this. Please create an empty file at ' + preferencesFile);
return complain(new Error('Sorry; no code yet to create a preferences file at '));
} else {
reject(new Error('User declined to create a preferences file at ' + preferencesFile));
}
} else {
m2 = 'Strange: Error ' + status + ' trying to read your preferences file.' + message;
alert(m2);
}
}); // load prefs file then
})["catch"](function (err) {
// Fail initial login load prefs
reject(new Error('(via loadPrefs) ' + err));
});
});
}
/**
* Resolves with the same context, outputting
* output: index.public, index.private
*
* @see https://github.com/solid/solid/blob/master/proposals/data-discovery.md#discoverability
*
* @param context
* @param context.div - place to put UI
*
* @returns {Promise<context>}
*/
function loadTypeIndexes(_x) {
return _loadTypeIndexes.apply(this, arguments);
}
function _loadTypeIndexes() {
_loadTypeIndexes = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee(context) {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return loadPublicTypeIndex(context);
case 2:
_context.next = 4;
return loadPrivateTypeIndex(context);
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _loadTypeIndexes.apply(this, arguments);
}
function loadPublicTypeIndex(_x2) {
return _loadPublicTypeIndex.apply(this, arguments);
}
function _loadPublicTypeIndex() {
_loadPublicTypeIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee2(context) {
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
return _context2.abrupt("return", loadIndex(context, ns.solid('publicTypeIndex'), true));
case 1:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
return _loadPublicTypeIndex.apply(this, arguments);
}
function loadPrivateTypeIndex(_x3) {
return _loadPrivateTypeIndex.apply(this, arguments);
}
function _loadPrivateTypeIndex() {
_loadPrivateTypeIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee3(context) {
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
return _context3.abrupt("return", loadIndex(context, ns.solid('privateTypeIndex'), false));
case 1:
case "end":
return _context3.stop();
}
}
}, _callee3);
}));
return _loadPrivateTypeIndex.apply(this, arguments);
}
function loadOneTypeIndex(_x4, _x5) {
return _loadOneTypeIndex.apply(this, arguments);
}
function _loadOneTypeIndex() {
_loadOneTypeIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee4(context, isPublic) {
var predicate;
return _regenerator["default"].wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
predicate = isPublic ? ns.solid('publicTypeIndex') : ns.solid('privateTypeIndex');
return _context4.abrupt("return", loadIndex(context, predicate, isPublic));
case 2:
case "end":
return _context4.stop();
}
}
}, _callee4);
}));
return _loadOneTypeIndex.apply(this, arguments);
}
function loadIndex(_x6, _x7, _x8) {
return _loadIndex.apply(this, arguments);
}
/**
* Resolves with the same context, outputting
* @see https://github.com/solid/solid/blob/master/proposals/data-discovery.md#discoverability
*
* @private
*
* @param context {Object}
* @param context.me
* @param context.preferencesFile
* @param context.preferencesFileError - Set if preferences file is blocked at theis origin so don't use it
* @param context.publicProfile
* @param context.index
*
* @returns {Promise}
*/
function _loadIndex() {
_loadIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee5(context, predicate, isPublic) {
var ns, kb, me, ixs;
return _regenerator["default"].wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
ns = UI.ns;
kb = UI.store; // Loading preferences is more than loading profile
_context5.prev = 2;
_context5.next = 5;
return isPublic;
case 5:
if (!_context5.sent) {
_context5.next = 9;
break;
}
logInLoadProfile(context);
_context5.next = 10;
break;
case 9:
logInLoadPreferences(context);
case 10:
_context5.next = 15;
break;
case 12:
_context5.prev = 12;
_context5.t0 = _context5["catch"](2);
UI.widgets.complain(context, 'loadPubicIndex: login and load problem ' + _context5.t0);
case 15:
me = context.me;
context.index = context.index || {};
if (!isPublic) {
_context5.next = 22;
break;
}
ixs = kb.each(me, predicate, undefined, context.publicProfile);
context.index["public"] = ixs;
_context5.next = 31;
break;
case 22:
if (context.preferencesFileError) {
_context5.next = 30;
break;
}
ixs = kb.each(me, ns.solid('privateTypeIndex'), undefined, context.preferencesFile);
context.index["private"] = ixs;
if (!(ixs.length === 0)) {
_context5.next = 28;
break;
}
UI.widgets.complain('Your preference file ' + context.preferencesFile + ' does not point to a private type index.');
return _context5.abrupt("return", context);
case 28:
_context5.next = 31;
break;
case 30:
console.log('We know your preferences file is noty available, so not bothering with private type indexes.');
case 31:
_context5.prev = 31;
_context5.next = 34;
return kb.fetcher.load(ixs);
case 34:
_context5.next = 39;
break;
case 36:
_context5.prev = 36;
_context5.t1 = _context5["catch"](31);
UI.widgets.complain(context, 'loadPubicIndex: loading public type index ' + _context5.t1);
case 39:
return _context5.abrupt("return", context);
case 40:
case "end":
return _context5.stop();
}
}
}, _callee5, null, [[2, 12], [31, 36]]);
}));
return _loadIndex.apply(this, arguments);
}
function ensureTypeIndexes(_x9) {
return _ensureTypeIndexes.apply(this, arguments);
}
/* Load or create ONE type index
* Find one or mke one or fail
* Many reasons for filing including script not having permission etc
*
*/
/**
* Adds it output to the context
* @see https://github.com/solid/solid/blob/master/proposals/data-discovery.md#discoverability
*
* @private
*
* @param context {Object}
* @param context.me
* @param context.preferencesFile
* @param context.preferencesFileError - Set if preferences file is blocked at theis origin so don't use it
* @param context.publicProfile
* @param context.index
*
* @returns {Promise}
*/
function _ensureTypeIndexes() {
_ensureTypeIndexes = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee6(context) {
return _regenerator["default"].wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
_context6.next = 2;
return ensureOneTypeIndex(context, true);
case 2:
_context6.next = 4;
return ensureOneTypeIndex(context, false);
case 4:
case "end":
return _context6.stop();
}
}
}, _callee6);
}));
return _ensureTypeIndexes.apply(this, arguments);
}
function ensureOneTypeIndex(_x10, _x11) {
return _ensureOneTypeIndex.apply(this, arguments);
}
/**
* Returns promise of context with arrays of symbols
*
* 2016-12-11 change to include forClass arc a la
* https://github.com/solid/solid/blob/master/proposals/data-discovery.md
*
* @param context.div - inuput - Place to put UI for login
* @param context.instances - output - array of instances
* @param context.containers - output - array of containers to look in
* @param klass
* @returns {Promise} of context
*/
function _ensureOneTypeIndex() {
_ensureOneTypeIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee9(context, isPublic) {
var makeIndexIfNecesary, _makeIndexIfNecesary;
return _regenerator["default"].wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
_makeIndexIfNecesary = function _ref4() {
_makeIndexIfNecesary = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee8(context, isPublic) {
var relevant, visibility, putIndex, _putIndex, newIndex, addMe, msg, ixs;
return _regenerator["default"].wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
_putIndex = function _ref2() {
_putIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee7(newIndex) {
var _msg;
return _regenerator["default"].wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
_context7.prev = 0;
_context7.next = 3;
return kb.fetcher.webOperation('PUT', newIndex.uri, {
data: '# ' + new Date() + ' Blank initial Type index\n',
contentType: 'text/turtle'
});
case 3:
return _context7.abrupt("return", context);
case 6:
_context7.prev = 6;
_context7.t0 = _context7["catch"](0);
_msg = 'Error creating new index ' + _context7.t0;
widgets.complain(context, _msg);
case 10:
case "end":
return _context7.stop();
}
}
}, _callee7, null, [[0, 6]]);
}));
return _putIndex.apply(this, arguments);
};
putIndex = function _ref(_x22) {
return _putIndex.apply(this, arguments);
};
relevant = isPublic ? context.publicProfile : context.preferencesFile;
visibility = isPublic ? 'public' : 'private';
// putIndex
context.index = context.index || {};
context.index[visibility] = context.index[visibility] || [];
if (!(context.index[visibility].length === 0)) {
_context8.next = 29;
break;
}
newIndex = $rdf.sym(relevant.dir().uri + visibility + 'TypeIndex.ttl');
console.log('Linking to new fresh type index ' + newIndex);
if (confirm('Ok to create a new empty index file at ' + newIndex + ', overwriting anything that was there?')) {
_context8.next = 11;
break;
}
throw new Error('cancelled by user');
case 11:
console.log('Linking to new fresh type index ' + newIndex);
addMe = [$rdf.st(context.me, ns.solid(visibility + 'TypeIndex'), newIndex, relevant)];
_context8.prev = 13;
_context8.next = 16;
return updatePromise([], addMe);
case 16:
_context8.next = 23;
break;
case 18:
_context8.prev = 18;
_context8.t0 = _context8["catch"](13);
msg = 'Error saving type index link saving back ' + newIndex + ': ' + _context8.t0;
UI.widgets.complain(context, msg);
return _context8.abrupt("return", context);
case 23:
console.log('Creating new fresh type index file' + newIndex);
_context8.next = 26;
return putIndex(newIndex);
case 26:
context.index[visibility].push(newIndex); // @@ wait
_context8.next = 38;
break;
case 29:
// officially exists
ixs = context.index[visibility];
_context8.prev = 30;
_context8.next = 33;
return kb.fetcher.load(ixs);
case 33:
_context8.next = 38;
break;
case 35:
_context8.prev = 35;
_context8.t1 = _context8["catch"](30);
UI.widgets.complain(context, 'ensureOneTypeIndex: loading indexes ' + _context8.t1);
case 38:
case "end":
return _context8.stop();
}
}
}, _callee8, null, [[13, 18], [30, 35]]);
}));
return _makeIndexIfNecesary.apply(this, arguments);
};
makeIndexIfNecesary = function _ref3(_x20, _x21) {
return _makeIndexIfNecesary.apply(this, arguments);
};
_context9.prev = 2;
_context9.next = 5;
return loadOneTypeIndex(context, isPublic);
case 5:
console.log('ensureOneTypeIndex: Type index exists already ' + isPublic ? context.index["public"][0] : context.index["private"][0]);
return _context9.abrupt("return", context);
case 9:
_context9.prev = 9;
_context9.t0 = _context9["catch"](2);
_context9.next = 13;
return makeIndexIfNecesary(context, isPublic);
case 13:
case "end":
return _context9.stop();
}
}
}, _callee9, null, [[2, 9]]);
}));
return _ensureOneTypeIndex.apply(this, arguments);
}
function findAppInstances(_x12, _x13, _x14) {
return _findAppInstances.apply(this, arguments);
} // @@@@ use teh one in rdflib.js when it is avaiable and delete this
function _findAppInstances() {
_findAppInstances = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee10(context, klass, isPublic) {
var kb, ns, fetcher, visibility, thisIndex, registrations, instances, containers, e, i, cont;
return _regenerator["default"].wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
kb = UI.store;
ns = UI.ns;
fetcher = UI.store.fetcher;
if (!(isPublic === undefined)) {
_context10.next = 9;
break;
}
_context10.next = 6;
return findAppInstances(context, klass, true);
case 6:
_context10.next = 8;
return findAppInstances(context, klass, false);
case 8:
return _context10.abrupt("return", context);
case 9:
visibility = isPublic ? 'public' : 'private';
_context10.prev = 10;
_context10.next = 13;
return loadOneTypeIndex(context, isPublic);
case 13:
_context10.next = 17;
break;
case 15:
_context10.prev = 15;
_context10.t0 = _context10["catch"](10);
case 17:
thisIndex = context.index[visibility];
registrations = thisIndex.map(function (ix) {
return kb.each(undefined, ns.solid('forClass'), klass, ix);
}).flat();
instances = registrations.map(function (reg) {
return kb.each(reg, ns.solid('instance'));
}).flat();
containers = registrations.map(function (reg) {
return kb.each(reg, ns.solid('instanceContainer'));
}).flat();
context.instances = context.instances || [];
context.instances = context.instances.concat(instances);
context.containers = context.containers || [];
context.containers = context.containers.concat(containers);
if (containers.length) {
_context10.next = 27;
break;
}
return _context10.abrupt("return", context);
case 27:
_context10.prev = 27;
_context10.next = 30;
return fetcher.load(containers);
case 30:
_context10.next = 37;
break;
case 32:
_context10.prev = 32;
_context10.t1 = _context10["catch"](27);
e = new Error('[FAI] Unable to load containers' + _context10.t1);
console.log(e); // complain
UI.widgets.complain(context, "Error looking for ".concat(UI.utils.label(klass), ": ").concat(_context10.t1)); // but then ignoire it
// throw new Error(e)
case 37:
for (i = 0; i < containers.length; i++) {
cont = containers[i];
context.instances = context.instances.concat(kb.each(cont, ns.ldp('contains')));
}
return _context10.abrupt("return", context);
case 39:
case "end":
return _context10.stop();
}
}
}, _callee10, null, [[10, 15], [27, 32]]);
}));
return _findAppInstances.apply(this, arguments);
}
function updatePromise(updater, del, ins) {
return new Promise(function (resolve, reject) {
updater.update(del, ins, function (uri, ok, errorBody) {
if (!ok) {
reject(new Error(errorBody));
} else {
resolve();
}
}); // callback
}); // promise
}
/* Register a new app in a type index
*/
function registerInTypeIndex(_x15, _x16, _x17, _x18) {
return _registerInTypeIndex.apply(this, arguments);
}
/**
* UI to control registration of instance
*
* @param context
* @param instance
* @param klass
*
* @returns {Promise}
*/
function _registerInTypeIndex() {
_registerInTypeIndex = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee11(context, instance, klass, isPublic) {
var kb, ns, indexes, index, registration, ins;
return _regenerator["default"].wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
kb = UI.store;
ns = UI.ns;
_context11.next = 4;
return ensureOneTypeIndex(context, isPublic);
case 4:
indexes = isPublic ? context.index["public"] : context.index["private"];
if (indexes.length) {
_context11.next = 7;
break;
}
throw new Error('registerInTypeIndex: What no type index?');
case 7:
index = indexes[0];
registration = UI.widgets.newThing(index);
ins = [// See https://github.com/solid/solid/blob/master/proposals/data-discovery.md
$rdf.st(registration, ns.rdf('type'), ns.solid('TypeRegistration'), index), $rdf.st(registration, ns.solid('forClass'), klass, index), $rdf.st(registration, ns.solid('instance'), instance, index)];
_context11.prev = 10;
_context11.next = 13;
return updatePromise(kb.updater, [], ins);
case 13:
_context11.next = 19;
break;
case 15:
_context11.prev = 15;
_context11.t0 = _context11["catch"](10);
console.log(_context11.t0);
alert(_context11.t0);
case 19:
return _context11.abrupt("return", context);
case 20:
case "end":
return _context11.stop();
}
}
}, _callee11, null, [[10, 15]]);
}));
return _registerInTypeIndex.apply(this, arguments);
}
function registrationControl(context, instance, klass) {
var kb = UI.store;
var ns = UI.ns;
var dom = context.dom;
var box = dom.createElement('div');
context.div.appendChild(box);
return ensureTypeIndexes(context).then(function () {
box.innerHTML = '<table><tbody><tr></tr><tr></tr></tbody></table>'; // tbody will be inserted anyway
box.setAttribute('style', 'font-size: 120%; text-align: right; padding: 1em; border: solid gray 0.05em;');
var tbody = box.children[0].children[0];
var form = kb.bnode(); // @@ say for now
var registrationStatements = function registrationStatements(index) {
var registrations = kb.each(undefined, ns.solid('instance'), instance).filter(function (r) {
return kb.holds(r, ns.solid('forClass'), klass);
});
var reg = registrations.length ? registrations[0] : widgets.newThing(index);
return [$rdf.st(reg, ns.solid('instance'), instance, index), $rdf.st(reg, ns.solid('forClass'), klass, index)];
};
var index, statements;
if (context.index["public"] && context.index["public"].length > 0) {
index = context.index["public"][0];
statements = registrationStatements(index);
tbody.children[0].appendChild(widgets.buildCheckboxForm(context.dom, UI.store, 'Public link to this ' + context.noun, null, statements, form, index));
}
if (context.index["private"] && context.index["private"].length > 0) {
index = context.index["private"][0];
statements = registrationStatements(index);
tbody.children[1].appendChild(widgets.buildCheckboxForm(context.dom, UI.store, 'Personal note of this ' + context.noun, null, statements, form, index));
}
return context;
}, function (e) {
var msg;
if (context.preferencesFileError) {
msg = '(Preferences not available)';
context.div.appendChild(dom.createElement('p')).textContent = msg;
} else {
msg = 'registrationControl: Type indexes not available: ' + e;
context.div.appendChild(UI.widgets.errorMessageBlock(context.dom, e));
}
console.log(msg);
})["catch"](function (e) {
var msg = 'registrationControl: Error making panel:' + e;
context.div.appendChild(UI.widgets.errorMessageBlock(context.dom, e));
console.log(msg);
});
}
/**
* UI to List at all registered things
* @param context
* @param options
*
* @returns {Promise}
*/
function registrationList(context, options) {
var kb = UI.store;
var ns = UI.ns;
var dom = context.dom;
var box = dom.createElement('div');
context.div.appendChild(box);
return ensureTypeIndexes(context).then(function (indexes) {
box.innerHTML = '<table><tbody></tbody></table>'; // tbody will be inserted anyway
box.setAttribute('style', 'font-size: 120%; text-align: right; padding: 1em; border: solid #eee 0.5em;');
var table = box.firstChild;
var ix = [];
var sts = [];
var vs = ['private', 'public'];
vs.forEach(function (visibility) {
if (options[visibility]) {
ix = ix.concat(context.index[visibility][0]);
sts = sts.concat(kb.statementsMatching(undefined, ns.solid('instance'), undefined, context.index[visibility][0]));
}
});
for (var i = 0; i < sts.length; i++) {
var statement = sts[i]; // var cla = statement.subject
var inst = statement.object; // if (false) {
// var tr = table.appendChild(dom.createElement('tr'))
// var anchor = tr.appendChild(dom.createElement('a'))
// anchor.setAttribute('href', inst.uri)
// anchor.textContent = utils.label(inst)
// } else {
// }
var deleteInstance = function deleteInstance(x) {
kb.updater.update([statement], [], function (uri, ok, errorBody) {
if (ok) {
console.log('Removed from index: ' + statement.subject);
} else {
console.log('Error: Cannot delete ' + statement + ': ' + errorBody);
}
});
};
var opts = {
deleteFunction: deleteInstance
};
var tr = widgets.personTR(dom, ns.solid('instance'), inst, opts);
table.appendChild(tr);
}
/*
//var containers = kb.each(klass, ns.solid('instanceContainer'));
if (containers.length) {
fetcher.load(containers).then(function(xhrs){
for (var i=0; i<containers.length; i++) {
var cont = containers[i];
instances = instances.concat(kb.each(cont, ns.ldp('contains')));
}
});
}
*/
return context;
});
}
/**
* Simple Access Control
*
* This function sets up a simple default ACL for a resource, with
* RWC for the owner, and a specified access (default none) for the public.
* In all cases owner has read write control.
* Parameter lists modes allowed to public
*
* @param docURI
* @param me {NamedNode} WebID of user
* @param options
* @param options.public {Array<string>} eg ['Read', 'Write']
*
* @returns {Promise<NamedNode>} Resolves with aclDoc uri on successful write
*/
function setACLUserPublic(docURI, me, options) {
var kb = UI.store;
var aclDoc = kb.any(kb.sym(docURI), kb.sym('http://www.iana.org/assignments/link-relations/acl'));
return Promise.resolve().then(function () {
if (aclDoc) {
return aclDoc;
}
return fetchACLRel(docURI)["catch"](function (err) {
throw new Error("Error fetching rel=ACL header for ".concat(docURI, ": ").concat(err));
});
}).then(function (aclDoc) {
var aclText = genACLText(docURI, me, aclDoc.uri, options);
return kb.fetcher.webOperation('PUT', aclDoc.uri, {
data: aclText,
contentType: 'text/turtle'
}).then(function (result) {
if (!result.ok) {
throw new Error('Error writing ACL text: ' + result.error);
}
return aclDoc;
});
});
}
/**
* @param docURI {string}
* @returns {Promise<NamedNode|null>}
*/
function fetchACLRel(docURI) {
var kb = UI.store;
var fetcher = kb.fetcher;
return fetcher.load(docURI).then(function (result) {
if (!result.ok) {
throw new Error('fetchACLRel: While loading:' + result.error);
}
var aclDoc = kb.any(kb.sym(docURI), kb.sym('http://www.iana.org/assignments/link-relations/acl'));
if (!aclDoc) {
throw new Error('fetchACLRel: No Link rel=ACL header for ' + docURI);
}
return aclDoc;
});
}
/**
* @param docURI {string}
* @param me {NamedNode}
* @param aclURI {string}
* @param options {Object}
*
* @returns {string} Serialized ACL
*/
function genACLText(docURI, me, aclURI) {
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var optPublic = options["public"] || [];
var g = $rdf.graph();
var auth = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
var a = g.sym(aclURI + '#a1');
var acl = g.sym(aclURI);
var doc = g.sym(docURI);
g.add(a, UI.ns.rdf('type'), auth('Authorization'), acl);
g.add(a, auth('accessTo'), doc, acl);
if (options.defaultForNew) {
// TODO: Should this be auth('default') instead?
g.add(a, auth('defaultForNew'), doc, acl);
}
g.add(a, auth('agent'), me, acl);
g.add(a, auth('mode'), auth('Read'), acl);
g.add(a, auth('mode'), auth('Write'), acl);
g.add(a, auth('mode'), auth('Control'), acl);
if (optPublic.length) {
a = g.sym(aclURI + '#a2');
g.add(a, UI.ns.rdf('type'), auth('Authorization'), acl);
g.add(a, auth('accessTo'), doc, acl);
g.add(a, auth('agentClass'), UI.ns.foaf('Agent'), acl);
for (var p = 0; p < optPublic.length; p++) {
g.add(a, auth('mode'), auth(optPublic[p]), acl); // Like 'Read' etc
}
}
return $rdf.serialize(acl, g, aclURI, 'text/turtle');
}
/**
* @returns {NamedNode|null}
*/
function offlineTestID() {
if (typeof $SolidTestEnvironment !== 'undefined' && $SolidTestEnvironment.username) {
// Test setup
console.log('Assuming the user is ' + $SolidTestEnvironment.username);
return $rdf.sym($SolidTestEnvironment.username);
}
if (typeof document !== 'undefined' && document.location && ('' + document.location).slice(0, 16) === 'http://localhost') {
var div = document.getElementById('appTarget');
if (!div) return null;
var id = div.getAttribute('testID');
if (!id) return null;
/* me = kb.any(subject, UI.ns.acl('owner')); // when testing on plane with no webid
*/
console.log('Assuming user is ' + id);
return $rdf.sym(id);
}
return null;
}
/**
* Bootstrapping identity
* (Called by `loginStatusBox()`)
* @private
*
* @param dom
* @param setUserCallback(user: object)
*
* @returns {Element}
*/
function getDefaultSignInButtonStyle() {
return 'padding: 1em; border-radius:0.5em; margin: 2em; font-size: 100%;';
}
function signInOrSignUpBox(dom, setUserCallback, options) {
options = options || {};
var signInButtonStyle = options.buttonStyle || getDefaultSignInButtonStyle();
var box = dom.createElement('div');
var magicClassName = 'SolidSignInOrSignUpBox';
console.log('widgets.signInOrSignUpBox');
box.setUserCallback = setUserCallback;
box.setAttribute('class', magicClassName);
box.style = 'display:flex;'; // Sign in button with PopUP
var signInPopUpButton = dom.createElement('input'); // multi
box.appendChild(signInPopUpButton);
signInPopUpButton.setAttribute('type', 'button');
signInPopUpButton.setAttribute('value', 'Log in');
signInPopUpButton.setAttribute('style', signInButtonStyle + 'background-color: #eef;');
signInPopUpButton.addEventListener('click', function () {
var offline = offlineTestID();
if (offline) return setUserCallback(offline.uri);
return solidAuthClient.popupLogin().then(function (session) {
var webIdURI = session.webId; // setUserCallback(webIdURI)
var divs = dom.getElementsByClassName(magicClassName);
console.log('Logged in, ' + divs.length + ' panels to be serviced'); // At the same time, satiffy all the other login boxes
for (var i = 0; i < divs.length; i++) {
var div = divs[i];
if (div.setUserCallback) {
try {
div.setUserCallback(webIdURI);
var parent = div.parentNode;
if (parent) {
parent.removeChild(div);
}
} catch (e) {
console.log('## Error satisfying login box: ' + e);
div.appendChild(UI.widgets.errorMessageBlock(dom, e));
}
}
}
});
}, false); // Sign up button
var signupButton = dom.createElement('input');
box.appendChild(signupButton);
signupButton.setAttribute('type', 'button');
signupButton.setAttribute('value', 'Sign Up for Solid');
signupButton.setAttribute('style', signInButtonStyle + 'background-color: #efe;');
signupButton.addEventListener('click', function (e) {
var signupMgr = new SolidTls.Signup();
signupMgr.signup().then(function (uri) {
console.log('signInOrSignUpBox signed up ' + uri);
setUserCallback(uri);
});
}, false);
return box;
}
/**
* @returns {Promise<string|null>} Resolves with WebID URI or null
*/
function webIdFromSession(session) {
var webId = session ? session.webId : null;
if (webId) {
saveUser(webId);
}
return webId;
}
/**
* @returns {Promise<string|null>} Resolves with WebID URI or null
*/
/*
function checkCurrentUser () {
return checkUser()
}
*/
/**
* @param [setUserCallback] {Function} Optional callback, `setUserCallback(webId|null)`
*
* @returns {Promise<string|null>} Resolves with web id uri, if no callback provided
*/
function checkUser(setUserCallback) {
// Check to see if already logged in / have the WebID
var me = defaultTestUser();
if (me) {
return Promise.resolve(setUserCallback ? setUserCallback(me) : me);
} // doc = kb.any(doc, UI.ns.link('userMirror')) || doc
return solidAuthClient.currentSession().then(webIdFromSession, function (err) {
console.log('Error fetching currentSession:', err);
return null;
}).then(function (webId) {
// if (webId.startsWith('dns:')) { // legacy rww.io pseudo-users
// webId = null
// }
var me = saveUser(webId);
if (me) {
console.log('(Logged in as ' + me + ' by authentication)');
}
return setUserCallback ? setUserCallback(me) : me;
});
}
/**
* Login status box
*
* A big sign-up/sign in box or a logout box depending on the state
*
* @param dom
* @param listener(uri)
*
* @returns {Element}
*/
function loginStatusBox(dom, listener, options) {
// 20190630
var me = defaultTestUser();
var box = dom.createElement('div');
function setIt(newidURI) {
if (!newidURI) {
return;
}
var uri = newidURI.uri || newidURI; // UI.preferences.set('me', uri)
me = $rdf.sym(uri);
box.refresh();
if (listener) listener(me.uri);
}
function logoutButtonHandler(event) {
// UI.preferences.set('me', '')
solidAuthClient.logout().then(function () {
var message = 'Your Web ID was ' + me + '. It has been forgotten.';
me = null;
try {
UI.log.alert(message);
} catch (e) {
window.alert(message);
}
box.refresh();
if (listener) listener(null);
}, function (err) {
alert('Fail to log out:' + err);
});
}
var logoutButton = function logoutButton(me, options) {
options = options || {};
var signInButtonStyle = options.buttonStyle || getDefaultSignInButtonStyle();
var logoutLabel = 'Web ID logout';
if (me) {
var nick = UI.store.any(me, UI.ns.foaf('nick')) || UI.store.any(me, UI.ns.foaf('name'));
if (nick) {
logoutLabel = 'Logout ' + nick.value;
}
}
var signOutButton = dom.createElement('input'); // signOutButton.className = 'WebIDCancelButton'
signOutButton.setAttribute('type', 'button');
signOutButton.setAttribute('value', logoutLabel);
signOutButton.setAttribute('style', signInButtonStyle + 'background-color: #eee;');
signOutButton.addEventListener('click', logoutButtonHandler, false);
return signOutButton;
};
box.refresh = function () {
solidAuthClient.currentSession().then(function (session) {
if (session && session.webId) {
me = $rdf.sym(session.webId);
} else {
me = null;
}
if (me && box.me !== me.uri || !me && box.me) {
widgets.clearElement(box);
if (me) {
box.appendChild(logoutButton(me, options));
} else {
box.appendChild(signInOrSignUpBox(dom, setIt, options));
}
}
box.me = me ? me.uri : null;
}, function (err) {
alert('loginStatusBox: ' + err);
});
};
if (solidAuthClient.trackSession) {
solidAuthClient.trackSession(function (session) {
if (session && session.webId) {
me = $rdf.sym(session.webId);
} else {
me = null;
}
box.refresh();
});
}
box.me = '99999'; // Force refresh
box.refresh();
return box;
}
/**
* Workspace selection etc
*/
/**
* Returns a UI object which, if it selects a workspace,
* will callback(workspace, newBase).
*
* If necessary, will get an account, preferences file, etc. In sequence:
*
* - If not logged in, log in.
* - Load preferences file
* - Prompt user for workspaces
* - Allows the user to just type in a URI by hand
*
* Calls back with the ws and the base URI
*
* @param dom
* @param appDetails
* @param callbackWS
* @returns {Element}
*/
function selectWorkspace(dom, appDetails, callbackWS) {
var noun = appDetails.noun;
var appPathSegment = appDetails.appPathSegment;
var me = defaultTestUser();
var kb = UI.store;
var box = dom.createElement('div');
var context = {
me: me,
dom: dom,
div: box
};
var say = function say(s) {
box.appendChild(UI.widgets.errorMessageBlock(dom, s));
};
var figureOutBase = function figureOutBase(ws) {
var newBase = kb.any(ws, UI.ns.space('uriPrefix'));
if (!newBase) {
newBase = ws.uri.split('#')[0];
} else {
newBase = newBase.value;
}
if (newBase.slice(-1) !== '/') {
console.log(appPathSegment + ': No / at end of uriPrefix ' + newBase); // @@ paramater?
newBase = newBase + '/';
}
var now = new Date();
newBase += appPathSegment + '/id' + now.getTime() + '/'; // unique id
return newBase;
};
var displayOptions = function displayOptions(context) {
// var status = ''
var id = context.me;
var preferencesFile = context.preferencesFile;
var newBase = null; // A workspace specifically defined in the private preferences file:
var w = kb.statementsMatching(id, UI.ns.space('workspace'), // Only trust prefs file here
undefined, preferencesFile).map(function (st) {
return st.object;
}); // A workspace in a storage in the public profile:
var storages = kb.each(id, UI.ns.space('storage')); // @@ No provenance requirement at the moment
storages.map(function (s) {
w = w.concat(kb.each(s, UI.ns.ldp('contains')));
});
if (w.length === 1) {
say('Workspace used: ' + w[0].uri); // @@ allow user to see URI
newBase = figureOutBase(w[0]); // callbackWS(w[0], newBase)
} else if (w.length === 0) {
say("You don't seem to have any workspaces. You have " + storages.length + ' storages.');
} // Prompt for ws selection or creation
// say( w.length + " workspaces for " + id + "Chose one.");
var table = dom.createElement('table');
table.setAttribute('style', 'border-collapse:separate; border-spacing: 0.5em;'); // var popup = window.open(undefined, '_blank', { height: 300, width:400 }, false)
box.appendChild(table); // Add a field for directly adding the URI yourself
// var hr = box.appendChild(dom.createElement('hr')) // @@
box.appendChild(dom.createElement('hr')); // @@
var p = box.appendChild(dom.createElement('p'));
p.textContent = 'Where would you like to store the data for the ' + noun + '? ' + 'Give the URL of the directory where you would like the data stored.';
var baseField = box.appendChild(dom.createElement('input'));
baseField.setAttribute('type', 'text');
baseField.size = 80; // really a string
baseField.label = 'base URL';
baseField.autocomplete = 'on';
if (newBase) {
// set to default
baseField.value = newBase;
}
context.baseField = baseField;
box.appendChild(dom.createElement('br')); // @@
var button = box.appendChild(dom.createElement('button'));
button.textContent = 'Start new ' +