@iotize/ionic
Version:
Iotize specific building blocks on top of @ionic/angular.
1,271 lines (1,256 loc) • 294 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Pipe, EventEmitter, Component, Output, Input, ViewChild, Directive, NgModule, InjectionToken, Inject, HostListener, ContentChild } from '@angular/core';
import * as i2 from '@ionic/angular';
import { IonicModule } from '@ionic/angular';
import { isCodeError } from '@iotize/common/error';
import { NfcError, parseTapNdefMessage } from '@iotize/device-com-nfc.cordova';
import { AppPathType, TapResponse, Tap, TapResponseStatusError, HostProtocol, TapError, PathParameter } from '@iotize/tap';
import { _TAP_EXTENSION_AUTH_, INITIAL_SESSION_STATE } from '@iotize/tap/auth';
import { ResultCode, TAP_REQUEST_FRAME_HEADER_LENGTH, TapRequestFrame } from '@iotize/tap/client/api';
import { TapRequestHelper, TapClientError, ResultCodeTranslation } from '@iotize/tap/client/impl';
import { _TAP_EXTENSION_DATA_ } from '@iotize/tap/ext/data';
import { _TAP_EXTENSION_DATA_LOG_ } from '@iotize/tap/ext/data-log';
import { factoryReset } from '@iotize/tap/ext/factory-reset';
import { _TAP_EXTENSION_KEEP_ALIVE_ } from '@iotize/tap/ext/keep-alive';
import { ConnectionState as ConnectionState$1, ComProtocol } from '@iotize/tap/protocol/api';
import { _TAP_SERVICE_ALL_EXTENSIONS_ } from '@iotize/tap/service/all';
import { Observable, BehaviorSubject, Subject, of, merge, fromEvent, NEVER, ReplaySubject, defer, pipe, throwError } from 'rxjs';
import { switchMap, distinctUntilChanged, skip, filter, startWith, shareReplay, tap, map, share, catchError, first, takeUntil } from 'rxjs/operators';
import { InterfaceService } from '@iotize/tap/service/impl/interface';
import { TapnpassService } from '@iotize/tap/service/impl/tapnpass';
import { TargetService } from '@iotize/tap/service/impl/target';
import { MonitorEngine, MonitoringController, createMonitoringTicker, TapVariable } from '@iotize/tap/data';
import { createDebugger } from '@iotize/common/debug';
import { bufferToHexString, hexStringToBuffer } from '@iotize/common/byte-converter';
import * as i2$1 from '@iotize/app-common';
import { toError } from '@iotize/app-common';
import * as i2$2 from '@angular/router';
import * as i1 from '@angular/forms';
import { Validators, ReactiveFormsModule } from '@angular/forms';
import * as i2$3 from '@angular/common';
import { CommonModule } from '@angular/common';
import { converters } from '@iotize/tap/service/core';
import { Buffer as Buffer$1 } from 'buffer';
import { NFC } from '@awesome-cordova-plugins/nfc/ngx';
import { sleep } from '@iotize/common/utility';
import { ADMIN_USER } from '@iotize/tap/configurator';
import { WifiMode } from '@iotize/tap/service/impl/wifi';
import { getScanRecordsFromBytes, getIoTizeBleCordovaPlugin, BLEScanner } from '@iotize/device-com-ble.cordova';
import { PeripheralAdapter, WebBluetoothScanner } from '@iotize/device-com-web-bluetooth.js';
const TAP_BLE_SCANNER = 'TAP_BLE_SCANNER';
const TAP_WIFI_SCANNER = 'TAP_WIFI_SCANNER';
const TAP_NETWORK_SCANNER = 'TAP_NETWORK_SCANNER';
const CONFIGURATION_MODE_VERSION = '255.255.65535';
const FACTORY_RESET_MODE_VERSION = '0.0.0';
async function adpServiceGetAdpVersion() {
const response = await this.getStatus();
response.successful();
const data = await response.rawBody();
const version = data.slice(2, 5);
return {
major: version[0],
minor: version[1],
patch: version[2],
};
}
TapnpassService.prototype.getAdpVersion = adpServiceGetAdpVersion;
// export async function interfaceServiceGetAppType(this: InterfaceService): Promise<TapVersion>{
// let response: TapResponse<any> = await this.
// }
// InterfaceService.prototype.getAppType = interfaceServiceGetAppType;
async function interfaceServiceGetAppPathResolved() {
const response = (await this.getAppPath());
if (response.isSuccessful()) {
let value = response.body();
if (value.length >= 3) {
const start = value.substring(0, 3);
const valueWithoutPrefix = value.substring(3);
switch (start) {
case AppPathType.PRIMER_CLOUD_WEB_APP_URL:
value = `https://user.cloud.iotize.com/users/${valueWithoutPrefix}`;
break;
case AppPathType.URL:
value = `${valueWithoutPrefix}`;
break;
case AppPathType.PRIMER_CLOUD_CONFIG_URL:
value = `https://user.cloud.iotize.com/users/${valueWithoutPrefix}`;
break;
}
}
response.setBody(value);
}
return response;
}
InterfaceService.prototype.getAppPathResolved =
interfaceServiceGetAppPathResolved;
async function interfaceServiceGetConfigPath() {
const response = (await this.getAppPath());
if (response.isSuccessful()) {
let value = response.body();
const start = value.substr(0, 2);
switch (start) {
case '$5':
value = `http://user.cloud.iotize.com/users${value.substr(2)}`;
break;
default:
if (!value.endsWith('.json') &&
!value.endsWith('.xml') &&
!value.endsWith('.cloud')) {
value = '';
}
}
response.setBody(value);
}
return response;
}
InterfaceService.prototype.getConfigPath = interfaceServiceGetConfigPath;
// export async function targetServiceGetAppType(this: TargetService): Promise<TapResponse<string>> {
// let response = await this.getProtocol();
// let protocol = response.body();
// let newResponse: TapResponse<string> = TapResponse.SUCCESS();
// switch (protocol) {
// case TargetProtocol.SERIAL_VIA_TAPNPASS:
// newResponse.setBody("tapnpass");
// default:
// newResponse.setBody("tapnlink");
// }
// return newResponse;
// }
async function targetServiceGetAppType() {
const response = await this.getSubProtocol();
const newResponse = TapResponse.SUCCESS();
if (response.isSuccessful()) {
newResponse.setBody('tapnpass');
}
else {
newResponse.setBody('tapnlink');
}
return newResponse;
}
TargetService.prototype.getAppType = targetServiceGetAppType;
// export async function isConnected(this: TargetService): Promise<TapResponse<boolean>> {
// return this._call({
// path: "/target/connect",
// methodType: "get",
// responseBodyDecoder: "boolean"
// })
// }
// TargetService.prototype.isConnected = isConnected;
const debug = createDebugger('@iotize/ionic/core');
const TAG$b = 'DataManager';
class DataHolder {
constructor(items = []) {
this.items = items;
}
item(id) {
const item = this.items.find((v) => v.id == id);
if (!item) {
throw new Error(`Item with id "${id}" does not exist`);
}
return item;
}
add(item, replace) {
if (replace || this.items.find((v) => v.id == item.id) == undefined) {
debug(TAG$b, 'Added item: ', item);
this.items.push(item);
}
return this.item(item.id);
}
}
// export function newBundleConfigToOldBundleConfig(bundlesConfig: BundleConfig[] | undefined): BundleConfigPredefined[] {
// if (!bundlesConfig) {
// return [];
// }
// return bundlesConfig.map((bundleConfig: BundleSchemaConfig) => {
// const predefinedConfig: BundleConfigPredefined = {
// id: bundleConfig.id,
// converter: undefined,
// name: bundleConfig.name || `Bundle ${bundleConfig.id}`,
// variables: (bundleConfig.variables || []).map((variableConfig: MemoryVariableConfig) => {
// const variableType = VariableType[variableConfig.type];
// if (variableType === undefined) {
// throw new Error(`Invalid variable type: ${variableConfig.type}`);
// }
// let converter: BodyConverter<any> | undefined;
// if (variableConfig.converter) {
// converter = variableConfig.converter;
// } else {
// converter = getConverterFromFormat(variableType, variableConfig.length || 1);
// }
// const predifinedVariableConfig: TapVariable.Config = {
// id: variableConfig.id,
// converter: converter,
// length: variableConfig.length,
// name: variableConfig.name || `Variable ${variableConfig.id}`,
// type: variableType,
// unit: variableConfig.unit
// };
// return predifinedVariableConfig;
// })
// };
// return predefinedConfig;
// });
// }
class DataManagerIonic {
constructor(tap) {
this.tap = tap;
// dataStream: Observable<Record<string, any>>;
// tlvConverter: TlvBundleConverter<Record<string, Uint8Array>>;
// dataLogger: DataLogger;
this.sourceControllers = {};
// const allVariables: AbstractVariable<VariableByType[VariableKey]>[] = Object.values(data.variables);
// this.tlvConverter = new TlvBundleConverter(allVariables.map((variable: AbstractVariable<any>) => {
// if (!(variable instanceof VariableConfig)) {
// throw new Error(`Variable ${variable.identifier} has no config`);
// }
// return {
// id: variable.config.id,
// name: variable.config.name,
// converter: variable.converter
// };
// }));
// this.dataStream = this.setupDataStream();
// this.dataLogger = DataLogger.fromTap(
// this.tap,
// {
// period: 2000 // TODO change
// }
// );
// this.tap.variables._variables = data.variables as any;
// this.tap.bundles._bundles = data.bundles as any;
// Change variable stream;
// Object.values(this.data.variables).map((variable: any) => {
// const variableName = variable.config.name;
// // console.log('Replacing monitor for ', variable);
// variable._monitor = ObservableMonitor.fromObservable(
// this.dataStream
// .pipe(
// filter(data => variableName in data),
// map(data => data[variableName]),
// rxTap(value => debug(TAG, `Extracted key ${variableName} from monitoring data`, value))
// )
// ,
// MonitorEngine.State.START
// );
// });
// // Change bundle stream;
// Object.values(this.data.bundles).map((bundle: any) => {
// bundle._monitor = ObservableMonitor.fromObservable(
// this.dataStream
// .pipe(
// rxTap(value => debug(TAG, `New bundle value `, value))
// )
// ,
// MonitorEngine.State.START
// );
// // TODO factorize and clean
// Object.values(bundle.variables).map((variable: any) => {
// const variableName = variable.config.name;
// // console.log('Replacing monitor for variable in bundle', variable);
// variable._monitor = ObservableMonitor.fromObservable(
// this.dataStream
// .pipe(
// filter(data => variableName in data),
// map(data => data[variableName]),
// rxTap(value => debug(TAG, `Extracted key ${variableName} from monitoring data`, value))
// )
// ,
// MonitorEngine.State.START
// );
// });
// });
// debug(TAG, 'New data manager instance: ', this);
}
// get variables(): Record<VariableKey, AbstractVariable<VariableByType[VariableKey]>> {
// return this.data.variables;
// }
// get bundles(): Record<BundleKey, Bundle<BundleByType[BundleKey]>> {
// return this.data.bundles;
// }
get sourceController() {
const currentSourceId = this.switchableStreamController.getCurrentSourceId();
return this.sourceControllers[currentSourceId];
}
get isMonitoringRunning() {
for (const value of Object.values(this.sourceControllers)) {
if (value.state == MonitorEngine.State.START) {
return true;
}
}
return false;
}
// public static fromSchemaConfig(tap: Tap, schema: TapConfiguratorConfig): DataManagerIonic {
// const bundles = schema.config.data ? schema.config.data.bundles || [] : [];
// return DataManagerIonic.fromBundleConfig(tap, bundles);
// }
// public static fromBundleConfig(tap: Tap, bundles: BundleConfig[]): DataManagerIonic {
// return new DataManagerIonic(
// tap,
// DataManagerIonic.createDataFromSchemaConfig(tap, bundles)
// );
// }
// public static createDataFromSchemaConfig<VariableByType extends Record<string, any>, VariableKey extends string, BundleByType extends Record<string, any>, BundleKey extends string>(tap: Tap, bundlesConfig: BundleSchemaConfig[]) {
// const predifinedConfig: BundleConfigPredefined[] = newBundleConfigToOldBundleConfig(bundlesConfig);
// return DataManagerIonic.createDataFromConfig(tap, predifinedConfig);
// }
// public static createDataFromConfig<VariableByType extends Record<string, any>, VariableKey extends string, BundleByType extends Record<string, any>, BundleKey extends string>(tap: Tap, bundlesConfig: BundleConfigPredefined[]) {
// // let bundleConfigCopy = [...bundlesConfig];
// const bundles: Record<string, any> = {};
// bundlesConfig.forEach((bundleConfig: BundleConfigPredefined) => {
// const bundle: Bundle = Bundle.createFromConfig(bundleConfig, tap.service.bundle, tap.service.variable);
// bundles[bundleConfig.name] = bundle;
// });
// const variables: Record<string, any> = {};
// bundlesConfig.forEach((bundle) => {
// bundle.variables.forEach((variableConfig: TapVariable.Config) => {
// debug(TAG, 'Create from variable config: ', variableConfig);
// variables[variableConfig.name || variableConfig.id.toString()] = TapVariable.createFromConfig(variableConfig, tap.service.variable);
// });
// });
// return {
// bundles: bundles as Record<BundleKey, Bundle<BundleByType[BundleKey]>>,
// variables: variables as Record<VariableKey, AbstractVariable<VariableByType[VariableKey]>>
// };
// }
listVariables() {
return this.tap.data.listVariables();
}
listBundles() {
return this.tap.data.listBundles();
}
// public variable<T extends VariableKey>(key: T): AbstractVariable<VariableByType[T]> {
// if (!(key in this.variables)) {
// throw new Error(`Variable with identifier "${key}" is not configured`);
// }
// return this.variables[key] as AbstractVariable<VariableByType[T]>;
// }
// public bundle<T extends BundleKey>(key: T): Bundle<BundleByType[T]> {
// if (!(key in this.bundles)) {
// throw new Error(`Bundle with identifier "${key}" is not configured`);
// }
// return this.bundles[key] as any;
// }
stopAll() {
debug(TAG$b, 'Stop monitoring');
// for (const ctrlKey in this.sourceControllers) {
// this.sourceControllers[ctrlKey].state = MonitorEngine.State.PAUSE;
// }
this.tap.data.monitoring.stop();
}
startAll(period) {
// debug(TAG, `[Controller=${this.sourceController.id}] Start monitoring with period ${period || this.sourceController.period.value}`, this.sourceController);
// if (period) {
// this.setMonitoringPeriod(period);
// }
// this.sourceController.state = MonitorEngine.State.START;
this.tap.data.monitoring.start();
}
setMonitoringPeriod(period) {
debug(TAG$b, 'Changing monitoring period to ', period);
this.sourceController.period.next(period);
}
destroy() {
this.stopAll();
}
refresh() {
if (this.sourceController.refresh) {
this.sourceController.refresh();
}
}
changeDataSource(dataSource) {
switch (dataSource) {
case 'datalog':
this.useDatalogSource();
break;
default:
this.useLiveSource();
}
}
useLiveSource() {
this.switchableStreamController.useSource('live');
}
useDatalogSource() {
this.switchableStreamController.useSource('datalog');
}
}
function isSameTag(t1, t2) {
return (bufferToHexString(t1.id) ===
bufferToHexString(t2.id));
}
function isNfcTapRequiredError(err) {
const cause = getCauseError(err);
return (isCodeError(NfcError.ErrorCode.TagLostError, cause) ||
isCodeError(NfcError.ErrorCode.NotConnectedError, cause) ||
isCodeError(NfcError.ErrorCode.Unknown, cause) ||
isCodeError(NfcError.ErrorCode.InternalError, cause));
}
function getCauseError(err) {
let depth = 0;
while (err.cause && depth < 5) {
err = err.cause;
depth++;
}
return err;
}
/**
* Do not use this classe.
* Used for angular DI
* Create a class that implemement this one
*/
class ProtocolFactoryService {
constructor() { }
create(meta) {
throw new Error(`Unsupported protocol type "${meta.type}"`);
}
isValid(meta) {
return false;
}
}
function runInZone(zone) {
return (source) => {
return new Observable((observer) => {
const onNext = (value) => zone.run(() => observer.next(value));
const onError = (e) => zone.run(() => observer.error(e));
const onComplete = () => zone.run(() => observer.complete());
return source.subscribe(onNext, onError, onComplete);
});
};
}
class LibError {
static missingFromField(fieldKey) {
return new Error(`InternalError: form must have a field with name "${fieldKey}"`);
}
static componentArgumentRequired(component, input) {
return new Error(`InternalError: ${component} requires input "${input}"`);
}
static abstractVariableRequired() {
return new Error('InternalError: AbstractVariable cannot be undefined');
}
}
function createBleName(appName, serialNumber) {
return appName + '_' + serialNumber.substr(serialNumber.length - 5);
}
function getFormFieldOrError(form, key) {
const field = form.get(key);
if (!field) {
throw LibError.missingFromField(key);
}
return field;
}
function getAbstractVariableOrError(variable) {
if (!variable) {
throw LibError.abstractVariableRequired();
}
else {
return variable;
}
}
const TAG$a = 'CurrentDeviceService';
function LONG_RANGE_PROTOCOL_FILTER(meta) {
return meta.type !== 'nfc';
}
function getProtocolOrUndefined(client) {
try {
return client.getCurrentProtocol();
}
catch (err) {
return undefined;
}
}
class TapServiceError extends Error {
static illegalArgument(msg) {
throw new Error(`Illegal argument: ${msg}`);
}
static illegalStateNoTap() {
return new TapServiceError('Illegal state: Tap is not set yet');
}
}
class CurrentDeviceService {
get sessionStateSnapshot() {
return this._sessionStateSnapshot;
}
get sessionState() {
return this._sessionState$;
}
get protocolMeta$() {
return this._protocolMeta.asObservable();
}
get availableProtocols$() {
return this._availableProtocols.asObservable();
}
get protocolMeta() {
return this._protocolMeta.value;
}
set protocolMeta(meta) {
if (meta) {
this.addProtocolMeta(meta);
}
debug(TAG$a, 'setting protocol meta', meta);
this._protocolMeta.next(meta);
}
get connectionLost() {
return this._connectionLost$.asObservable();
}
get availableProtocols() {
return this._availableProtocols.value;
}
get tap() {
if (!this._tap) {
throw new Error('Connect to a device first');
// throw TapServiceError.illegalStateNoTap();
}
return this._tap;
}
get tapOrUndefined() {
return this._tap;
}
get hasTap() {
return this._tap !== undefined;
}
set tapConfig(schema) {
debug(TAG$a, 'Set Tap config', schema);
this.dataManager = new DataManagerIonic(this.tap);
this._tapConfig$.next(schema);
if (schema) {
if (schema.config?.data) {
// this.tap.data.clear();
this.tap.data.configureWithDataConfig(schema.config.data);
/*
this.tap.data.values.subscribe(v => {
})
this.tap.data.monitoring.asSubject()
.subscribe(state => {
})
*/
}
else {
this.tap.data.clear();
}
//
// this.dataLogger.converter = new DataLogPacketConverter(
// newBundleConfigToOldBundleConfig(schema.config.data.bundles)
// );
// debug(TAG, 'Updating datalog converter');
// this.dataLogger.converter = DataLogPacketConverter.createFromManager(this.tap.bundles, this.tap.variables);
// if (schema.config.data) {
// this.dataLogger.converter = new DataLogPacketConverter(
// newBundleConfigToOldBundleConfig(schema.config.data.bundles || [])
// );
// }
}
}
get tapConfig() {
return this._tapConfig$.value;
}
get tapConfig$() {
return this._tapConfig$;
}
isSameTag(tag) {
if (this.protocolMeta && this.protocolMeta.type === 'nfc') {
// Check is it's the same tag
const currentTag = this.protocolMeta.info.tag;
if (currentTag.id && currentTag.id?.length == 0) {
return false;
}
return isSameTag(currentTag, tag);
}
else {
// It's not nfc currently
const nfcProtocolMeta = this.availableProtocols.find((p) => p.type === 'nfc');
if (nfcProtocolMeta) {
return isSameTag(nfcProtocolMeta.info.tag, tag);
}
}
return false;
}
/**
* Use another communicaiton protocol
* May be rejected
* @param meta: ProtocolMeta
* @param disonnectCurrentProtocol if set to true and if tap is already connected with
* a communication protocol, it will disconnect from it first
* @param connectToNew: boolean
*/
async useProtocol(meta, disonnectCurrentProtocol = true, connectToNew = true) {
debug(TAG$a, 'use protocol', meta);
const protocol = await this.protocolFactory.create(meta);
if (!this._tap) {
this.tap = Tap.fromProtocol(protocol);
}
else {
// let oldConnectionState = this._tap.protocol.getConnectionState();
if (disonnectCurrentProtocol) {
try {
await this.tap.protocol.disconnect().toPromise();
}
catch (err) {
console.warn('Cannot disconnect current protocol properly: ', err);
}
}
this._tap.useComProtocol(protocol);
}
this.protocolMeta = meta;
if (connectToNew) {
await this.connect();
}
}
async executeFactoryReset() {
await this.tap.factoryReset();
this.tap.auth.clearCache();
this.tap.encryption.stop();
}
set tap(t) {
this.setTap(t, { emit: true });
}
constructor(platform, toastCtrl, protocolFactory, ngZone) {
this.platform = platform;
this.toastCtrl = toastCtrl;
this.protocolFactory = protocolFactory;
this.ngZone = ngZone;
/**
* Hack to prevent angular treeshaking from removing loaded extension.
*/
this._loadedTapExtensions = [
_TAP_EXTENSION_DATA_,
_TAP_EXTENSION_DATA_LOG_,
_TAP_EXTENSION_KEEP_ALIVE_,
_TAP_SERVICE_ALL_EXTENSIONS_,
_TAP_EXTENSION_AUTH_,
factoryReset,
];
this._tapOrUndefined = new BehaviorSubject(undefined);
/**
* Only connection lost event
*/
this._connectionLost$ = new Subject();
this._sessionState$ = this._tapOrUndefined.pipe(switchMap((tapDeviceOrUndefined) => {
if (!tapDeviceOrUndefined) {
return of(INITIAL_SESSION_STATE);
}
else {
return tapDeviceOrUndefined.auth.sessionState;
}
}));
this.listeners = [];
this.keepAlivePeriod = 10 * 1000;
this.meta = {};
/**
* @deprecated
*/
this._tapConfig$ = new BehaviorSubject(undefined);
this._maxFrameSizeCache = {};
/**
* Event trigger when currrent Tap changed (set or unset)
* If tap is removed, value will be undefined
* Immediatly triggered the current value when subscribing
*/
this.tapOrUndefinedChangedWithLastValue = this._tapOrUndefined.pipe(distinctUntilChanged());
/**
* Event trigger when currrent Tap changed (set or unset)
* If tap is removed, value will be undefined
*/
this.tapOrUndefinedChanged = this.tapOrUndefinedChangedWithLastValue.pipe(skip(1));
/**
* Event trigger when a new Tap is selected (no event when tap is removed)
*/
this.tapChanged = this.tapOrUndefinedChanged.pipe(filter((tap) => !!tap));
/**
* Event trigger when a new Tap is selected (no event when tap is removed)
* Immediatly triggered the current value when subscribing
*/
this.tapChangedWithLastValue = this.tapOrUndefinedChangedWithLastValue.pipe(filter((tap) => !!tap));
/**
* Event triggered when tap is removed
*/
this.tapRemoved = new Subject();
this._protocolMeta = new BehaviorSubject(undefined);
this._availableProtocols = new BehaviorSubject([]);
this._isManualDisconnection = false;
this._sessionStateSnapshot = INITIAL_SESSION_STATE;
/**
* Connection state events with replay.
* It works even when tap is changed
*/
this.connectionStateReplay = this.tapOrUndefinedChangedWithLastValue.pipe(switchMap((tapDevice) => {
if (!tapDevice) {
return of({
newState: ConnectionState$1.DISCONNECTED,
oldState: ConnectionState$1.DISCONNECTED,
});
}
const currentProtocol = getProtocolOrUndefined(tapDevice.client);
return tapDevice.client.onProtocolChange().pipe(startWith({
newProtocol: currentProtocol,
}), switchMap((event) => {
if (!event.newProtocol) {
return of({
newState: ConnectionState$1.DISCONNECTED,
oldState: ConnectionState$1.DISCONNECTED,
});
}
return event.newProtocol.onConnectionStateChange().pipe(runInZone(this.ngZone), startWith({
newState: event.newProtocol.getConnectionState(),
oldState: ConnectionState$1.DISCONNECTED,
}));
}));
}), shareReplay(1));
/**
* Any connection state change
*/
this._connectionState$ = this.connectionStateReplay.pipe(skip(1));
debug(TAG$a, 'NEW INSTANCE', this._loadedTapExtensions);
this.tapChangedWithLastValue.subscribe((newTap) => {
newTap.client.addInterceptor((context, next) => {
return next.handle(context).pipe(tap((tapResponseFrame) => {
const response = new TapResponse(tapResponseFrame, context.request);
if (response.status === ResultCode.UNAUTHORIZED) {
if (TapRequestHelper.pathToString(context.request.header.path) !== this.tap.service.interface.resources.login.path) {
this.listeners.forEach((listener) => {
if (listener.onTapRequestUnauthorized) {
listener.onTapRequestUnauthorized({
request: context,
response: response,
});
}
});
}
}
if (!response.isSuccessful()) {
this.listeners.forEach((listener) => {
if (listener.onTapRequestError) {
listener.onTapRequestError({
request: context.request,
error: new TapResponseStatusError(response),
response: response,
});
}
});
}
}, (error) => {
this.listeners.forEach((listener) => {
console.warn(`Request ${context.request} errored: ${error}`);
if (listener.onTapRequestError) {
listener.onTapRequestError({
request: context.request,
error: error,
});
}
});
}));
});
});
this.sessionState.subscribe((newSessionState) => {
this._sessionStateSnapshot = newSessionState;
});
this.connectionStateReplay.subscribe(async (event) => {
if (event.newState === ConnectionState$1.DISCONNECTED) {
this.keepAliveEngine?.stop();
if (!this._isManualDisconnection) {
this._connectionLost$?.next(event);
}
}
else if (event.newState == ConnectionState$1.CONNECTED) {
this.ngZone.runOutsideAngular(() => {
if (this.keepAlivePeriod > 0) {
debug(TAG$a, 'Start keep alive with period', this.keepAlivePeriod);
this.keepAliveEngine?.start();
}
else {
debug(TAG$a, 'NO keep alive');
}
});
}
this.listeners.forEach((listener) => listener.onTapConnectionStateChange(event));
});
}
addProtocolMeta(meta) {
if (meta) {
const protocols = this.availableProtocols;
const protocolIndex = protocols.findIndex((existing) => {
return existing.type === meta.type;
});
if (protocolIndex >= 0) {
debug(TAG$a, 'Protocol meta', meta, 'already exists. Replacing infos');
protocols[protocolIndex] = meta;
this._availableProtocols.next(protocols);
}
else {
debug(TAG$a, 'Adding protocol meta', meta);
protocols.push(meta);
this._availableProtocols.next(protocols);
}
}
}
/**
* Parse NDefTag and try to create a ProtocolMeta thanks to record
* @returns undefined if there is no other protocol in the tag
*/
async registerProtocolsFromTag(tag) {
let meta;
if (!tag.ndefMessage) {
return undefined;
}
const info = parseTapNdefMessage(tag.ndefMessage);
if (info.macAddress && info.macAddress !== '00:00:00:00:00:00') {
meta = {
type: 'ble',
info: {
mac: info.macAddress,
name: info.name,
},
};
}
else if (info.ssid) {
const hostname = (await this.tap.service.wifi.getHostname()).body();
meta = {
type: 'wifi',
info: {
ssid: info.ssid,
// name: info.name,
url: `tcp://${hostname}:2000`,
},
};
}
if (meta) {
this.addProtocolMeta(meta);
}
return meta;
}
async getCurrentHostProtocolMaxFrameSizeCacheFirst() {
const protocol = this.protocolMeta?.type;
if (!protocol || !this._maxFrameSizeCache[protocol]) {
const hostProtocol = (await this.tap.service.interface.getCurrentHostProtocolMaxFrameSize()).body();
if (hostProtocol.request > 0xff) {
hostProtocol.request -= 2; // due to 2 more bytes for apdu.header.lc
}
if (protocol) {
this._maxFrameSizeCache[protocol] = hostProtocol;
}
else {
return hostProtocol;
}
}
return this._maxFrameSizeCache[protocol];
}
/**
* Will register available communication protocols on current tap
* by asking LWM2M resources.
* @returns the list of new ProtocolMeta found
*/
async registerProtocolsFromTap() {
const nProtocolMeta = [];
const [appNameResponse, serialNumberResponse, authorizedHostProtocolsResponse, bleAddressResponse, ipResponse,] = await this.tap.service.interface.executeMultipleCalls([
this.tap.service.interface.getAppNameCall(),
this.tap.service.device.getSerialNumberCall(),
this.tap.service.interface.getAuthorizedHostProtocolCall(),
this.tap.service.ble.getAddressCall(),
this.tap.service.wifi.getIpCall(),
]);
const authorizedHostProtocols = authorizedHostProtocolsResponse.body();
if (this.protocolMeta?.type !== 'ble' &&
authorizedHostProtocols.includes(HostProtocol.BLE)) {
if (this.platform.is('ios')) {
const bleName = createBleName(appNameResponse.body(), serialNumberResponse.body());
nProtocolMeta.push({
type: 'ble',
info: {
name: bleName,
},
});
}
else {
nProtocolMeta.push({
type: 'ble',
info: {
mac: bleAddressResponse.body(),
},
});
}
}
if (this.protocolMeta?.type !== 'socket' &&
this.platform.is('mobile') &&
authorizedHostProtocols.includes(HostProtocol.WIFI)) {
const ip = ipResponse.body();
if (ip && ip !== '0.0.0.0') {
nProtocolMeta.push({
type: 'socket',
info: {
url: 'tcp://' + ip + ':2000',
},
});
}
}
nProtocolMeta.forEach((meta) => this.addProtocolMeta(meta));
return nProtocolMeta;
}
async useProtocolFromMeta(newProtocolMeta, disonnectCurrentProtocol = true, connectToNew = true) {
if (!this.protocolMeta || newProtocolMeta.type !== this.protocolMeta.type) {
return await this.useProtocol(newProtocolMeta, disonnectCurrentProtocol, connectToNew);
}
}
/**
* Switch from NFC communication protocol to a long range communication
* @warning this is only available when we are in NFC
*
* @return the new protocol meta used or undefined if it does not have a long range protocol to use
*/
async useLongRangeProtocol() {
debug(TAG$a, 'useLongRangeProtocol');
const currentProtocol = this.protocolMeta;
if (!this._tap) {
throw TapServiceError.illegalStateNoTap();
}
if (currentProtocol) {
if (currentProtocol.type === 'nfc') {
const protocolMeta = this.availableProtocols.find(LONG_RANGE_PROTOCOL_FILTER);
if (protocolMeta) {
debug(TAG$a, 'Using long range protocol: ', protocolMeta);
await this.useProtocolFromMeta(protocolMeta);
return protocolMeta;
}
else {
debug(TAG$a, 'NFC tag does not have long range protocol information...');
}
}
}
return undefined;
}
/**
* Connection state events.
* It works even when tap is changed
*/
get connectionState() {
return this._connectionState$;
}
/**
* @returns true if user is connected as given username or one of the given usernames
*/
async isLoggedInAsUserOrProfileName(userOrProfileNames) {
if (typeof userOrProfileNames === 'string') {
userOrProfileNames = [userOrProfileNames];
}
if (typeof userOrProfileNames === 'number') {
console.warn('invalid parameter for CurrentDeviceService.isLoggedInAsUserOrProfileName() username should be a string');
userOrProfileNames = [userOrProfileNames.toString()];
}
if (!this._tap || !this.tap.auth.sessionStateSnapshot) {
return false;
}
const sessionState = this.tap.auth.sessionStateSnapshot ||
(await this.tap.auth.refreshSessionState());
return (userOrProfileNames.includes(sessionState.profileName) ||
userOrProfileNames.includes(sessionState.name));
}
async login(username, password, refreshSessionState = true) {
return await this.tap.auth.login({ username, password }, {
noRefreshSessionState: !refreshSessionState,
});
}
async logout(throwErr = false) {
if (!this._tap) {
return false;
}
try {
await this._tap.auth.logout();
return true;
}
catch (err) {
if (throwErr) {
throw err;
}
else {
this.onError.bind(this);
return false;
}
}
}
setTap(newTap, options = { emit: true }) {
if (!newTap) {
throw new Error('Illegal state: cannot set undefined tap');
}
this._tap = newTap;
debug(TAG$a, 'Setting tap...');
this.meta = {};
this._tap = newTap;
if (this.keepAlivePeriod === 0) {
newTap.keepAlive.stop();
}
newTap.keepAlive.period = this.keepAlivePeriod;
if (options.emit) {
this.notifyNewTap();
}
}
notifyNewTap() {
this._tapOrUndefined.next(this._tap);
}
setTapFromEvent(event, options = { emit: true }) {
if (!event.protocolMeta) {
throw new Error('Missing protocol information');
}
this.protocolMeta = event.protocolMeta;
// this.tap = event.tap;
this.setTap(event.tap, options);
debug(TAG$a, 'setTapFromEvent = >', event);
}
/**
* Connect to the Tap and refresh session state
* Throw error if it fails
*/
async connect(
/**
* @deprecated
*/
throwErr = true) {
try {
await this.tap.connect();
await this.refreshSessionState();
return true;
}
catch (err) {
debug(TAG$a, 'Throwing error on connection failed');
throw err;
}
}
/**
* Disconnect Tap
*/
async disconnect() {
debug(TAG$a, 'disconnect');
if (!this.hasProtocol()) {
return;
}
this._isManualDisconnection = true;
debug(TAG$a, 'Setting _isManualDisconnection flag to true');
return (this._tap
? this._tap.disconnect()
: Promise.reject(TapServiceError.illegalStateNoTap())).then((res) => {
// This is a temporary hack to differentiate manual/unexpected disconnection
// The timeout is used to make sure that direct events are trigger before we set the manual
// disconnection flag back to false
// TODO remove later
setTimeout(() => {
debug(TAG$a, 'Setting _isManualDisconnection flag to false');
this._isManualDisconnection = false;
}, 50);
return res;
});
}
/**
* Remove currently used Tap
* If no Tap was set, do nothing
*/
async remove(disconnect = true) {
if (this._tap) {
const oldTap = this._tap;
debug(TAG$a, 'Removing tap');
this.protocolMeta = undefined;
this._availableProtocols.next([]);
if (disconnect) {
await this.disconnect().catch(this.onError.bind(this));
}
console.log('Stopping old keep alive', oldTap.keepAlive);
oldTap.keepAlive.stop();
this._tap = undefined;
this._dataManager = undefined;
this._maxFrameSizeCache = {};
// this._tapConfig = undefined;
this.notifyTapRemoved(oldTap);
}
else {
debug(TAG$a, 'No tap to remove');
}
}
notifyTapRemoved(t) {
this.tapRemoved.next(t);
this.notifyNewTap();
}
/**
* @deprecated
*/
async configureClientIfRequired() {
if (!this.meta.isClientConfigured) {
console.log('Configuring client...');
await this.configureClient().catch((err) => {
this.onError(err);
});
}
}
/**
* Rebuild tap configuration according to data on the Tap
*
* @deprecated
*/
async loadConfigFromDevice() {
debug(TAG$a, 'dynamically load Tap config');
const syncEvent = await this.tap.data.synchronizeTapConfig().toPromise();
this.meta.isClientConfigured = true;
// TODO Fix to get real config
if (syncEvent?.step === 'done') {
const bundles = syncEvent.bundles;
this.tapConfig = {
meta: {
version: '1.0.0',
partial: true,
},
config: {
version: 1,
data: {
bundles: [],
},
},
};
}
}
set dataManager(v) {
debug(TAG$a, 'Setting data manager', v);
if (this._dataManager) {
this._dataManager.destroy();
}
this._dataManager = v;
}
get dataManager() {
if (!this._dataManager) {
return undefined;
}
return this._dataManager;
}
/**
* Configure client by reading tap device configuration
*
* @deprecated will be moved to a separate service
*
* @throws
*/
async configureClient(refresh = false) {
debug(TAG$a, 'configureClient');
if (refresh) {
this.tapConfig = undefined;
}
// TODO only once
// if (this.modelConfig) {
// console.info('load from iotz model configuration file')
// let tapConfigConfigurator = new TapConfigConfigurator(this.modelConfig);
// await this.tap.configure(tapConfigConfigurator);
// this.meta.isClientConfigured = true;
// console.log('Variables: ', this.tap.variables);
// }
if (this.tapConfig === undefined) {
await this.loadConfigFromDevice();
// this.tap.bundles.clear();
// this.tap.variables.clear();
// await this.tap.configure(this.readDeviceDataConfigurator).catch(this.onError.bind(this));
}
}
getCurrentProtocol() {
return getProtocolOrUndefined(this.tap.client);
}
get keepAliveEngine() {
return this._tap?.keepAlive;
}
hasProtocol() {
return this.getCurrentProtocol() !== undefined;
}
registerEventListerner(listener) {
this.listeners.push(listener);
}
unregisterEventListener(listener) {
const index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
}
/**
* @deprecated refractor: remove on error from this service to use a global handler
*
* @param err
*/
async onError(err) {
// TODO remove
const toast = await this.toastCtrl.create({
message: err.message || 'Unknown error',
color: 'danger',
position: 'bottom',
duration: 3000,
buttons: [
{
text: 'Close',
role: 'cancel',
handler: () => {
console.log('Close clicked');
},
},
],
});
await toast.present();
}
async reboot() {
let rebootResponse;
try {
rebootResponse = await this.tap.service.device.reboot();
}
catch (err) {
if (!isCodeError(TapError.Code.ExecuteRequestError, err)) {
throw err;
}
else {
const cause = err.cause;
if (cause?.code !== ComProtocol.ErrorCode.TimeoutError) {
console.warn('Reboot error', err);
throw err;
}
// ignore timeout error as tap may reboot before sending response
}
}
if (rebootResponse) {
rebootResponse.successful();
}
await this.clearAuth();
this.tap.disconnect().catch((err) => { });
}
async clearAuth() {
this.tap.auth.clearCache();
this.tap.encryption.stop();
await this.tap.auth.logout().catch((err) => { });
}
async refreshSessionState() {
try {
return await this.tap.auth.refreshSessionState();
}
catch (err) {
if (isCodeError(TapError.Code.ScramNotStartedYet, err) ||
isCodeError(TapError.Code.InvalidScramKey, err)) {
console.warn(`SCRAM session ended due to error`, err.message);
this.tap.encryption.stop();
return await this.tap.auth.refreshSessionState();
}
else {
throw err;
}
}
}
}
/** @nocollapse */ CurrentDeviceService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CurrentDeviceService, deps: [{ token: i2.Platform }, { token: i2.ToastController }, { token: ProtocolFactoryService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ CurrentDeviceService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CurrentDeviceService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CurrentDeviceService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: i2.Platform }, { type: i2.ToastController }, { type: ProtocolFactoryService }, { type: i0.NgZone }]; } });
/**
* Route resolver to make sure that a Tap is set at this point
*
*/
class ConnectedTapResolver {
constructor(tapService) {
this.tapService = tapService;
}
async resolve(route, state) {
try {
await this.tapService.connect();
}
catch (err) {
return this.onError(err);
}
}
onError(error) {