blockstack-storage
Version:
The Blockstack Javascript library for storage.
816 lines (663 loc) • 27.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.datastoreGetId = datastoreGetId;
exports.datastoreCreateRequest = datastoreCreateRequest;
exports.datastoreCreate = datastoreCreate;
exports.datastoreCreateIsPartialFailure = datastoreCreateIsPartialFailure;
exports.datastoreCreateSetPartialFailure = datastoreCreateSetPartialFailure;
exports.datastoreCreateSetRetry = datastoreCreateSetRetry;
exports.datastoreCreateUnsetPartialFailure = datastoreCreateUnsetPartialFailure;
exports.datastoreDeleteRequest = datastoreDeleteRequest;
exports.datastoreDelete = datastoreDelete;
exports.datastoreRequestPathInfo = datastoreRequestPathInfo;
exports.datastoreMount = datastoreMount;
exports.datastoreMountOrCreate = datastoreMountOrCreate;
var _policy = require('./policy');
var _requests = require('./requests');
var _api = require('./api');
var _schemas = require('./schemas');
var _blob = require('./blob');
var _inode = require('./inode');
var _util = require('./util');
var _blockstack = require('blockstack');
var _metadata = require('./metadata');
var http = require('http');
var uuid4 = require('uuid/v4');
var bitcoinjs = require('bitcoinjs-lib');
var BigInteger = require('bigi');
var Promise = require('promise');
var assert = require('assert');
var Ajv = require('ajv');
var jsontokens = require('jsontokens');
var urlparse = require('url');
/*
* Convert a datastore public key to its ID.
* @param ds_public_key (String) hex-encoded ECDSA public key
*/
function datastoreGetId(ds_public_key_hex) {
var ec = bitcoinjs.ECPair.fromPublicKeyBuffer(Buffer.from(ds_public_key_hex, 'hex'));
return ec.getAddress();
}
/*
* Create the signed request to create a datastore.
* This information can be fed into datastoreCreate()
* Returns an object with:
* .datastore_info: datastore information
* .datastore_sigs: signatures over the above.
*/
function datastoreCreateRequest(ds_type, ds_private_key_hex, drivers, device_id, all_device_ids) {
assert(ds_type === 'datastore' || ds_type === 'collection');
var root_uuid = uuid4();
var ds_public_key = (0, _blockstack.getPubkeyHex)(ds_private_key_hex);
var datastore_id = datastoreGetId(ds_public_key);
// make empty device root
var device_root = (0, _inode.makeEmptyDeviceRootDirectory)(datastore_id, []);
var device_root_data_id = datastore_id + '.' + root_uuid;
var device_root_blob = (0, _blob.makeDataInfo)(device_root_data_id, (0, _util.jsonStableSerialize)(device_root), device_id);
var device_root_str = (0, _util.jsonStableSerialize)(device_root_blob);
// actual datastore payload
var datastore_info = {
'type': ds_type,
'pubkey': ds_public_key,
'drivers': drivers,
'device_ids': all_device_ids,
'root_uuid': root_uuid
};
var data_id = datastore_id + '.datastore';
var datastore_blob = (0, _blob.makeDataInfo)(data_id, (0, _util.jsonStableSerialize)(datastore_info), device_id);
var datastore_str = (0, _util.jsonStableSerialize)(datastore_blob);
// sign them all
var root_sig = (0, _blob.signDataPayload)(device_root_str, ds_private_key_hex);
var datastore_sig = (0, _blob.signDataPayload)(datastore_str, ds_private_key_hex);
// make and sign tombstones for the root
var root_data_id = datastore_id + '.' + root_uuid;
var root_tombstones = (0, _blob.makeDataTombstones)(all_device_ids, root_data_id);
var signed_tombstones = (0, _blob.signDataTombstones)(root_tombstones, ds_private_key_hex);
var info = {
'datastore_info': {
'datastore_blob': datastore_str,
'root_blob': device_root_str
},
'datastore_sigs': {
'datastore_sig': datastore_sig,
'root_sig': root_sig
},
'root_tombstones': signed_tombstones
};
return info;
}
/*
* Create a datastore
* Asynchronous; returns a Promise that resolves to either {'status': true} (on success)
* or {'error': ...} (on error)
*/
function datastoreCreate(blockstack_hostport, blockstack_session_token, datastore_request) {
var datastore_pubkey = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var apiPassword = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var payload = {
'datastore_info': {
'datastore_blob': datastore_request.datastore_info.datastore_blob,
'root_blob': datastore_request.datastore_info.root_blob
},
'datastore_sigs': {
'datastore_sig': datastore_request.datastore_sigs.datastore_sig,
'root_sig': datastore_request.datastore_sigs.root_sig
},
'root_tombstones': datastore_request.root_tombstones
};
var hostinfo = (0, _util.splitHostPort)(blockstack_hostport);
var options = {
'method': 'POST',
'host': hostinfo.host,
'port': hostinfo.port,
'path': '/v1/stores'
};
if (apiPassword) {
assert(datastore_pubkey, 'Need datastore public key for password-based datastore creation');
options['path'] += '?datastore_pubkey=' + datastore_pubkey;
options['headers'] = { 'Authorization': 'bearer ' + apiPassword };
} else {
options['headers'] = { 'Authorization': 'bearer ' + blockstack_session_token };
}
var body = JSON.stringify(payload);
options['headers']['Content-Type'] = 'application/json';
options['headers']['Content-Length'] = new Buffer(body).length;
return (0, _requests.httpRequest)(options, _schemas.PUT_DATASTORE_RESPONSE, body);
}
/*
* Did we partially succeed to create the datastore indicated by the session token?
* Return true if so; false if not.
*/
function datastoreCreateIsPartialFailure(sessionToken) {
var session_app_name = (0, _metadata.getSessionAppName)(sessionToken);
var session = jsontokens.decodeToken(sessionToken).payload;
var blockchain_id = (0, _metadata.getBlockchainIDFromSessionOrDefault)(session);
var gaia_state = (0, _metadata.getGaiaLocalData)();
var marker = blockchain_id + '/' + session_app_name;
if (!gaia_state.partial_create_failures) {
return false;
}
if (gaia_state.partial_create_failures[marker]) {
return true;
}
return false;
}
/*
* Remember that we failed to create this datastore, and that
* a subsequent datastoreCreate() should succeed.
*/
function datastoreCreateSetPartialFailure(sessionToken) {
var session_app_name = (0, _metadata.getSessionAppName)(sessionToken);
var session = jsontokens.decodeToken(sessionToken).payload;
var blockchain_id = (0, _metadata.getBlockchainIDFromSessionOrDefault)(session);
var gaia_state = (0, _metadata.getGaiaLocalData)();
var marker = blockchain_id + '/' + session_app_name;
if (!gaia_state.partial_create_failures) {
gaia_state.partial_create_failures = {};
}
gaia_state.partial_create_failures[marker] = true;
(0, _metadata.setGaiaLocalData)(gaia_state);
}
/*
* This is the "public" version of datastoreCreateSetPartialFailure
* that clients should call
*/
function datastoreCreateSetRetry(sessionToken) {
return datastoreCreateSetPartialFailure(sessionToken);
}
/*
* Remember that we succeeded to create this datastore, and that
* a subsequent datastoreCreate() should fail.
*/
function datastoreCreateUnsetPartialFailure(sessionToken) {
var session_app_name = (0, _metadata.getSessionAppName)(sessionToken);
var session = jsontokens.decodeToken(sessionToken).payload;
var blockchain_id = (0, _metadata.getBlockchainIDFromSessionOrDefault)(session);
var gaia_state = (0, _metadata.getGaiaLocalData)();
var marker = blockchain_id + '/' + session_app_name;
if (!gaia_state.partial_create_failures) {
gaia_state.partial_create_failures = {};
}
gaia_state.partial_create_failures[marker] = false;
(0, _metadata.setGaiaLocalData)(gaia_state);
}
/*
* Generate the data needed to delete a datastore.
*
* @param ds (Object) a datastore context (will be loaded from localstorage if not given)
*
* Returns an object to be given to datastoreDelete()
*/
function datastoreDeleteRequest() {
var ds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (!ds) {
var app_name = (0, _metadata.getSessionAppName)();
assert(app_name);
var _datastore_id = (0, _metadata.getSessionDatastoreID)();
assert(_datastore_id);
ds = (0, _metadata.getCachedMountContext)(_datastore_id, app_name);
assert(ds);
}
var datastore_id = ds.datastore_id;
var device_ids = ds.datastore.device_ids;
var root_uuid = ds.datastore.root_uuid;
var data_id = datastore_id + '.datastore';
var root_data_id = datastore_id + '.' + root_uuid;
var tombstones = (0, _blob.makeDataTombstones)(device_ids, data_id);
var signed_tombstones = (0, _blob.signDataTombstones)(tombstones, ds.privkey_hex);
var root_tombstones = (0, _blob.makeDataTombstones)(device_ids, root_data_id);
var signed_root_tombstones = (0, _blob.signDataTombstones)(root_tombstones, ds.privkey_hex);
var ret = {
'datastore_tombstones': signed_tombstones,
'root_tombstones': signed_root_tombstones
};
return ret;
}
/*
* Delete a datastore
*
* @param ds (Object) OPTINOAL: the datastore context (will be loaded from localStorage if not given)
* @param ds_tombstones (Object) OPTINOAL: signed information from datastoreDeleteRequest()
* @param root_tombstones (Object) OPTINAL: signed information from datastoreDeleteRequest()
*
* Asynchronous; returns a Promise that resolves to either {'status': true} on success
* or {'error': ...} on error
*/
function datastoreDelete() {
var ds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var ds_tombstones = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var root_tombstones = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (!ds) {
var session = jsontokens.decodeToken((0, _metadata.getSessionToken)()).payload;
var app_name = (0, _metadata.getSessionAppName)();
assert(app_name);
var datastore_id = (0, _metadata.getSessionDatastoreID)();
assert(datastore_id);
ds = (0, _metadata.getCachedMountContext)(datastore_id, app_name);
assert(ds);
}
if (!ds_tombstones || !root_tombstones) {
var delete_info = datastoreDeleteRequest(ds);
ds_tombstones = delete_info['datastore_tombstones'];
root_tombstones = delete_info['root_tombstones'];
}
var payload = {
'datastore_tombstones': ds_tombstones,
'root_tombstones': root_tombstones
};
var options = {
'method': 'DELETE',
'host': ds.host,
'port': ds.port,
'path': '/v1/stores'
};
options['headers'] = { 'Authorization': 'bearer ' + (0, _metadata.getSessionToken)() };
var body = JSON.stringify(payload);
options['headers']['Content-Type'] = 'application/json';
options['headers']['Content-Length'] = new Buffer(body).length;
return (0, _requests.httpRequest)(options, _schemas.SUCCESS_FAIL_SCHEMA, body);
}
/*
* Are we in single-reader storage?
* i.e. does this device's session token own this datastore?
*/
function isSingleReaderMount(sessionToken, datastore_id) {
var blockchain_id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (!blockchain_id) {
blockchain_id = (0, _metadata.getSessionBlockchainID)(sessionToken);
}
if (!blockchain_id) {
// no blockchain ID given means we can't be in multi-reader storage mode
return true;
}
if (datastore_id === jsontokens.decodeToken(sessionToken).payload.app_user_id) {
// the session token indicates that the datastore we're mounting was, in fact,
// created by this device. We can use datastore IDs and device IDs.
return true;
}
return false;
}
/*
* Make a request's query string for either single-reader
* or multi-reader storage, given the datastore mount context.
*
* Returns {'store_id': ..., 'qs': ..., 'host': ..., 'port': ...} on success
* Throws on error.
*/
function datastoreRequestPathInfo(dsctx) {
assert(dsctx.blockchain_id && dsctx.app_name || dsctx.datastore_id);
if (dsctx.datastore_id) {
// single-reader mode
var device_ids = [];
var public_keys = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = dsctx['app_public_keys'][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var apk = _step.value;
device_ids.push(apk['device_id']);
public_keys.push(apk['public_key']);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var device_ids_str = device_ids.join(',');
var public_keys_str = public_keys.join(',');
var info = {
'store_id': dsctx.datastore_id,
'qs': 'device_ids=' + device_ids_str + '&device_pubkeys=' + public_keys_str,
'host': dsctx.host,
'port': dsctx.port
};
return info;
} else {
// multi-reader mode
var _info = {
'store_id': dsctx.app_name,
'qs': 'blockchain_id=' + dsctx.blockchain_id,
'host': dsctx.host,
'port': dsctx.port
};
return _info;
}
}
/*
* Look up a datastore and establish enough contextual information to do subsequent storage operations.
* Asynchronous; returns a Promise
*
* opts is an object that must contain EITHER:
* * a single-reader datastore identifier, which is:
* * * datastoreID (string) the datastore ID
* * * deviceID (string) this device ID
* * * dataPubkeys (array) this is an array of {'device_id': ..., 'public_key': ...} objects, where one such object has `device_id` equal to opts.device_id
* OR
* * a multi-reader datastore identifier, which is:
* * * appName (string) the application name
* * * blockchainID (string) the blockchain ID that owns the datastore
*
* If we're going to write to this datastore, then we *additionally* need:
* * appPrivateKey (string) the application private key
* * sessionToken (string) the session JWT (optional)
*
* sessionToken may be given as an opt, in which case the following fields will be used
* if not provided in opts:
* * appName from session.app_domain
* * blockchainID from session.blockchain_id
* * dataPubkeys from session.app_public_keys
* * deviceID from session.device_id
*
* Uses opts.apiPassword for authentication if given.
*
* Returns a Promise that resolves to a datastore connection,
* with the following properties:
* .host: blockstack host
* .datastore: datastore object
*
* Returns a Promise that resolves to null, if the datastore does not exist.
*
* Throws an error on all other errors
*/
function datastoreMount(opts) {
var no_cache = opts.noCachedMounts;
var sessionToken = opts.sessionToken;
var blockchain_id = opts.blockchainID;
var app_name = opts.appName;
var this_device_id = opts.deviceID;
var app_public_keys = opts.dataPubkeys;
if (!sessionToken) {
sessionToken = (0, _metadata.getSessionToken)();
assert(sessionToken);
}
var session_blockchain_id = (0, _metadata.getSessionBlockchainID)(sessionToken);
var session_datastore_id = (0, _metadata.getSessionDatastoreID)(sessionToken);
var api_endpoint = null;
if (session_blockchain_id === blockchain_id) {
// this is definitely single-reader
blockchain_id = null;
}
// get our app private key
var userData = (0, _metadata.getUserData)();
var datastore_privkey_hex = userData.appPrivateKey;
if (!datastore_privkey_hex) {
// can only happen if testing
datastore_privkey_hex = opts.appPrivateKey;
}
assert(datastore_privkey_hex);
var session = jsontokens.decodeToken(sessionToken).payload;
var session_app_name = (0, _metadata.getSessionAppName)(sessionToken);
// did we try to create this before, but failed part-way through?
if (datastoreCreateIsPartialFailure(sessionToken)) {
// technically does not exist yet.
console.log('Will not mount datastore due to previous partial failure');
return new Promise(function (resolve, reject) {
resolve(null);
});
}
// maybe cached?
if (!no_cache) {
// if this is our datastore, use our datastore ID.
// otherwise use the blockchain ID
var ds_cache_key = blockchain_id || session_datastore_id;
var ds = (0, _metadata.getCachedMountContext)(ds_cache_key, session_app_name);
if (ds) {
return new Promise(function (resolve, reject) {
resolve(ds);
});
}
}
// not cached. setup from session token
if (!this_device_id) {
this_device_id = session.device_id;
assert(this_device_id);
}
if (!app_public_keys) {
app_public_keys = session.app_public_keys;
}
api_endpoint = session.api_endpoint;
if (!blockchain_id) {
assert(session_datastore_id, 'No datastore ID in session');
console.log('Single-reader/writer mount of ' + session_datastore_id);
} else {
// multi-reader info
assert(app_name || session, 'Need either appName or sessionToken in opts');
if (!app_name && session) {
app_name = (0, _metadata.getSessionAppName)(sessionToken);
assert(app_name, 'Invalid session token ' + sessionToken);
}
console.log('Multi-reader mount of ' + blockchain_id + '/' + app_name);
}
assert(blockchain_id && app_name || session_datastore_id, 'Need either blockchain_id (' + blockchain_id + ') / app_name (' + app_name + ') or datastore_id (' + session_datastore_id + ')');
if (api_endpoint.indexOf('://') < 0) {
var new_api_endpoint = 'https://' + api_endpoint;
if (urlparse.parse(new_api_endpoint).hostname === 'localhost') {
new_api_endpoint = 'http://' + api_endpoint;
}
api_endpoint = new_api_endpoint;
}
var urlinfo = urlparse.parse(api_endpoint);
var blockstack_hostport = urlinfo.host;
var scheme = urlinfo.protocol.replace(':', '');
var host = urlinfo.hostname;
var port = urlinfo.port;
var ctx = {
'scheme': scheme,
'host': host,
'port': port,
'blockchain_id': blockchain_id,
'app_name': app_name,
'datastore_id': session_datastore_id,
'app_public_keys': app_public_keys,
'device_id': this_device_id,
'datastore': null,
'privkey_hex': null,
'created': false
};
if (!blockchain_id) {
// this is *our* datastore
ctx['privkey_hex'] = datastore_privkey_hex;
}
var path_info = datastoreRequestPathInfo(ctx);
assert(path_info.store_id, 'BUG: no store ID deduced from ' + JSON.stringify(ctx));
var options = {
'method': 'GET',
'host': path_info.host,
'port': path_info.port,
'path': '/v1/stores/' + path_info.store_id + '?' + path_info.qs
};
console.log('Mount datastore ' + options.path);
if (opts.apiPassword && datastore_privkey_hex) {
options['headers'] = { 'Authorization': 'bearer ' + opts.apiPassword };
// need to explicitly pass the datastore public key
options['path'] += '&datastore_pubkey=' + (0, _blockstack.getPubkeyHex)(datastore_privkey_hex);
} else {
options['headers'] = { 'Authorization': 'bearer ' + sessionToken };
}
return (0, _requests.httpRequest)(options, _schemas.DATASTORE_RESPONSE_SCHEMA).then(function (ds) {
if (!ds || ds.error) {
// ENOENT?
if (!ds || ds.errno === 'ENOENT') {
return null;
} else {
var errorMsg = ds.error || 'No response given';
throw new Error('Failed to get datastore: ' + errorMsg);
}
} else {
ctx['datastore'] = ds.datastore;
// save
if (!no_cache) {
// if this is our datastore, use the datastore ID.
// otherwise use the blockchain ID
var _ds_cache_key = blockchain_id || session_datastore_id;
if (_ds_cache_key === session_datastore_id) {
// this is *our* datastore. We had better have the data key
assert(datastore_privkey_hex, 'Missing data private key');
assert(ctx.privkey_hex, 'Missing data private key in mount context');
}
console.log('Cache datastore for ' + _ds_cache_key + '/' + session_app_name);
(0, _metadata.setCachedMountContext)(_ds_cache_key, session_app_name, ctx);
}
return ctx;
}
});
}
/*
* Connect to or create a datastore.
* Asynchronous, returns a Promise
*
* Returns a Promise that yields a datastore connection context.
* If we created this datastore, then .urls = {'datastore': [...], 'root': [...]} will be defined in the returned context.
*
* Throws on error.
*
*/
function datastoreMountOrCreate() {
var replication_strategy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var sessionToken = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var appPrivateKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var apiPassword = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
if (!sessionToken) {
var userData = (0, _metadata.getUserData)();
sessionToken = userData.coreSessionToken;
assert(sessionToken);
}
// decode
var session = jsontokens.decodeToken(sessionToken).payload;
var session_blockchain_id = (0, _metadata.getBlockchainIDFromSessionOrDefault)(session);
var session_datastore_id = (0, _metadata.getSessionDatastoreID)(sessionToken);
var session_app_name = (0, _metadata.getSessionAppName)(sessionToken);
// cached, and not partially-failed create?
var ds = (0, _metadata.getCachedMountContext)(session_datastore_id, session_app_name);
if (ds && !datastoreCreateIsPartialFailure(sessionToken)) {
return new Promise(function (resolve, reject) {
resolve(ds);
});
}
// no cached datastore context.
// go ahead and create one (need appPrivateKey)
if (!appPrivateKey) {
var _userData = (0, _metadata.getUserData)();
appPrivateKey = _userData.appPrivateKey;
assert(appPrivateKey);
}
var drivers = null;
var app_name = (0, _metadata.getSessionAppName)(sessionToken);
// find satisfactory storage drivers
if (replication_strategy.drivers) {
drivers = replication_strategy.drivers;
} else {
if (Object.keys(session.storage.preferences).includes(app_name)) {
// app-specific preference
drivers = session.storage.preferences[app_name];
} else {
// select defaults given the replication strategy
drivers = (0, _policy.selectDrivers)(replication_strategy, session.storage.classes);
}
}
var api_endpoint = session.api_endpoint;
if (api_endpoint.indexOf('://') < 0) {
var new_api_endpoint = 'https://' + api_endpoint;
if (urlparse.parse(new_api_endpoint).hostname === 'localhost') {
new_api_endpoint = 'http://' + api_endpoint;
}
api_endpoint = new_api_endpoint;
}
var hostport = urlparse.parse(api_endpoint).host;
var appPublicKeys = session.app_public_keys;
var deviceID = session.device_id;
var allDeviceIDs = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = appPublicKeys[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var apk = _step2.value;
allDeviceIDs.push(apk['device_id']);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
if (drivers) {
console.log('Will use drivers ' + drivers.join(','));
}
console.log('Datastore will span devices ' + allDeviceIDs.join(','));
var datastoreOpts = {
'appPrivateKey': appPrivateKey,
'sessionToken': sessionToken,
'apiPassword': apiPassword
};
return datastoreMount(datastoreOpts).then(function (datastore_ctx) {
if (!datastore_ctx) {
// does not exist
console.log("Datastore does not exist; creating...");
var info = datastoreCreateRequest('datastore', appPrivateKey, drivers, deviceID, allDeviceIDs);
// go create it
return datastoreCreate(hostport, sessionToken, info, (0, _blockstack.getPubkeyHex)(appPrivateKey), apiPassword).then(function (res) {
if (res.error) {
console.log(res.error);
var errorNo = res.errno || 'UNKNOWN';
var errorMsg = res.error || 'UNKNOWN';
throw new Error('Failed to create datastore (errno ' + errorNo + '): ' + errorMsg);
}
assert(res.root_urls, 'Missing root URLs');
assert(res.datastore_urls, 'Missing datastore URLs');
// this create succeeded
datastoreCreateUnsetPartialFailure(sessionToken);
// this is required for testing purposes, since the core session token will not have been set
var userData = (0, _metadata.getUserData)();
if (!userData.coreSessionToken && sessionToken || !userData.appPrivateKey && appPrivateKey) {
console.log("\nIn test framework; saving session token\n");
if (!userData.coreSessionToken && sessionToken) {
userData.coreSessionToken = sessionToken;
}
if (!userData.appPrivateKey && appPrivateKey) {
userData.appPrivateKey = appPrivateKey;
}
(0, _metadata.setUserData)(userData);
}
// connect to it now
return datastoreMount(datastoreOpts).then(function (datastore_ctx) {
// return URLs as well
datastore_ctx.urls = {
root: res.root_urls,
datastore: res.datastore_urls
};
datastore_ctx.created = true;
return datastore_ctx;
});
});
} else if (datastore_ctx.error) {
// some other error
var errorMsg = datastore_ctx.error || 'UNKNOWN';
var errorNo = datastore_ctx.errno || 'UNKNOWN';
throw new Error('Failed to access datastore (errno ' + errorNo + '): ' + errorMsg);
} else {
// exists
return datastore_ctx;
}
});
}