import { App } from './App'; import { logger } from './logger'; import { IObniz, IObnizOptions } from './Obniz.interface'; import { User } from 'obniz-cloud-sdk/sdk'; import { getSdk } from 'obniz-cloud-sdk'; import { DeviceInfo } from './types/device'; import { Slave } from './Slave'; /** * This class is exported from this library * "Abstract" must be drop * Example: https://qiita.com/okdyy75/items/610623943979cf422775#%E3%81%BE%E3%81%82%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E3%81%A9%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%81%AB%E6%9B%B8%E3%81%8F%E3%81%AE */ export class Worker { /** * @deprecated Please use deviceInfo instead. */ public install: DeviceInfo; public deviceInfo: DeviceInfo; protected app: App; protected slave: Slave; protected obniz: O; public state: 'stopped' | 'starting' | 'started' | 'stopping' = 'stopped'; protected readonly _obnizOption: IObnizOptions; public user?: User | null; private _cloudSdk: ReturnType | null; constructor( deviceInfo: DeviceInfo, app: App, slave: Slave, option: IObnizOptions = {} ) { this.install = deviceInfo; this.deviceInfo = deviceInfo; this.app = app; this.slave = slave; this._obnizOption = option; const overrideOptions: IObnizOptions = { auto_connect: false, }; this.obniz = new this.app.obnizClass(this.deviceInfo.id, { ...this._obnizOption, ...overrideOptions, }); this.obniz.onconnect = this.onObnizConnect.bind(this); this.obniz.onloop = this.onObnizLoop.bind(this); this.obniz.onclose = this.onObnizClose.bind(this); this.user = this.deviceInfo.user; this._cloudSdk = this._obnizOption.access_token ? getSdk(this._obnizOption.access_token, app._options.obnizCloudSdkOption) : null; } /** * Worker lifecycle */ /** * Called When newaly Installed * This will be called before onStart after instantiated. * Introduces from v1.4.0 */ async onInstall(): Promise {} /** * Called When Uninstalled * This will be called before onEnd() * Introduces from v1.4.0 */ async onUnInstall(): Promise {} /** * Worker lifecycle */ async onStart(): Promise {} /** * This funcion will be called rrepeatedly while App is started. */ async onLoop(): Promise {} async onEnd(): Promise {} /** * * @param key string key that represents what types of reqeust. * @returns string for requested key */ async onRequest(key: string): Promise { return ''; } /** * obniz lifecycle */ async onObnizConnect(obniz: O): Promise {} async onObnizLoop(obniz: O): Promise {} async onObnizClose(obniz: O): Promise {} /** * Start Application by recofnizing Install/Update * @param onInstall if start reason is new install then true; */ async start(onInstall = false): Promise { if (this.state !== 'stopped') { throw new Error(`invalid state`); } this.state = 'starting'; if (onInstall) { await this.onInstall(); } await this.onStart(); this.state = 'started'; this.obniz.autoConnect = true; this.obniz.connect(); // in background // noinspection ES6MissingAwait this._loop(); } private async _loop(): Promise { while (this.state === 'starting' || this.state === 'started') { try { await this.onLoop(); } catch (e) { logger.error(e); } await new Promise((resolve) => { setTimeout(resolve, 1000); }); } } async restart(): Promise { await this.slave.restartWorker(this.deviceInfo.id); } /** * Send a request to all other Workers in this App (across every Slave * instance) and collect their `onRequest` responses keyed by obnizId. * * This is the worker-to-worker counterpart of `App.request()`. * The requesting Worker's own Slave also handles the request, so the * returned object includes sibling Workers on the same instance. * * @param key string payload passed to `onRequest` on each Worker. * @param timeout time in milliseconds to wait for responses. Default 30s. * @returns `{ [obnizId]: response }` collected across Slaves. */ public async request( key: string, timeout = 30 * 1000 ): Promise<{ [key: string]: string }> { return await this.slave.workerRequest(key, timeout); } /** * Send a request directly to a single Worker identified by obnizId and * receive its `onRequest` response. Rejects with ObnizAppTimeoutError if * no Worker responds within `timeout`. * * @param obnizId target Worker's obniz id. * @param key string payload passed to `onRequest`. * @param timeout time in milliseconds to wait for a response. Default 30s. */ public async directRequest( obnizId: string, key: string, timeout = 30 * 1000 ): Promise<{ [key: string]: string }> { return await this.slave.workerDirectRequest(obnizId, key, timeout); } async stop(): Promise { if (this.state === 'starting' || this.state === 'started') { this.state = 'stopping'; if (this.obniz) { try { await this.obniz.closeWait(); } catch (e) { console.error(e); // handle close caused error. and promise onEnd() called } } await this.onEnd(); this.state = 'stopped'; } } protected async statusUpdateWait(status: 'success' | 'error', text: string) { if (!this._cloudSdk) { return; } await this._cloudSdk.createAppStatus({ input: { obniz: { id: this.obniz.id, }, result: { status, text, }, }, }); } protected addLogQueue(level: 'info' | 'error', message: string) { if (!this._cloudSdk) { return; } message = '' + message; this._cloudSdk .createAppLog({ input: { obniz: { id: this.obniz.id, }, app: { logJson: JSON.stringify({ message }), level, }, }, }) .catch((e) => { console.warn(`failed to send log ${message}`); }); } cloudLog = { info: (message: string) => { this.addLogQueue('info', message); }, error: (message: string) => { this.addLogQueue('error', message); }, }; } export type WorkerStatic = new ( deviceInfo: DeviceInfo, app: App, slave: Slave, option: IObnizOptions ) => Worker;