diff --git a/packages/adapter/src/lib/_Types.ts b/packages/adapter/src/lib/_Types.ts index 031aa512a6..ffd3b61e3c 100644 --- a/packages/adapter/src/lib/_Types.ts +++ b/packages/adapter/src/lib/_Types.ts @@ -50,6 +50,13 @@ export interface AdapterOptions { error?: ioBroker.ErrorHandler; } +export type IoPackageInstanceObject = + | ioBroker.StateObject + | ioBroker.DeviceObject + | ioBroker.ChannelObject + | ioBroker.FolderObject + | ioBroker.MetaObject; + type MessageUnsubscribeReason = 'client' | 'disconnect'; export type ClientUnsubscribeReason = MessageUnsubscribeReason | 'clientSubscribeError'; type UserInterfaceClientUnsubscribeReason = ClientUnsubscribeReason | 'timeout'; diff --git a/packages/adapter/src/lib/adapter/adapter.ts b/packages/adapter/src/lib/adapter/adapter.ts index 7f77bcb43f..0fb8196104 100644 --- a/packages/adapter/src/lib/adapter/adapter.ts +++ b/packages/adapter/src/lib/adapter/adapter.ts @@ -109,7 +109,8 @@ import type { InternalAdapterConfig, UserInterfaceClientRemoveMessage, SendToUserInterfaceClientOptions, - AllPropsUnknown + AllPropsUnknown, + IoPackageInstanceObject } from '../_Types'; import { UserInterfaceMessagingController } from './userInterfaceMessagingController'; @@ -1267,7 +1268,7 @@ export class AdapterClass extends EventEmitter { /** * Encrypt the password/value with given key - * @param secretVal to use for encrypt (or value if only one parameter is given) + * @param secretVal to use for encrypting (or value if only one parameter is given) * @param [value] value to encrypt (if secret is provided) */ encrypt(secretVal: unknown, value?: unknown): string { @@ -11939,7 +11940,7 @@ export class AdapterClass extends EventEmitter { } private async _createInstancesObjects(instanceObj: ioBroker.InstanceObject): Promise { - let objs: ioBroker.AnyObject[]; + let objs: (IoPackageInstanceObject & { state?: unknown })[]; if (instanceObj?.common && !('onlyWWW' in instanceObj.common) && instanceObj.common.mode !== 'once') { // @ts-expect-error @@ -11949,8 +11950,9 @@ export class AdapterClass extends EventEmitter { } if (instanceObj && 'instanceObjects' in instanceObj) { - // @ts-expect-error - for (const obj of instanceObj.instanceObjects) { + for (const instObj of instanceObj.instanceObjects) { + const obj: IoPackageInstanceObject & { state?: unknown } = instObj; + if (!obj._id.startsWith(this.namespace)) { // instanceObjects are normally defined without namespace prefix obj._id = obj._id === '' ? this.namespace : `${this.namespace}.${obj._id}`; @@ -11959,38 +11961,36 @@ export class AdapterClass extends EventEmitter { if (obj && (obj._id || obj.type === 'meta')) { if (obj.common) { if (obj.common.name) { + const commonName = obj.common.name; // if name has many languages - if (typeof obj.common.name === 'object') { - Object.keys(obj.common.name).forEach( - lang => - (obj.common.name[lang] = obj.common.name[lang].replace( - '%INSTANCE%', - this.instance - )) - ); + if (tools.isObject(commonName)) { + for (const [lang, value] of Object.entries(commonName)) { + commonName[lang as keyof ioBroker.Translated] = value.replace( + '%INSTANCE%', + this.instance!.toString() + ); + } } else { - obj.common.name = obj.common.name.replace('%INSTANCE%', this.instance); + obj.common.name = commonName.replace('%INSTANCE%', this.instance!.toString()); } } - if (obj.common.desc) { + if ('desc' in obj.common) { + const commonDesc = obj.common.desc; + // if description has many languages - if (typeof obj.common.desc === 'object') { - Object.keys(obj.common.desc).forEach( - lang => - (obj.common.desc[lang] = obj.common.desc[lang].replace( - '%INSTANCE%', - this.instance - )) - ); + if (tools.isObject(commonDesc)) { + for (const [lang, value] of Object.entries(commonDesc)) { + commonDesc[lang] = value.replace('%INSTANCE%', this.instance); + } } else { - obj.common.desc = obj.common.desc.replace('%INSTANCE%', this.instance); + obj.common.desc = commonDesc.replace('%INSTANCE%', this.instance); } } if (obj.type === 'state' && obj.common.def !== undefined) { // default value given - if obj non-existing we have to set it try { - const checkObj = await this.getForeignObjectAsync(obj._id); + const checkObj = await adapterObjects!.objectExists(obj._id); if (!checkObj) { obj.state = obj.common.def; } diff --git a/packages/cli/src/lib/_Types.ts b/packages/cli/src/lib/_Types.ts index 4157a87744..5d1040ad59 100644 --- a/packages/cli/src/lib/_Types.ts +++ b/packages/cli/src/lib/_Types.ts @@ -32,7 +32,5 @@ interface IoPackageCommon extends ioBroker.AdapterCommon { }; } export interface IoPackage extends ioBroker.AdapterObject { - objects: ioBroker.Object[]; - instanceObjects: ioBroker.Object[]; common: IoPackageCommon; } diff --git a/packages/cli/src/lib/setup/setupBackup.ts b/packages/cli/src/lib/setup/setupBackup.ts index 6085906275..4841792cc4 100644 --- a/packages/cli/src/lib/setup/setupBackup.ts +++ b/packages/cli/src/lib/setup/setupBackup.ts @@ -1199,9 +1199,7 @@ export class BackupRestore { const adapterObj = await this.objects.getObjectAsync(`system.adapter.${adapterName}`); if (adapterObj?.common?.version) { let installSource; - // @ts-expect-error https://github.com/ioBroker/adapter-core/issues/455 if (adapterObj.common.installedFrom) { - // @ts-expect-error https://github.com/ioBroker/adapter-core/issues/455 installSource = adapterObj.common.installedFrom; } else { installSource = `${tools.appName.toLowerCase()}.${adapterName}@${adapterObj.common.version}`; diff --git a/packages/cli/src/lib/setup/setupInstall.ts b/packages/cli/src/lib/setup/setupInstall.ts index 18e5b4ffd2..9d5be8a75e 100644 --- a/packages/cli/src/lib/setup/setupInstall.ts +++ b/packages/cli/src/lib/setup/setupInstall.ts @@ -885,9 +885,7 @@ export class Install { instanceObj._id = `system.adapter.${adapter}.${instance}`; // @ts-expect-error we now convert the adapter object to an instance object instanceObj.type = 'instance'; - // @ts-expect-error types needed TODO if (instanceObj.common.news) { - // @ts-expect-error types needed TODO delete instanceObj.common.news; // remove this information as it could be big, but it will be taken from repo } diff --git a/packages/cli/src/lib/setup/setupUpload.ts b/packages/cli/src/lib/setup/setupUpload.ts index a8c4476a92..585e96d343 100644 --- a/packages/cli/src/lib/setup/setupUpload.ts +++ b/packages/cli/src/lib/setup/setupUpload.ts @@ -714,7 +714,7 @@ export class Upload { */ async _upgradeAdapterObjectsHelper( name: string, - ioPack: Record, + ioPack: ioBroker.AdapterObject, hostname: string, logger: Logger | typeof console ): Promise { @@ -730,6 +730,8 @@ export class Upload { const _obj = await this.objects.getObjectAsync(row.id); const newObject = deepClone(_obj) as ioBroker.InstanceObject; + // TODO: refactor the following assignments into a method, where we can define which attributes need a real override and their defaults + // all common settings should be taken from new one newObject.common = this.extendCommon( newObject.common, @@ -743,15 +745,19 @@ export class Upload { newObject.encryptedNative = ioPack.encryptedNative || []; newObject.notifications = ioPack.notifications || []; // update instanceObjects and objects - // @ts-expect-error TODO needs to be added to types newObject.instanceObjects = ioPack.instanceObjects || []; - // @ts-expect-error TODO needs to be added to types newObject.objects = ioPack.objects || []; newObject.common.version = ioPack.common.version; newObject.common.installedVersion = ioPack.common.version; newObject.common.installedFrom = ioPack.common.installedFrom; + if (ioPack.common.visWidgets) { + newObject.common.visWidgets = ioPack.common.visWidgets; + } else { + delete newObject.common.visWidgets; + } + if (!ioPack.common.compact && newObject.common.compact) { newObject.common.compact = ioPack.common.compact; } @@ -770,17 +776,17 @@ export class Upload { } // updates only "_design/system" and co "_design/*" - if (ioPack.objects && typeof ioPack.objects === 'object') { - for (const _id of Object.keys(ioPack.objects)) { - if (name === 'js-controller' && !_id.startsWith('_design/')) { + if (Array.isArray(ioPack.objects)) { + for (const obj of ioPack.objects) { + if (name === 'js-controller' && !obj._id.startsWith('_design/')) { continue; } - ioPack.objects[_id].from = `system.host.${hostname}.cli`; - ioPack.objects[_id].ts = Date.now(); + obj.from = `system.host.${hostname}.cli`; + obj.ts = Date.now(); try { - await this.objects.setObjectAsync(ioPack.objects[_id]._id, ioPack.objects[_id]); + await this.objects.setObjectAsync(obj._id, obj); } catch (err) { logger.error(`Cannot update object: ${err}`); } @@ -795,7 +801,7 @@ export class Upload { */ async upgradeAdapterObjects( name: string, - ioPack?: Record, + ioPack?: ioBroker.AdapterObject, _logger?: Logger | typeof console ): Promise { const logger = _logger || console; @@ -803,7 +809,7 @@ export class Upload { const adapterDir = tools.getAdapterDir(name); let ioPackFile; try { - ioPackFile = fs.readJSONSync(adapterDir + '/io-package.json'); + ioPackFile = fs.readJSONSync(`${adapterDir}/io-package.json`); } catch { if (adapterDir) { logger.error(`Cannot find io-package.json in ${adapterDir}`); @@ -815,8 +821,8 @@ export class Upload { ioPack = ioPack || ioPackFile; if (ioPack) { - // Always update installed From from File on disk if exists and set - if (ioPackFile && ioPackFile.common && ioPackFile.common.installedFrom) { + // Always update installedFrom from File on disk if exists and set + if (ioPackFile?.common?.installedFrom) { ioPack.common = ioPack.common || {}; ioPack.common.installedFrom = ioPackFile.common.installedFrom; } @@ -830,7 +836,9 @@ export class Upload { const obj: Omit = _obj || { common: ioPack.common, native: ioPack.native, - type: 'adapter' + type: 'adapter', + instanceObjects: [], + objects: [] }; obj.common = ioPack.common || {}; @@ -840,19 +848,15 @@ export class Upload { obj.encryptedNative = ioPack.encryptedNative || []; obj.notifications = ioPack.notifications || []; // update instanceObjects and objects - // @ts-expect-error TODO needs to be added to types obj.instanceObjects = ioPack.instanceObjects || []; - // @ts-expect-error TODO needs to be added to types obj.objects = ioPack.objects || []; obj.type = 'adapter'; - obj.common!.installedVersion = ioPack.common.version; + obj.common.installedVersion = ioPack.common.version; - // @ts-expect-error TODO needs to be added to types - if (obj.common!.news) { - // @ts-expect-error TODO needs to be added to types - delete obj.common!.news; // remove this information as it could be big, but it will be taken from repo + if (obj.common.news) { + delete obj.common.news; // remove this information as it could be big, but it will be taken from repo } const hostname = tools.getHostName(); diff --git a/packages/controller/src/main.ts b/packages/controller/src/main.ts index 7f3a38a946..058e225f67 100644 --- a/packages/controller/src/main.ts +++ b/packages/controller/src/main.ts @@ -2382,7 +2382,8 @@ async function startAdapterUpload(): Promise { // @ts-expect-error yes the logger is missing some levels await upload.uploadAdapter(uploadTasks[0].adapter, true, true, '', logger); - await upload.upgradeAdapterObjects(uploadTasks[0].adapter, logger); + // @ts-expect-error the logger is missing some levels + await upload.upgradeAdapterObjects(uploadTasks[0].adapter, undefined, logger); // @ts-expect-error yes the logger is missing some levels await upload.uploadAdapter(uploadTasks[0].adapter, false, true, '', logger); // send response to requester diff --git a/packages/controller/test/lib/testConsole.ts b/packages/controller/test/lib/testConsole.ts index b9720467c1..15866666ae 100644 --- a/packages/controller/test/lib/testConsole.ts +++ b/packages/controller/test/lib/testConsole.ts @@ -721,7 +721,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont installedVersion: '1.0.0' }, native: {}, - type: 'instance' + type: 'instance', + instanceObjects: [], + objects: [] }); // license must be taken res = await execAsync(`"${process.execPath}" "${iobExecutable}" license ${licenseFile}`); diff --git a/packages/controller/test/lib/testObjectsACL.ts b/packages/controller/test/lib/testObjectsACL.ts index 9e314912a5..0b5c9dae1c 100644 --- a/packages/controller/test/lib/testObjectsACL.ts +++ b/packages/controller/test/lib/testObjectsACL.ts @@ -96,7 +96,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont object: 1536, // 0600 owner: 'system.user.admin', ownerGroup: 'system.group.administrator' - } + }, + objects: [], + instanceObjects: [] }) ); }).timeout(2_000); diff --git a/packages/controller/test/lib/testObjectsFunctions.ts b/packages/controller/test/lib/testObjectsFunctions.ts index 558f95dafb..1015a1843f 100644 --- a/packages/controller/test/lib/testObjectsFunctions.ts +++ b/packages/controller/test/lib/testObjectsFunctions.ts @@ -329,7 +329,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont username: 'tesla', password: 'winning' }, - protectedNative: ['username', 'password'] + protectedNative: ['username', 'password'], + objects: [], + instanceObjects: [] }, function (err) { expect(err).to.be.null; @@ -372,7 +374,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont username: 'tesla', password: 'winning' }, - protectedNative: ['username', 'password'] + protectedNative: ['username', 'password'], + objects: [], + instanceObjects: [] }, function (err) { expect(err).to.be.null; @@ -1114,7 +1118,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont username: 'tesla', password: 'winning' }, - protectedNative: ['username', 'password'] + protectedNative: ['username', 'password'], + objects: [], + instanceObjects: [] }, function (err) { expect(err).to.be.null; @@ -1159,7 +1165,9 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont username: 'tesla', password: 'winning' }, - protectedNative: ['username', 'password'] + protectedNative: ['username', 'password'], + objects: [], + instanceObjects: [] }, function (err) { expect(err).to.be.null; diff --git a/packages/types-dev/objects.d.ts b/packages/types-dev/objects.d.ts index 1fa489c0eb..bfbbca8f21 100644 --- a/packages/types-dev/objects.d.ts +++ b/packages/types-dev/objects.d.ts @@ -456,6 +456,13 @@ declare global { type AutoUpgradePolicy = 'none' | 'patch' | 'minor' | 'major'; + interface VisWidget { + i18n: 'component' | true | Translated; + name: string; + url: string; + components: string[]; + } + interface AdapterCommon extends ObjectCommon { /** Custom attributes to be shown in admin in the object browser */ adminColumns?: any[]; @@ -502,6 +509,8 @@ declare global { getHistory?: boolean; /** Filename of the local icon which is shown for installed adapters. Should be located in the `admin` directory */ icon?: string; + /** Source, where this adapter has been installed from, to enable reinstall on e.g. backup restore */ + installedFrom?: string; /** Which version of this adapter is installed */ installedVersion: string; keywords?: string[]; @@ -525,6 +534,8 @@ declare global { mode: InstanceMode; /** Name of the adapter (without leading `ioBroker.`) */ name: string; + /** News per version in i18n */ + news?: Record>; /** If `true`, no configuration dialog will be shown */ noConfig?: true; /** If `true`, this adapter's instances will not be shown in the admin overview screen. Useful for icon sets and widgets... */ @@ -580,6 +591,7 @@ declare global { unsafePerm?: true; /** The available version in the ioBroker repo. */ version: string; + visWidgets?: Record; /** If `true`, the adapter will be started if any value is written into `system.adapter...wakeup. Normally the adapter should stop after processing the event. */ wakeup?: boolean; /** Include the adapter version in the URL of the web adapter, e.g. `http://ip:port/1.2.3/material` instead of `http://ip:port/material` */ @@ -721,6 +733,10 @@ declare global { encryptedNative?: string[]; /** Register notifications for the built-in notification system */ notifications?: Notification[]; + /** Objects created for each instance, inside the namespace of this adapter */ + instanceObjects: (StateObject | DeviceObject | ChannelObject | FolderObject | MetaObject)[]; + /** Objects created for the adapter, anywhere in the global namespace */ + objects: ioBroker.AnyObject[]; } interface PartialInstanceObject extends Partial> { common?: Partial; @@ -746,6 +762,10 @@ declare global { encryptedNative?: string[]; /** Register notifications for the built-in notification system */ notifications?: Notification[]; + /** Objects created for each instance, inside the namespace of this adapter */ + instanceObjects: (StateObject | DeviceObject | ChannelObject | FolderObject | MetaObject)[]; + /** Objects created for the adapter, anywhere in the global namespace */ + objects: ioBroker.AnyObject[]; } interface PartialAdapterObject extends Partial> { common?: Partial; diff --git a/packages/types-public/index.test-d.ts b/packages/types-public/index.test-d.ts index d27b527f3b..e76b730c06 100644 --- a/packages/types-public/index.test-d.ts +++ b/packages/types-public/index.test-d.ts @@ -783,7 +783,9 @@ const _adapterObject: ioBroker.AdapterObject = { }, version: '1.2.3', blockedVersions: ['~3.14.0', '4.0.1'] - } + }, + instanceObjects: [], + objects: [] }; const _folderObject: ioBroker.FolderObject = { @@ -820,7 +822,9 @@ const _instanceObject: ioBroker.InstanceObject = { materialize: true, installedVersion: '1.0.0' }, - native: {} + native: {}, + instanceObjects: [], + objects: [] }; const _userObject: ioBroker.UserObject = {