baqend
Version:
Baqend JavaScript SDK
229 lines (189 loc) • 7.17 kB
text/typescript
import { EntityManager } from './EntityManager';
import * as message from './message';
import {
Connector, Message, Response,
} from './connector';
import { JsonMap, Lockable } from './util';
import { TokenStorage, TokenStorageFactory, Code } from './intersection';
import { Metamodel } from './metamodel';
const CONNECTED = Symbol('Connected');
export type ConnectData = {
bloomFilterRefresh?: number,
schema?: JsonMap,
token?: string,
device?: JsonMap,
user?: JsonMap,
bloomFilter?: JsonMap,
};
export class EntityManagerFactory extends Lockable {
public connection: Connector | null = null;
public metamodel: Metamodel = this.createMetamodel();
public code: Code = new Code(this.metamodel, this);
public tokenStorageFactory: TokenStorageFactory = TokenStorage.WEB_STORAGE || TokenStorage.GLOBAL;
public tokenStorage: TokenStorage | null = null;
public staleness: number | null = null;
public connectData: ConnectData | null = null;
private [CONNECTED]?: () => any;
/**
* Creates a new EntityManagerFactory connected to the given destination
* @param [options] The destination to connect with, or an options object
* @param [options.host] The destination to connect with
* @param [options.port=80|443] The optional destination port to connect with
* @param [options.secure=false] <code>true</code> To use a secure ssl encrypted connection
* @param [options.basePath="/v1"] The base path of the api
* @param [options.schema=null] The serialized schema as json used to initialize the metamodel
* @param [options.tokenStorage] The tokenStorage which should be used by this emf
* @param [options.tokenStorageFactory] The tokenStorage factory implementation which
* should be used for token storage
* @param [options.staleness=60] The maximum staleness of objects that are acceptable while reading cached
* data
*/
constructor(options: {
host?: string,
port?: number,
secure?: boolean,
basePath?: string,
schema?: JsonMap,
tokenStorage?: TokenStorage,
tokenStorageFactory?: TokenStorageFactory,
staleness?: number
} | string = {}) {
super();
const opt = typeof options === 'string' ? { host: options } : options || {};
this.configure(opt);
let isReady = true;
let ready = new Promise<void>((success) => {
this[CONNECTED] = success;
});
if (opt.host) {
this.connect(opt.host, opt.port, opt.secure, opt.basePath);
} else {
isReady = false;
}
if (!this.tokenStorage) {
isReady = false;
ready = ready
.then(() => this.tokenStorageFactory.create(this.connection!.origin))
.then((tokenStorage) => {
this.tokenStorage = tokenStorage;
});
}
if (opt.schema) {
this.connectData = opt;
this.metamodel.init(opt.schema);
} else {
isReady = false;
ready = ready.then(() => {
const msg = new message.Connect();
msg.withCredentials = true; // used for registered devices
if (this.staleness === 0) {
msg.noCache();
}
if (this.tokenStorage?.token) {
// disable client cache on the connect resource if we are authenticated
msg.addQueryString('BCB');
}
return this.send(msg);
}).then((response: Response) => {
this.connectData = response.entity as JsonMap;
if (this.staleness === null) {
this.staleness = this.connectData.bloomFilterRefresh || 60;
}
if (!this.metamodel.isInitialized) {
this.metamodel.init(this.connectData.schema);
}
this.tokenStorage!.update(this.connectData.token as string | null);
});
}
if (!isReady) {
this.withLock(() => ready, true);
}
}
/**
* Apply additional configurations to this EntityManagerFactory
* @param options The additional configuration options
* @param [options.tokenStorage] The tokenStorage which should be used by this emf
* @param [options.tokenStorageFactory] The tokenStorage factory implementation which
* should be used for token storage
* @param [options.staleness=60] The maximum staleness of objects that are acceptable while reading cached
* data, <code>0</code> to always bypass the browser cache
* @return
*/
configure(options: { tokenStorage?: TokenStorage, tokenStorageFactory?: TokenStorageFactory,
staleness?: number }): void {
if (this.connection) {
throw new Error('The EntityManagerFactory can only be configured before is is connected.');
}
if (options.tokenStorage) {
this.tokenStorage = options.tokenStorage;
}
if (options.tokenStorageFactory) {
this.tokenStorageFactory = options.tokenStorageFactory;
}
if (options.staleness !== undefined) {
this.staleness = options.staleness;
}
}
/**
* Connects this EntityManager to the given destination
* @param hostOrApp The host or the app name to connect with
* @param [secure=true] <code>true</code> To use a secure connection
* @param [basePath="/v1"] The base path of the api
* @return
*/
connect(hostOrApp: string, secure?: boolean, basePath?: string): Promise<this>;
/**
* Connects this EntityManager to the given destination
* @param hostOrApp The host or the app name to connect with
* @param [port=80|443] The port to connect to
* @param [secure=false] <code>true</code> To use a secure connection
* @param [basePath="/v1"] The base path of the api
* @return
*/
connect(hostOrApp: string, port?: number, secure?: boolean, basePath?: string): Promise<this>;
connect(hostOrApp: string, port?: number | boolean | undefined, secure?: string | boolean | undefined,
basePath?: string | undefined): Promise<this> {
if (this.connection) {
throw new Error('The EntityManagerFactory is already connected.');
}
if (typeof port === 'boolean') {
return this.connect(hostOrApp, 0, port, secure as string);
}
this.connection = Connector.create(hostOrApp, port, secure as boolean, basePath);
this[CONNECTED]!!();
return this.ready();
}
/**
* Creates a new Metamodel instance, which is not connected
* @return A new Metamodel instance
*/
createMetamodel(): Metamodel {
return new Metamodel(this);
}
/**
* Create a new application-managed EntityManager.
*
* @param useSharedTokenStorage The token storage to persist the authorization token, or
* <code>true</code> To use the shared token storage of the emf.
* <code>false</code> To use a instance based storage.
*
* @return a new entityManager
*/
createEntityManager(useSharedTokenStorage?: boolean): EntityManager {
const em = new EntityManager(this);
if (this.isReady) {
em.connected(useSharedTokenStorage);
} else {
em.withLock(() => this.ready().then(() => {
em.connected(useSharedTokenStorage);
}), true);
}
return em;
}
send(msg: Message): Promise<Response> {
if (!msg.tokenStorage()) {
msg.tokenStorage(this.tokenStorage);
}
return this.connection!.send(msg);
}
}