UNPKG

realm-object-server

Version:

Realm Object Server

252 lines 13.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const realmUtil_1 = require("./shared/realmUtil"); const path = require("path"); const URI = require("urijs"); const util_1 = require("./shared/util"); const RealmType_1 = require("./realms/RealmType"); function createRealmSchema(klass) { const myClass = function () { }; myClass.prototype = Object.create(klass.prototype, { constructor: { configurable: true, enumerable: false, value: myClass, writable: true, }, }); Object.setPrototypeOf(myClass, klass); const schema = klass.schema; Object.defineProperty(myClass, "schema", { get() { return schema; }, }); return myClass; } exports.createRealmSchema = createRealmSchema; class RealmPromise extends Promise { constructor(executor) { super(executor); this.openCount = 0; this.forceCloseHandlers = {}; } } class RealmFactory { constructor(params) { this.syncedRealmPromises = {}; this.discovery = params.discovery; this.adminToken = params.adminToken; this.dataPath = params.dataPath; this.logger = params.logger; this.realmDirectoryClient = params.realmDirectoryClient; this.ssl = params.ssl; this.realmsEncryptionKey = params.realmsEncryptionKey; } open(definition) { return __awaiter(this, void 0, void 0, function* () { let promise = this.syncedRealmPromises[definition.remotePath]; if (promise && promise.error) { if (typeof promise.forceClose === "function") { promise.forceClose(promise.error.message); } else { delete this.syncedRealmPromises[definition.remotePath]; } promise = undefined; } if (!promise) { promise = new RealmPromise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const rejectPromise = (err) => { promise.error = err; reject(err); }; const resolvePromise = (realm) => { promise.isResolved = true; resolve(realm); }; try { const partialInfo = util_1.extractPartialInfo(definition.remotePath); const syncLabel = yield this.findSyncLabel(definition, partialInfo.isPartial ? RealmType_1.RealmType.partial : RealmType_1.RealmType.full); const labelTag = `label=${syncLabel}`; const handle = yield this.discovery.waitForService("sync", ["role=master", labelTag]); this.logger.debug(`Opening realm at ${definition.remotePath}: ${handle.address}:${handle.port}`); const syncServiceWatcher = this.discovery.watchService("sync", ["role=master", labelTag]); const trimRegex = /^\/+/g; const localPath = definition.localPath || `${encodeURIComponent(definition.remotePath.replace(trimRegex, ""))}.realm`; const uri = new URI().host(handle.address) .port(handle.port.toString()) .segment(partialInfo.canonicalPath); const adminCredentials = realmUtil_1.Realm.Sync.Credentials.adminToken(this.adminToken); const config = { schema: definition.schema, path: path.join(this.dataPath, "realms", localPath), encryptionKey: this.realmsEncryptionKey, sync: { url: uri.scheme(this.ssl ? "realms" : "realm").toString(), user: definition.user || realmUtil_1.Realm.Sync.User.login(uri.scheme(this.ssl ? "https" : "http").origin(), adminCredentials), fullSynchronization: !partialInfo.isPartial, clientResyncMode: "discard", error: (session, err) => { const closeNotification = promise.openCount === 0 ? " - Realm was already closed" : ""; const message = `Unable to connect to a Realm (path: ${definition.remotePath})${closeNotification}. ` + `Message: ${err.message}, category: ${err.category}, code: ${err.code}, name: ${err.name}.`; if (err.name === "ClientReset") { this.logger.warn(`A client reset error has occurred for Realm at path: ${definition.remotePath}`); this.forceCloseRealm(promise, "Force closing Realm due to client reset.", true) .catch((error) => { this.logger.error(`Failed to close Realm at path '${partialInfo.canonicalPath}'`, { error }); }); } else if (err.isFatal) { this.logger.error(message); rejectPromise(err); } else { const clarification = "This is likely a transient connectivity error and the server will recover automatically."; this.logger.detail(`${message} ${clarification}`); } }, customQueryBasedSyncIdentifier: partialInfo.customIdentifier, }, }; if (this.ssl) { let validate = this.ssl.agent.options.rejectUnauthorized; if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0") { validate = false; } config.sync.ssl = { certificatePath: this.ssl.httpsCertificatePath, validate }; } if (typeof RealmFactory.sessionStopPolicy !== "undefined") { config.sync._sessionStopPolicy = RealmFactory.sessionStopPolicy; } const realm = yield realmUtil_1.Realm.open(config).progress((a, b) => { this.logger.debug(`Open progress for realm at ${definition.remotePath}: ${a}/${b}`); }); this.logger.detail(`Opened realm at ${definition.remotePath}`); const oldClose = realm.close.bind(realm); promise.config = config; promise.remotePath = definition.remotePath; promise.forceClose = (message, isToBeDeleted) => { try { syncServiceWatcher.removeListener("available", serviceAvailableListener); oldClose(); for (const forceCloseHandler of util_1.enumerateObject(promise.forceCloseHandlers)) { forceCloseHandler(isToBeDeleted || false); } } finally { delete this.syncedRealmPromises[definition.remotePath]; } this.logger.detail(message); }; realm.open = () => { promise.openCount++; }; realm.close = () => { promise.openCount--; if (promise.openCount === 0 && !realm.isClosed) { promise.forceClose(`Closed realm at ${definition.remotePath}`); } }; const serviceAvailableListener = (handle) => { this.logger.detail(`Reconfiguring realm at ${definition.remotePath}: ${handle.address}:${handle.port}`); const syncSession = realm.syncSession; if (syncSession) { syncSession._overrideServer(handle.address, handle.port); } else { this.logger.error(`Reconfiguring realm at ${definition.remotePath} failed. Sync Session is not available.`); } }; syncServiceWatcher.addListener("available", serviceAvailableListener); resolvePromise(realm); } catch (err) { rejectPromise(err); } })); this.syncedRealmPromises[definition.remotePath] = promise; } promise.openCount++; if (definition.forceCloseHandler && !promise.forceCloseHandlers[definition.forceCloseHandler.id]) { promise.forceCloseHandlers[definition.forceCloseHandler.id] = definition.forceCloseHandler.handler; } return promise; }); } close() { return __awaiter(this, void 0, void 0, function* () { yield util_1.waitAsync(() => util_1.enumerateObject(this.syncedRealmPromises).filter(p => p.openCount > 0), a => a.length === 0, 5000, false); const openRealmPaths = util_1.enumerateObject(this.syncedRealmPromises) .filter(p => p.openCount > 0) .map(p => `'${p.remotePath}'`) .join(", "); if (openRealmPaths) { this.logger.error(`RealmFactory was closed with open Realms: ${openRealmPaths}, remember to close these Realms`); } const forceClosePromises = util_1.enumerateObject(this.syncedRealmPromises).map((promise) => __awaiter(this, void 0, void 0, function* () { try { const realm = yield util_1.timeout(promise, 1000); if (!realm.isClosed && typeof promise.forceClose === "function") { promise.forceClose(`Closing Realm at path '${promise.remotePath}' because server is shutting down.`); } } catch (_a) { } })); yield Promise.all(forceClosePromises); this.syncedRealmPromises = {}; }); } forceCloseRealm(pathOrPromise, message, deleteAfterClose = false) { return __awaiter(this, void 0, void 0, function* () { const promise = typeof pathOrPromise === "string" ? this.syncedRealmPromises[pathOrPromise] : pathOrPromise; if (promise) { yield promise; promise.forceClose(message, deleteAfterClose); if (deleteAfterClose) { yield util_1.delay(500); this.logger.detail(`Deleting RealmFactory file. Remote ${promise.remotePath}. Local: ${promise.config.path}`); realmUtil_1.Realm.deleteFile(promise.config); yield util_1.delay(100); } } }); } findSyncLabel(definition, realmType) { return __awaiter(this, void 0, void 0, function* () { let syncLabel = definition.syncLabel; while (!syncLabel) { try { const shouldCreate = true; const response = yield this.realmDirectoryClient.findByPath({ realmPath: definition.remotePath, shouldCreate: shouldCreate, realmType, token: definition.user ? definition.user.token : undefined, }); syncLabel = response.syncLabel; this.logger.debug(`Obtained sync label for realm at ${definition.remotePath}: ${syncLabel}`); } catch (err) { if (err.status !== 503) { throw err; } } } return syncLabel; }); } } exports.RealmFactory = RealmFactory; //# sourceMappingURL=RealmFactory.js.map