UNPKG

@neurosity/sdk

Version:
435 lines (434 loc) 18.1 kB
"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;