@neurosity/sdk
Version:
Neurosity SDK
435 lines (434 loc) • 18.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirebaseUser = exports.createUser = exports.credentialWithLink = void 0;
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const app_1 = __importDefault(require("firebase/app"));
const SERVER_TIMESTAMP = app_1.default.database.ServerValue.TIMESTAMP;
/**
* @hidden
*/
exports.credentialWithLink = app_1.default.auth.EmailAuthProvider.credentialWithLink;
/**
* @hidden
*/
function createUser(...args) {
return new app_1.default.User(...args);
}
exports.createUser = createUser;
/**
* @hidden
*/
class FirebaseUser {
constructor(firebaseApp) {
this.app = firebaseApp.app;
this.app.auth().onAuthStateChanged((user) => {
this.user = user;
});
}
auth() {
return this.app.auth();
}
createAccount(credentials) {
return __awaiter(this, void 0, void 0, function* () {
const { email, password } = credentials;
const [error, user] = yield this.app
.auth()
.createUserWithEmailAndPassword(email, password)
.then((user) => [null, user])
.catch((error) => [error, null]);
if (error) {
return Promise.reject(error);
}
return user;
});
}
deleteAccount() {
return __awaiter(this, void 0, void 0, function* () {
const user = this.app.auth().currentUser;
if (!user) {
return Promise.reject(new Error(`You are trying to delete an account that is not authenticated. To delete an account, the account must have signed in recently.`));
}
const [devicesError, devices] = yield this.getDevices()
.then((response) => [null, response])
.catch((error) => [error, null]);
if (devicesError) {
return Promise.reject(devicesError);
}
if (devices.length) {
const removeDeviceError = yield Promise.all(devices.map((device) => this.removeDevice(device.deviceId)))
.then(() => null)
.catch((error) => error);
if (removeDeviceError) {
return Promise.reject(removeDeviceError);
}
}
return user.delete();
});
}
onAuthStateChanged() {
return new rxjs_1.Observable((subscriber) => {
try {
this.app.auth().onAuthStateChanged((user) => {
subscriber.next(user);
}, (error) => {
subscriber.error(error);
});
}
catch (error) {
subscriber.error(error);
}
});
}
onLogin() {
return new rxjs_1.Observable((subscriber) => {
const unsubscribe = this.app
.auth()
.onAuthStateChanged((user) => {
if (!!user) {
subscriber.next(user);
subscriber.complete();
}
});
return () => unsubscribe();
});
}
login(credentials) {
if ("customToken" in credentials) {
const { customToken } = credentials;
return this.app.auth().signInWithCustomToken(customToken);
}
if ("idToken" in credentials && "providerId" in credentials) {
const provider = new app_1.default.auth.OAuthProvider(credentials.providerId);
const oAuthCredential = provider.credential(credentials.idToken);
return this.app.auth().signInWithCredential(oAuthCredential);
}
if ("email" in credentials && "password" in credentials) {
const { email, password } = credentials;
return this.app
.auth()
.signInWithEmailAndPassword(email, password);
}
throw new Error(`Either {email,password}, {customToken}, or {idToken,providerId} is required`);
}
logout() {
return this.app.auth().signOut();
}
createCustomToken() {
return __awaiter(this, void 0, void 0, function* () {
const [error, customToken] = yield this.app
.functions()
.httpsCallable("createCustomToken")()
.then(({ data }) => [null, data])
.catch((error) => [error, null]);
if (error) {
return Promise.reject(error);
}
return customToken;
});
}
removeOAuthAccess() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const userId = (_a = this.user) === null || _a === void 0 ? void 0 : _a.uid;
if (!userId) {
return Promise.reject(`OAuth access can only be removed while logged in via OAuth.`);
}
const [error, response] = yield this.app
.functions()
.httpsCallable("removeAccessOAuthApp")()
.then(({ data }) => [null, data])
.catch((error) => [error, null]);
if (error) {
return Promise.reject(error);
}
const logoutError = yield this.logout()
.then(() => false)
.catch((error) => error);
if (logoutError) {
return Promise.reject(logoutError);
}
return response;
});
}
getDevices() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const userId = (_a = this.user) === null || _a === void 0 ? void 0 : _a.uid;
if (!userId) {
return Promise.reject(`Please login.`);
}
const snapshot = yield this.app
.database()
.ref(this.getUserDevicesPath())
.once("value");
const userDevices = snapshot.val();
return this.userDevicesToDeviceInfoList(userDevices);
});
}
addDevice(deviceId) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const userId = (_a = this.user) === null || _a === void 0 ? void 0 : _a.uid;
if (!userId) {
return Promise.reject(`Please login.`);
}
const devices = yield this.getDevices().catch((error) => {
console.log(error);
});
const deviceAlreadyInAccount = devices &&
devices.length &&
devices.map(({ deviceId }) => deviceId).includes(deviceId);
if (deviceAlreadyInAccount) {
return Promise.reject(`The device is already added to this account.`);
}
const [isValid, invalidErrorMessage] = yield this.isDeviceIdValid(deviceId)
.then((isValid) => [isValid])
.catch((error) => [false, error]);
if (!isValid) {
return Promise.reject(invalidErrorMessage);
}
const claimedByPath = this.getDeviceClaimedByPath(deviceId);
const userDevicePath = this.getUserClaimedDevicePath(deviceId);
const [hasError, errorMessage] = yield this.app
.database()
.ref()
.update({
[claimedByPath]: userId,
[userDevicePath]: {
claimedOn: SERVER_TIMESTAMP
}
})
.then(() => [false])
.catch((error) => [true, error]);
if (hasError) {
return Promise.reject(errorMessage);
}
});
}
removeDevice(deviceId) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const userId = (_a = this.user) === null || _a === void 0 ? void 0 : _a.uid;
if (!userId) {
return Promise.reject(`Please login.`);
}
const claimedByPath = this.getDeviceClaimedByPath(deviceId);
const userDevicePath = this.getUserClaimedDevicePath(deviceId);
const claimedByRef = this.app.database().ref(claimedByPath);
const userDeviceRef = this.app.database().ref(userDevicePath);
const [hasError, errorMessage] = yield Promise.all([
claimedByRef.remove(),
userDeviceRef.remove()
])
.then(() => [false])
.catch((error) => [true, error]);
if (hasError) {
return Promise.reject(errorMessage);
}
});
}
transferDevice(options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const userId = (_a = this.user) === null || _a === void 0 ? void 0 : _a.uid;
if (!userId) {
return Promise.reject(new Error(`transferDevice: auth is required.`));
}
if (!("recipientsEmail" in options) &&
!("recipientsUserId" in options)) {
return Promise.reject(new Error(`transferDevice: either 'recipientsEmail' or 'recipientsUserId' key is required.`));
}
if (!(options === null || options === void 0 ? void 0 : options.deviceId)) {
return Promise.reject(new Error(`transferDevice: a deviceId is required.`));
}
const [error, response] = yield this.app
.functions()
.httpsCallable("transferDeviceOwnership")(options)
.then(({ data }) => [null, data])
.catch((error) => [error, null]);
if (error) {
return Promise.reject(error);
}
});
}
isDeviceIdValid(deviceId) {
return __awaiter(this, void 0, void 0, function* () {
// hex string of 32 characters
const hexRegEx = /[0-9A-Fa-f]{32}/g;
if (!deviceId ||
deviceId.length !== 32 ||
!hexRegEx.test(deviceId)) {
return Promise.reject("The device id is incorrectly formatted.");
}
const claimedByPath = this.getDeviceClaimedByPath(deviceId);
const claimedByRef = this.app.database().ref(claimedByPath);
const claimedBySnapshot = yield claimedByRef
.once("value")
.catch(() => null);
if (!claimedBySnapshot || claimedBySnapshot.exists()) {
return Promise.reject("The device has already been claimed.");
}
return true;
});
}
onUserDevicesChange() {
return this.onAuthStateChanged().pipe((0, operators_1.switchMap)((user) => {
if (!user) {
return rxjs_1.EMPTY;
}
const userDevicesPath = this.getUserDevicesPath();
const userDevicesRef = this.app.database().ref(userDevicesPath);
return (0, rxjs_1.fromEventPattern)((handler) => userDevicesRef.on("value", handler), (handler) => userDevicesRef.off("value", handler)).pipe((0, operators_1.map)(([snapshot]) => snapshot.val()), (0, operators_1.switchMap)((userDevices) => {
return (0, rxjs_1.from)(this.userDevicesToDeviceInfoList(userDevices));
}));
}));
}
onUserClaimsChange() {
return this.onAuthStateChanged().pipe((0, operators_1.switchMap)((user) => {
if (!user) {
return rxjs_1.EMPTY;
}
const claimsUpdatedOnPath = this.getUserClaimsUpdatedOnPath();
const claimsUpdatedOnRef = this.app
.database()
.ref(claimsUpdatedOnPath);
return (0, rxjs_1.fromEventPattern)((handler) => claimsUpdatedOnRef.on("value", handler), (handler) => claimsUpdatedOnRef.off("value", handler)).pipe((0, operators_1.map)(([snapshot]) => snapshot.val()), (0, operators_1.switchMap)(() => {
// Force refresh of auth id token
return (0, rxjs_1.from)(this.getIdToken(true)).pipe((0, operators_1.switchMap)(() => (0, rxjs_1.from)(this.getClaims())));
}));
}));
}
getIdToken(forceRefresh = false) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const user = (_a = this.app.auth()) === null || _a === void 0 ? void 0 : _a.currentUser;
if (!user) {
return Promise.reject(`getUserIdToken: unable to get currentUser`);
}
yield user.getIdToken(forceRefresh).catch((error) => {
console.error(error);
});
});
}
getClaims() {
var _a;
const user = (_a = this.app.auth()) === null || _a === void 0 ? void 0 : _a.currentUser;
if (!user) {
return Promise.reject(`getUserClaims: unable to get currentUser`);
}
return user
.getIdTokenResult()
.then((token) => token.claims)
.catch((error) => {
console.error(error);
return null;
});
}
userDevicesToDeviceInfoList(userDevices) {
return __awaiter(this, void 0, void 0, function* () {
const devicesInfoSnapshots = Object.keys(userDevices !== null && userDevices !== void 0 ? userDevices : {}).map((deviceId) => this.app
.database()
.ref(this.getDeviceInfoPath(deviceId))
.once("value"));
const devicesList = yield Promise.all(devicesInfoSnapshots).then((snapshots) => snapshots.map((snapshot) => snapshot.val()));
const validDevices = devicesList.filter((device) => !!device);
validDevices.sort((a, b) => {
return (userDevices[a.deviceId].claimedOn -
userDevices[b.deviceId].claimedOn);
});
return validDevices;
});
}
hasDevicePermission(deviceId) {
return __awaiter(this, void 0, void 0, function* () {
const deviceInfoPath = this.getDeviceInfoPath(deviceId);
const hasPermission = yield this.app
.database()
.ref(deviceInfoPath)
.once("value")
.then(() => true)
.catch(() => false);
return hasPermission;
});
}
getDeviceClaimedByPath(deviceId) {
return `devices/${deviceId}/status/claimedBy`;
}
getUserClaimedDevicePath(deviceId) {
const userId = this.user.uid;
return `users/${userId}/devices/${deviceId}`;
}
getUserDevicesPath() {
const userId = this.user.uid;
return `users/${userId}/devices`;
}
getUserClaimsUpdatedOnPath() {
const userId = this.user.uid;
return `users/${userId}/claimsUpdatedOn`;
}
getDeviceInfoPath(deviceId) {
return `devices/${deviceId}/info`;
}
onUserExperiments() {
return this.onAuthStateChanged().pipe((0, operators_1.switchMap)((user) => {
if (!user) {
return rxjs_1.EMPTY;
}
const userId = this.user.uid;
const userExperimentsRef = this.app
.database()
.ref("experiments")
.orderByChild("userId")
.equalTo(userId)
.limitToFirst(100);
return (0, rxjs_1.fromEventPattern)((handler) => userExperimentsRef.on("value", handler), (handler) => userExperimentsRef.off("value", handler)).pipe((0, operators_1.map)(([snapshot]) => snapshot.val()),
// transform experiments map into sorted list
(0, operators_1.map)((experimentsMaps) => {
return Object.entries(experimentsMaps !== null && experimentsMaps !== void 0 ? experimentsMaps : {})
.map(([id, value]) => {
var _a;
return (Object.assign({ id: (_a = value === null || value === void 0 ? void 0 : value.id) !== null && _a !== void 0 ? _a : id }, value));
})
.sort((a, b) => new Date(b === null || b === void 0 ? void 0 : b.timestamp).getTime() -
new Date(a === null || a === void 0 ? void 0 : a.timestamp).getTime());
}));
}));
}
deleteUserExperiment(experimentId) {
return __awaiter(this, void 0, void 0, function* () {
if (!experimentId) {
return Promise.reject(`deleteUserExperiment: please provide an experiment id`);
}
const removeExperiment = (experimentId) => {
return this.app
.database()
.ref("experiments")
.child(experimentId)
.remove();
};
const removeRelations = (experimentId) => {
return this.app.functions().httpsCallable("removeRelations")({
experimentId
});
};
yield Promise.all([
removeExperiment(experimentId),
removeRelations(experimentId)
]).catch(() => { });
});
}
}
exports.FirebaseUser = FirebaseUser;