realm-object-server
Version:
252 lines • 13.5 kB
JavaScript
;
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