import type { BlipClient } from '../client.ts' import { BlipError } from '../sender/bliperror.ts' import { PluginSender } from '../sender/plugin/pluginsender.ts' import type { Tenant } from '../types/account.ts' import { type Identity, Node, type PossiblyNode } from '../types/node.ts' import type { Application, ApplicationUserInfo, TenantAgentInfo, TenantSubscription, TenantUserInfo, } from '../types/portal.ts' import { uri } from '../utils/uri.ts' import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts' export class PortalNamespace extends Namespace { constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) { super(blipClient, 'portal', { ...defaultOptions, domain: 'blip.ai' }) } public async getInstalledPlugins(opts?: ConsumeOptions) { const configurations = await this.blipClient.account.getConfigurations({ ...opts, ownerIdentity: this.identity, }) if ('Plugins' in configurations) { const plugins = JSON.parse(configurations.Plugins) as Record return Object.entries(plugins).map(([key, value]) => ({ id: key, name: value.name, url: value.url, })) } return [] } public async deletePlugin(id: string, opts?: ConsumeOptions): Promise public async deletePlugin(url: string | RegExp, opts?: ConsumeOptions): Promise public async deletePlugin(idOrUrl: string | RegExp, opts?: ConsumeOptions): Promise { const configurations = await this.blipClient.account.getConfigurations({ ...opts, ownerIdentity: this.identity, }) if ('Plugins' in configurations) { const plugins = JSON.parse(configurations.Plugins) as Record for (const id in plugins) { if ( (idOrUrl instanceof RegExp && idOrUrl.test(plugins[id].url)) || (typeof idOrUrl === 'string' && (idOrUrl === id || plugins[id].url === idOrUrl)) ) { delete plugins[id] } } return await this.blipClient.account.setConfigurations( { Plugins: JSON.stringify(plugins) }, { ...opts, ownerIdentity: this.identity }, ) } } public async addPlugin( id: string, settings: { name: string url: string }, opts?: ConsumeOptions, ): Promise { const configurations = await this.blipClient.account.getConfigurations({ ...opts, ownerIdentity: this.identity, }) const plugins = 'Plugins' in configurations ? JSON.parse(configurations.Plugins) : {} plugins[id] = settings return await this.blipClient.account.setConfigurations( { Plugins: JSON.stringify(plugins) }, { ...opts, ownerIdentity: this.identity }, ) } public async updatePlugin( id: string, settings: Partial<{ name: string url: string }>, opts?: ConsumeOptions, ): Promise { const configurations = await this.blipClient.account.getConfigurations({ ...opts, ownerIdentity: this.identity, }) if ('Plugins' in configurations) { const plugins = JSON.parse(configurations.Plugins) as Record if (id in plugins) { plugins[id] = { ...plugins[id], ...settings } return await this.blipClient.account.setConfigurations( { Plugins: JSON.stringify(plugins) }, { ...opts, ownerIdentity: this.identity }, ) } else { throw new Error(`Plugin with id "${id}" not found`) } } else { throw new Error('No plugins found') } } public getApplication(bot?: PossiblyNode, opts?: ConsumeOptions): Promise { if (bot) { return this.sendCommand( { method: 'get', uri: uri`/applications/${Node.from(bot, 'msging.net')}`, }, opts, ) } else { if (!this.blipClient.context.isOnPortal()) { throw new Error('This command is only allowed for portal senders') } const sender = PluginSender.check(this.blipClient) return sender.channel.post('getApplication', null) } } public getApplications(tenantId: string, opts?: ConsumeOptions): Promise> { return this.sendCommand( { method: 'get', uri: uri`/applications?${{ tenantId }}`, }, { collection: true, // This route doesn't handle pagination correctly, it will always fetch all fetchall: false, ...opts, }, ) } /** @returns all applications where the current user is an admin */ public getAdminApplications(tenantId?: string, opts?: ConsumeOptions): Promise> { return this.sendCommand( { method: 'get', uri: uri`/applications-admin?${{ tenantId }}`, }, { collection: true, // This route doesn't handle pagination correctly, it will always fetch all fetchall: false, ...opts, }, ) } public async getParentApplications(): Promise> { const current = await this.getApplication() const applications = await this.getApplications(current.tenantId) const parentApps = await Promise.all( applications.map(async (app) => { const client = this.blipClient.as(app.shortName) const template = await client.account.getTemplateType() if (template === 'master') { const children = await client.account.getChildren() if (children.some((child) => child.identifier === current.shortName)) { return app } } // Return undefined if the app is not a parent return undefined }), ) return parentApps.filter((app) => app !== undefined) } public async getTenant(tenantId: string): Promise { return await this.sendCommand({ method: 'get', uri: uri`/tenants/${tenantId}`, }) } public async getTenantActiveMessagesMetrics( tenantId: string, interval: 'D' | 'M' | 'NI', filters?: { applicationIdentity?: Identity startDate?: Date | string endDate?: Date | string }, opts?: ConsumeOptions, ): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/metrics/${tenantId}/active-messages/${interval}?${filters}`, }, { collection: true, ...opts, }, ) } public async getTenantReceivedMessagesMetrics( tenantId: string, interval: 'D' | 'M' | 'NI', filters?: { applicationIdentity?: Identity startDate?: Date | string endDate?: Date | string }, opts?: ConsumeOptions, ): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/metrics/${tenantId}/received-messages/${interval}?${filters}`, }, { collection: true, ...opts, }, ) } public async getTenantSentMessagesMetrics( tenantId: string, interval: 'D' | 'M' | 'NI', filters?: { applicationIdentity?: Identity startDate?: Date | string endDate?: Date | string }, opts?: ConsumeOptions, ): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/metrics/${tenantId}/sent-messages/${interval}?${filters}`, }, { collection: true, ...opts, }, ) } public async getTenantActiveUsersMetrics( tenantId: string, interval: 'D' | 'M' | 'NI', filters?: { applicationIdentity?: Identity startDate?: Date | string endDate?: Date | string }, opts?: ConsumeOptions, ): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/metrics/${tenantId}/active-users/${interval}?${filters}`, }, { collection: true, ...opts, }, ) } public async getTenantEngagedUsersMetrics( tenantId: string, interval: 'D' | 'M' | 'NI', filters?: { applicationIdentity?: Identity startDate?: Date | string endDate?: Date | string }, opts?: ConsumeOptions, ): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/metrics/${tenantId}/engaged-users/${interval}?${filters}`, }, { collection: true, ...opts, }, ) } public async tenantExists(tenantId: string): Promise { try { await this.sendCommand({ method: 'get', uri: uri`/tenants/${tenantId}`, }) return true } catch (err) { if (err instanceof BlipError) { if (err.code === 67) { return false } else if (err.code === 35) { return true } } throw err } } public async getTenants(opts?: ConsumeOptions): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/tenants-mine`, }, { collection: true, ...opts, }, ) } public async getTenantUsers( tenantId: string, opts?: ConsumeOptions, ): Promise>> { return await this.sendCommand( { method: 'get', uri: uri`/tenants/${tenantId}/users`, }, { collection: true, ...opts, }, ) } public async getTenantUser(tenantId: string, user: Identity, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'get', uri: uri`/tenants/${tenantId}/users/${user}`, }, opts, ) } public async getTenantSubscription(tenantId: string, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'get', uri: uri`/tenants/${tenantId}/subscription`, }, opts, ) } public async deleteTenantUser(tenantId: string, user: Identity, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'delete', uri: uri`/tenants/${tenantId}/users/${user}`, }, opts, ) } public async setTenantUser( tenantId: string, user: Identity, roleId: TenantUserInfo['roleId'], opts?: ConsumeOptions, ): Promise { return await this.sendCommand( { method: 'set', uri: uri`/tenants/${tenantId}/users`, type: 'application/vnd.lime.collection+json', resource: { items: [ { tenantId, userIdentity: user, roleId, }, ], itemType: 'application/vnd.iris.portal.tenant-user+json', }, }, opts, ) } public async getApplicationUsers(bot: PossiblyNode, opts?: ConsumeOptions): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/applications/${Node.from(bot, 'msging.net')}/users`, }, { collection: true, ...opts, }, ) } public async getApplicationUser( bot: PossiblyNode, user: Identity, opts?: ConsumeOptions, ): Promise { return await this.sendCommand( { method: 'get', uri: uri`/applications/${Node.from(bot, 'msging.net')}/users/${user}`, }, opts, ) } public async deleteApplicationUser(bot: PossiblyNode, user: Identity, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'delete', uri: uri`/applications/${Node.from(bot, 'msging.net')}/users/${user}`, }, opts, ) } public async setApplicationUser( bot: PossiblyNode, user: Identity, roleId: TenantUserInfo['roleId'], tenantId: string, opts?: ConsumeOptions, ): Promise { await this.sendCommand( { method: 'set', uri: uri`/applications/${Node.from(bot, 'msging.net')}/users?${{ tenantId }}`, type: 'application/vnd.lime.identity', resource: user, }, opts, ) await this.updateApplicationUserPermissions(bot, user, roleId, opts) } public async updateApplicationUserPermissions( bot: PossiblyNode, user: Identity, roleId: TenantUserInfo['roleId'], opts?: ConsumeOptions, ): Promise { const actions: Array<'read' | 'write'> = ['read'] const otherPermissions: Array<{ permissionId: string actions: typeof actions userIdentity: typeof user }> = [] if (roleId !== 'guest') { actions.push('write') } if (roleId === 'admin') { otherPermissions.push({ permissionId: 'team', actions, userIdentity: user, }) } return await this.sendCommand( { method: 'set', uri: uri`/applications/${Node.from(bot, 'msging.net')}/permissions`, type: 'application/vnd.lime.collection+json', resource: { itemType: 'application/vnd.iris.portal.user-permission+json', items: [ ...otherPermissions, { permissionId: 'payments', actions, userIdentity: user }, { permissionId: 'ai-providers', actions, userIdentity: user }, { permissionId: 'ai-model', actions, userIdentity: user }, { permissionId: 'ai-enhancement', actions, userIdentity: user }, { permissionId: 'ai-answers', actions, userIdentity: user }, { permissionId: 'channels', actions, userIdentity: user }, { permissionId: 'desk', actions, userIdentity: user }, { permissionId: 'users', actions, userIdentity: user }, { permissionId: 'scheduler', actions, userIdentity: user }, { permissionId: 'config-basicConfigurations', actions, userIdentity: user }, { permissionId: 'config-connectionInformation', actions, userIdentity: user }, { permissionId: 'resources', actions, userIdentity: user }, { permissionId: 'logMessages', actions, userIdentity: user }, { permissionId: 'builder', actions, userIdentity: user }, { permissionId: 'analysis', actions, userIdentity: user }, ], }, }, opts, ) } public async getTenantAgents(tenantId: string, opts?: ConsumeOptions): Promise> { return await this.sendCommand( { method: 'get', uri: uri`/tenants/${tenantId}/agents`, }, { collection: true, ...opts, }, ) } public async getTenantAgent(tenantId: string, agent: Identity, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'get', uri: uri`/tenants/${tenantId}/agents/${agent}`, }, opts, ) } public async setTenantAgent(tenantId: string, agents: Array, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'set', uri: uri`/tenants/${tenantId}/agents`, type: 'application/vnd.lime.collection+json', resource: { items: agents.map((agent) => ({ tenantId, userIdentity: agent, })), itemType: 'application/vnd.iris.portal.tenant-agent-user+json', }, }, opts, ) } public async deleteTenantAgent(tenantId: string, agent: Identity, opts?: ConsumeOptions): Promise { return await this.sendCommand( { method: 'delete', uri: uri`/tenants/${tenantId}/agents/${agent}`, }, opts, ) } }