UNPKG

c8osdkjscore

Version:
721 lines (632 loc) 28.9 kB
import {C8oBase} from "./c8oBase"; import {C8oHttpInterfaceCore} from "./c8oHttpInterfaceCore"; import {C8oLogger} from "./c8oLogger"; import {C8oLogLevel} from "./c8oLogLevel"; import {C8oSettings} from "./c8oSettings"; import {C8oUtilsCore} from "./c8oUtilsCore"; import {C8oFullSync, C8oFullSyncCbl} from "./c8oFullSync"; import {C8oResponseJsonListener, C8oResponseListener} from "./c8oResponse"; import {C8oExceptionMessage} from "./Exception/c8oExceptionMessage"; import {C8oCallTask} from "./c8oCallTask"; import {C8oFullSyncChangeListener} from "./c8oFullSyncChangeListener"; import {C8oPromise} from "./c8oPromise"; import {C8oCouchBaseLiteException} from "./Exception/c8oCouchBaseLiteException"; import {C8oException} from "./Exception/c8oException"; import {C8oExceptionListener} from "./Exception/c8oExceptionListener"; declare var require: any; /** * Allows to send requests to a Convertigo Server (or Studio), these requests are called c8o calls.<br/> * C8o calls are done thanks to a HTTP request or a CouchbaseLite usage.<br/> * An instance of C8o is connected to only one Convertigo and can't change it.<br/> * To use it, you have to first initialize the C8o instance with the Convertigo endpoint, then use call methods with Convertigo variables as parameter. */ export abstract class C8oCore extends C8oBase { // Log: // - VERBOSE (v): methods parameters, // - DEBUG (d): methods calls, // - INFO (i): // - WARN (w): // - ERROR (e): /*** Regular expression ***/ /** * The regex used to handle the c8o requestable syntax ("&lt;project&gt;.&lt;sequence&gt;" or "&lt;project&gt;.&lt;connector&gt;.&lt;transaction&gt;") */ protected static RE_REQUESTABLE = /^([^.]*)\.(?:([^.]+)|(?:([^.]+)\.([^.]+)))$/; /** * The regex used to get the part of the endpoint before '/projects/...' */ protected static RE_ENDPOINT = /^(https?:\/\/([^:/]+)(:[0-9]+)?\/?.*?)\/projects\/([^\/]+)$/; /** * Engine reserved parameters */ public static ENGINE_PARAMETER_PROJECT: string = "__project"; public static ENGINE_PARAMETER_SEQUENCE: string = "__sequence"; public static ENGINE_PARAMETER_CONNECTOR: string = "__connector"; public static ENGINE_PARAMETER_TRANSACTION: string = "__transaction"; public static ENGINE_PARAMETER_ENCODED: string = "__encoded"; public static ENGINE_PARAMETER_DEVICE_UUID: string = "__uuid"; public static ENGINE_PARAMETER_PROGRESS: string = "__progress"; public static ENGINE_PARAMETER_FROM_LIVE: string = "__fromLive"; /** * FULLSYNC parameters */ /** * Constant to use as a parameter for a Call of "fs://.post" and must be followed by a FS_POLICY_* constant. * <pre>{@code * c8o.callJson("fs://.post", * C8o.FS_POLICY, C8o.FS_POLICY_MERGE, * "docid", myid, * "mykey", myvalue * ).sync(); * }</pre> */ public static FS_POLICY: string = "_use_policy"; /** Use it with "fs://.post" and C8o.FS_POLICY. This is the default post policy that don't alter the document before the CouchbaseLite's insertion. */ public static FS_POLICY_NONE: string = "none"; /** Use it with "fs://.post" and C8o.FS_POLICY. This post policy remove the "_id" and "_rev" of the document before the CouchbaseLite's insertion. */ public static FS_POLICY_CREATE: string = "create"; /** Use it with "fs://.post" and C8o.FS_POLICY. This post policy inserts the document in CouchbaseLite even if a document with the same "_id" already exists. */ public static FS_POLICY_OVERRIDE: string = "override"; /** Use it with "fs://.post" and C8o.FS_POLICY. This post policy merge the document with an existing document with the same "_id" before the CouchbaseLite's insertion. */ public static FS_POLICY_MERGE: string = "merge"; /** Use it with "fs://.post". Default value is ".". This key allow to override the sub key separator in case of document depth modification. */ public static FS_SUBKEY_SEPARATOR: string = "_use_subkey_separator"; /** * Use it with "fs://" request as parameter to enable the live request feature.<br/> * Must be followed by a string parameter, the 'liveid' that can be use to cancel the live * request using c8o.cancelLive(liveid) method.<br/> * A live request automatically recall the then or thenUI handler when the database changed. */ public static FS_LIVE: string = "__live"; /** Local cache keys **/ public static LOCAL_CACHE_DOCUMENT_KEY_RESPONSE: string = "response"; public static LOCAL_CACHE_DOCUMENT_KEY_RESPONSE_TYPE: string = "responseType"; public static LOCAL_CACHE_DOCUMENT_KEY_EXPIRATION_DATE: string = "expirationDate"; public static LOCAL_CACHE_DATABASE_NAME: string = "c8olocalcache"; /** Response type **/ public static RESPONSE_TYPE_XML: string = "pxml"; public static RESPONSE_TYPE_JSON: string = "json"; /* Static configuration */ public static deviceUUID: string = C8oUtilsCore.getNewGUIDString(); /** Network **/ /** * The Convertigo endpoint, syntax: &lt;protocol&gt;://&lt;host&gt;:&lt;port&gt;/&lt;Convertigo web app path&gt;/projects/&lt;project name&gt; (Example: http://127.0.0.1:18080/convertigo/projects/MyProject) */ protected _endpoint: string; protected _endpointConvertigo: string; protected _endpointIsSecure: boolean; protected _endpointHost: string; protected _endpointPort: string; protected _endpointProject: string; protected _automaticRemoveSplashsCreen: boolean = true; /** * Used to run HTTP requests. */ public httpInterface: C8oHttpInterfaceCore; /** * Allows to log locally and remotely to the Convertigo server. */ public c8oLogger: C8oLogger; /** * Used to run fullSync requests. */ public c8oFullSync: C8oFullSync; public lives: C8oCallTask[] = []; public livesDb: string[] = []; protected data: any; protected _http: any; protected _couchUrl: string = null; protected promiseConstructor: Promise<any>; protected promiseInit: Promise<any>; protected promiseFinInit: Promise<any>; public get couchUrl(): string { return this._couchUrl; } public set couchUrl(value: string) { this._couchUrl = value; } public get logC8o(): boolean { return this._logC8o; } public set logC8o(value: boolean) { this._logC8o = value; } public get logRemote(): boolean { return this._logRemote; } public set logRemote(value: boolean) { this._logRemote = value; } public get logLevelLocal(): C8oLogLevel { return this._logLevelLocal; } public set logLevelLocal(value: C8oLogLevel) { this._logLevelLocal = value; } public get log(): C8oLogger { return this.c8oLogger; } public toString(): string { return "C8o[" + this._endpoint + "]"; } public get endpoint(): string { return this._endpoint; } public set endpoint(value: string) { this._endpoint = value; } public get endpointConvertigo(): string { return this._endpointConvertigo; } public set endpointConvertigo(value: string) { this._endpointConvertigo = value; } //noinspection JSUnusedGlobalSymbols public get endpointIsSecure(): boolean { return this._endpointIsSecure; } public set endpointIsSecure(value: boolean) { this._endpointIsSecure = value; } //noinspection JSUnusedGlobalSymbols public get endpointHost(): string { return this._endpointHost; } public set endpointHost(value: string) { this._endpointHost = value; } //noinspection JSUnusedGlobalSymbols public get endpointPort(): string { return this._endpointPort; } public set endpointPort(value: string) { this._endpointPort = value; } public get endpointProject(): string { return this._endpointProject; } public set endpointProject(value: string) { this._endpointProject = value; } public get deviceUUID(): string { return C8oCore.deviceUUID; } public get httpPublic(): any { return this._http; } public abstract get sdkVersion(): string; public get coreVersion(): string { return require("../../package.json").version; } /** * This is the base object representing a Convertigo Server end point. This object should be instantiated * when the apps starts and be accessible from any class of the app. Although this is not common , you may have * several C8o objects instantiated in your app. * * @param http * * @throws C8oException In case of invalid parameter or initialization failure. */ constructor() { super(); this.data = null; this.c8oLogger = new C8oLogger(this, true); } /** * This is the base object representing a Convertigo Server end point. This object should be instantiated * when the apps starts and be accessible from any class of the app. Although this is not common , you may have * several C8o objects instantiated in your app. * * @param c8oSettings Initialization options.<br/> * Example: new C8oSettings().setLogRemote(false).setDefaultDatabaseName("sample") * * @throws C8oException In case of invalid parameter or initialization failure. */ public abstract init(c8oSettings?: C8oSettings): Promise<any>; /** * This should be called OnPlatform Ready to remove splashscreen if necessary * */ public finalizeInit(): Promise<any> { this.promiseFinInit = new Promise((resolve) => { Promise.all([this.promiseInit]).then(() => { /** * Looking for splashScreen timeOut */ if (this._automaticRemoveSplashsCreen) { if (navigator["splashscreen"] !== undefined) { navigator["splashscreen"].hide(); } } /** * Looking for cblite */ if (window["cblite"] != undefined) { window["cblite"].getURL((err, url) => { if (err) { resolve(); } else { url = url.replace(new RegExp("/$"), ""); this.couchUrl = url; resolve(); } }); } else { resolve(); } }); }); return this.promiseFinInit; } protected extractendpoint() { if (!C8oUtilsCore.isValidUrl(this.endpoint)) { throw new C8oException(C8oExceptionMessage.illegalArgumentInvalidURL(this.endpoint).toString()); } const matches = C8oCore.RE_ENDPOINT.exec(this.endpoint.toString()); if (matches === null) { throw new C8oException(C8oExceptionMessage.illegalArgumentInvalidEndpoint(this.endpoint.toString())); } this.endpointConvertigo = matches[0].substring(0, (matches[0].indexOf("/projects"))); this.endpointIsSecure = matches[1] != null; this.endpointHost = matches[2]; this.endpointPort = matches[3]; this.endpointProject = matches[4]; } /** * Makes a c8o call with c8o requestable out of parameters.<br/> * To not use a C8oExceptionListener you can set the parameter to null * * @param requestable - Contains the Convertigo Sequence or Transaction targeted (Syntax: "<project>.<sequence>" or "<project>.<connector>.<transaction>") * @param parameters - Contains c8o variables * @param c8oResponseListener - Define the behavior with the c8o call response * @param c8oExceptionListener - Define the behavior when there is an exception during execution */ public call(requestable: string, parameters: Object = null, c8oResponseListener: C8oResponseListener = null, c8oExceptionListener: C8oExceptionListener = null) { try { if (requestable === null || requestable === undefined) { //noinspection ExceptionCaughtLocallyJS throw new C8oException(C8oExceptionMessage.illegalArgumentNullParameter("resquestable")); } if (parameters === null || parameters === undefined) { parameters = {}; } else { //parameters = (JSON.parse(JSON.stringify(parameters))); } const regex = C8oCore.RE_REQUESTABLE.exec(requestable); if (regex === null || regex === undefined) { //noinspection ExceptionCaughtLocallyJS throw new C8oException(C8oExceptionMessage.InvalidArgumentInvalidEndpoint(this._endpoint)); } if (regex[1] !== "") { parameters[C8oCore.ENGINE_PARAMETER_PROJECT.toString()] = regex[1]; } if (regex[2] != null) { parameters[C8oCore.ENGINE_PARAMETER_SEQUENCE.toString()] = regex[2]; } else { parameters[C8oCore.ENGINE_PARAMETER_CONNECTOR.toString()] = regex[3]; parameters[C8oCore.ENGINE_PARAMETER_TRANSACTION.toString()] = regex[4]; } return this._call(parameters, c8oResponseListener, c8oExceptionListener); } catch (error) { this.handleCallException(c8oExceptionListener, parameters, error); } finally { } } /** * Makes a c8o call with c8o requestable in parameters ('__project' and ('__sequence' or ('__connector' and '__transaction'))).<br/> * To not use a C8oExceptionListener you can set the parameter to null. * * @param parameters - Contains c8o variables * @param c8oResponseListener - Define the behavior with the c8o call response * @param c8oExceptionListener - Define the behavior when there is an exception during execution */ public _call(parameters: Object = null, c8oResponseListener: C8oResponseListener = null, c8oExceptionListener: C8oExceptionListener = null) { // IMPORTANT: all c8o calls have to end here ! Promise.all([this.promiseFinInit]).then(() => { try { this.c8oLogger.logMethodCall("call", parameters, c8oResponseListener, c8oExceptionListener); if (parameters == null) { parameters = {}; } else { //parameters = (JSON.parse(JSON.stringify(parameters))); } const task: C8oCallTask = new C8oCallTask(this, parameters, c8oResponseListener, c8oExceptionListener); task.run(); } catch (error) { this.handleCallException(c8oExceptionListener, parameters, error); } finally { } }); } /** * Makes a c8o call with c8o requestable out of parameters, expecting a JSON response through a C8oPromise.<br/> * The C8oPromise allow to register response handler with .then and .thenUI, * error handler with .fail and failUI, * replication handler with .progress * and synchronous response with .sync(). * * @param requestable - Contains the Convertigo Sequence or Transaction targeted (Syntax: "<project>.<sequence>" or "<project>.<connector>.<transaction>") * @param parameters: Object - Contains c8o variables as key/value pair in the Map * @return A C8oPromise that can deliver the JSON response */ public callJsonObject(requestable: string, parameters: Object): C8oPromise<JSON> { const promise: C8oPromise<JSON> = new C8oPromise<JSON>(this); this.call(requestable, parameters, new C8oResponseJsonListener((response: any, requestParameters: Object) => { if(requestParameters == null){ requestParameters = {}; } if (response == null && requestParameters[C8oCore.ENGINE_PARAMETER_PROGRESS]) { promise.onProgress(requestParameters[C8oCore.ENGINE_PARAMETER_PROGRESS]); } else { promise.onResponse(response, requestParameters); } }), new C8oExceptionListener((exception: C8oException, data: Object) => { promise.onFailure(exception, data); })); return promise; } /** * Makes a c8o call with c8o requestable out of parameters, expecting a JSON response through a C8oPromise.<br/> * The C8oPromise allow to register response handler with .then and .thenUI, * error handler with .fail and failUI, * replication handler with .progress * and synchronous response with .sync(). * * @param requestable - Contains the Convertigo Sequence or Transaction targeted (Syntax: "<project>.<sequence>" or "<project>.<connector>.<transaction>") * @param parameters - Contains c8o variables as key/value * @return A C8oPromise that can deliver the JSON response */ public callJson(requestable: string, ...parameters: any[]): C8oPromise<JSON> { return this.callJsonObject(requestable, C8oCore.toParameters(parameters)); } /** * Transforms siblings values as key/value of a Map. * * @param parameters pair of values to transform a object * @return a Map that contains all parameters */ public static toParameters(parameters: any): Object { const newParameters: Object = {}; if (0 !== parameters.length % 2) { throw new C8oException("Incorrect number of parameters"); } for (let i = 0; i < parameters.length; i += 2) { newParameters[parameters[i]] = parameters[i + 1]; } return newParameters; } /** * Calls the exception listener callback if it is not null, else prints the exception stack trace. * * @param c8oExceptionListener * @param requestParameters * @param exception */ public handleCallException(c8oExceptionListener: C8oExceptionListener, requestParameters: Object, exception: Error) { this.c8oLogger.warn("Handle a call exception", exception); if (c8oExceptionListener != null) { c8oExceptionListener.onException(exception, requestParameters); } } /** * get an attachment for a given object * * @param id: string * @param attachment_name: string * * @returns a promise containing a buffer */ public get_attachment(id: string, attachment_name: string, database_name?: string): Promise<any> { return new Promise((resolve, reject) => { if (database_name == null) { database_name = this.defaultDatabaseName; } if ((this.c8oFullSync as C8oFullSyncCbl) != undefined) { const fullsyncdb = (this.c8oFullSync as C8oFullSyncCbl).getOrCreateFullSyncDatabase(database_name); fullsyncdb.getdatabase.getAttachment(id, attachment_name).then((buffer) => { resolve(buffer); }).catch((err) => { reject(err); }); } }); } /** * Add a listener to monitor all changes of the 'db'. * * @param db the name of the fullsync database to monitor. Use the default database for a blank or a null value. * @param listener the listener to trigger on change. */ public addFullSyncChangeListener(db: string, listener: C8oFullSyncChangeListener) { (this.c8oFullSync as C8oFullSyncCbl).addFullSyncChangeListener(db, listener); } /** * Remove a listener for changes of the 'db'. * * @param db the name of the fullsync database to monitor. Use the default database for a blank or a null value. * @param listener the listener instance to remove. */ public removeFullSyncChangeListener(db: string, listener: C8oFullSyncChangeListener) { (this.c8oFullSync as C8oFullSyncCbl).removeFullSyncChangeListener(db, listener); } public addLive(liveid: string, db: string, task: C8oCallTask) { this.cancelLive(liveid); this.lives[liveid] = task; this.livesDb[liveid] = db; this.addFullSyncChangeListener(db, this.handleFullSyncLive); } public cancelLive(liveid: string) { if (this.livesDb[liveid] !== undefined) { let db: string = this.livesDb[liveid]; delete this.livesDb[liveid]; if (this.livesDb[db] !== undefined) { db = null; } if (db !== null) { this.removeFullSyncChangeListener(db, this.handleFullSyncLive); } } delete this.lives[liveid]; } //noinspection JSUnusedLocalSymbols protected handleFullSyncLive: C8oFullSyncChangeListener = new C8oFullSyncChangeListener( (changes: Object) => { for (const task in this.lives) { (this.lives[task] as C8oCallTask).executeFromLive(); } }); } export class FullSyncPolicy { public static NONE: FullSyncPolicy = new FullSyncPolicy(C8oCore.FS_POLICY_NONE, (database: any, newProperties: Object) => { let documentId = C8oUtilsCore.getParameterStringValue(newProperties, C8oFullSync.FULL_SYNC__ID, false); if (documentId === "") { documentId = null; } return new Promise((resolve, reject) => { database.post(newProperties).then((createdDocument) => { resolve(createdDocument); }).catch((error) => { reject(new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error)); }); }); }); public static CREATE: FullSyncPolicy = new FullSyncPolicy(C8oCore.FS_POLICY_CREATE, (database: any, newProperties: Object) => { try { delete newProperties[C8oFullSync.FULL_SYNC__ID]; delete newProperties[C8oFullSync.FULL_SYNC__REV]; return new Promise((resolve) => { database.post(newProperties).then((createdDocument) => { resolve(createdDocument); }); }); } catch (error) { throw new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error); } }); public static OVERRIDE: FullSyncPolicy = new FullSyncPolicy(C8oCore.FS_POLICY_OVERRIDE, (database: any, newProperties: Object) => { try { const documentId: string = C8oUtilsCore.getParameterStringValue(newProperties, C8oFullSync.FULL_SYNC__ID, false); delete newProperties[C8oFullSync.FULL_SYNC__ID]; delete newProperties[C8oFullSync.FULL_SYNC__REV]; if (documentId == null) { return new Promise((resolve) => { database.post(newProperties).then((createdDocument) => { resolve(createdDocument); }); }); } else { return new Promise((resolve, reject) => { database.get(documentId).then((doc) => { newProperties["_id"] = documentId; newProperties["_rev"] = doc._rev; return database.put(newProperties); }).then((createdDocument) => { resolve(createdDocument); }).catch((error) => { if (error.status === "404" || error.status === 404) { newProperties["_id"] = documentId; return database.post(newProperties); } else { reject(error); } }, ).then((createdDocument) => { resolve(createdDocument); }); }); } } catch (error) { throw new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error); } }); public static MERGE: FullSyncPolicy = new FullSyncPolicy(C8oCore.FS_POLICY_MERGE, (database: any, newProperties: Object) => { try { const documentId: string = C8oUtilsCore.getParameterStringValue(newProperties, C8oFullSync.FULL_SYNC__ID, false); // delete newProperties[C8oFullSync.FULL_SYNC__ID]; delete newProperties[C8oFullSync.FULL_SYNC__REV]; if (documentId == null) { return new Promise((resolve, reject) => { database.put(newProperties).then((createdDocument) => { resolve(createdDocument); }).catch((error) => { reject(new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error)); }); }); } else { return new Promise((resolve, reject) => { database.get(documentId).then((doc) => { C8oFullSyncCbl.mergeProperties(newProperties, doc); database.put(newProperties).then((createdDocument) => { resolve(createdDocument); }) .catch((error) => { reject(new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error)); }); }).catch((error) => { if (error.status === 404) { database.put(newProperties).then((createdDocument) => { resolve(createdDocument); }) .catch((error) => { reject(new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error)); }); } else { reject(new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error)); } }); }); } } catch (error) { throw new C8oCouchBaseLiteException(C8oExceptionMessage.fullSyncPutProperties(newProperties), error); } }); public value: string; public action: (PouchDB, Object) => any; constructor(value: string, action: (_Object, Object) => any) { this.value = value; this.action = action; } public static values(): FullSyncPolicy[] { return [this.NONE, this.CREATE, this.OVERRIDE, this.MERGE]; } public static getFullSyncPolicy(value: string): FullSyncPolicy { if (value != null) { const fullSyncPolicyValues: FullSyncPolicy[] = FullSyncPolicy.values(); for (const fullSyncPolicy of fullSyncPolicyValues) { if (fullSyncPolicy.value === value) { return fullSyncPolicy as FullSyncPolicy; } } } return this.NONE; } } export class FullSyncPostDocumentParameter { public static POLICY: FullSyncPostDocumentParameter = new FullSyncPostDocumentParameter(C8oCore.FS_POLICY); public static SUBKEY_SEPARATOR: FullSyncPostDocumentParameter = new FullSyncPostDocumentParameter(C8oCore.FS_SUBKEY_SEPARATOR); public name: string; constructor(name: string) { this.name = name; } public static values(): FullSyncPostDocumentParameter[] { const array: FullSyncPostDocumentParameter[] = []; array.push(this.POLICY, this.SUBKEY_SEPARATOR); return array; } }