Skip to content
Snippets Groups Projects
Select Git revision
  • ee4e0094a01d11a648bad75a637f642f409c74b3
  • main default protected
2 results

plugins.manager.service.ts

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    plugins.manager.service.ts 9.69 KiB
    /*
     *  Copyright (C) 2023 Savoir-faire Linux Inc.
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    import {Service} from 'typedi';
    import {FileManagerService} from './file.manager.service';
    import {type Plugins} from '../interfaces/plugins';
    import {TranslationService} from './translation.service';
    import {CertificateManager} from './certificate.manager.service';
    import {ArchitectureManager} from './architecture.manager';
    import {basename, extname} from 'path';
    
    @Service()
    export class PluginsManager {
      private plugins: Plugins[] = [];
      constructor(
        private readonly architectureManager: ArchitectureManager,
        private readonly translation: TranslationService,
        private readonly fileManager: FileManagerService,
        private readonly certificateManager: CertificateManager
      ) {
        if (process.env.DATA_DIRECTORY === undefined) {
          return;
        }
        const watcher = this.fileManager.watchFile(
          // eslint-disable-next-line
          __dirname + '/../..' + process.env.DATA_DIRECTORY
        );
        if (watcher === undefined) {
          return;
        }
        // eslint-disable-next-line
        watcher.on('change', (_, filename: string | Buffer) => {
          const filePath =
            // eslint-disable-next-line
            __dirname +
            '/../..' +
            process.env.DATA_DIRECTORY +
            '/' +
            filename.toString();
          // check if the file signature is valid and not in the list
          this.checkPluginAlreadyPull(filePath).then(isValid => {
            if (!isValid) {
              return;
            }
            this.setPlugins().catch(e => {
              console.log(e);
            });
          });
        });
      }
    
      // need eslint disable because of the refactoring
      async getPlugins(
        arch: string,
        lang: string
      ): Promise<Array<Omit<Omit<Plugins, 'arches'>, 'signature'>>> {
        if (this.plugins.length === 0) {
          await this.setPlugins();
        }
        return await Promise.all(
          this.plugins
            .filter(plugin => this.isPluginAvailable(plugin.id, arch))
            .map(async (plugin: Plugins) => {
              return {
                id: plugin.id,
                name: await this.translation.formatText(
                  plugin.id,
                  lang,
                  plugin.name,
                  await this.getPluginPath(plugin.id, arch)
                ),
                version: plugin.version,
                description: await this.translation.formatText(
                  plugin.id,
                  lang,
                  plugin.description,
                  await this.getPluginPath(plugin.id, arch)
                ),
                icon: plugin.icon,
                background: plugin.background,
                timestamp: plugin.timestamp,
                author: plugin.author,
              };
            })
        );
      }
    
      async getPlugin(
        id: string,
        lang: string
      ): Promise<
        | {
            id: string;
            name: string;
            version: string;
            description: string;
            icon: string | undefined;
            background: string | undefined;
            author: string;
            timestamp: string;
          }
        | undefined
      > {
        if (this.plugins.length === 0) {
          await this.setPlugins();
        }
        const plugin = this.plugins.find((plugin: Plugins) => plugin.id === id);
    
        return plugin === undefined
          ? undefined
          : {
              id: plugin.id,
              name: await this.translation.formatText(
                id,
                lang,
                plugin.name,
                await this.getPluginPath(id, plugin.arches[0])
              ),
              version: plugin.version,
              description: await this.translation.formatText(
                id,
                lang,
                plugin.description,
                await this.getPluginPath(id, plugin.arches[0])
              ),
              icon: plugin.icon,
              background: plugin.background,
              author: plugin.author,
              timestamp: plugin.timestamp,
            };
      }
    
      async findPlugin(id: string, arch: string): Promise<Plugins | undefined> {
        if (this.plugins.length === 0) {
          await this.setPlugins();
        }
        return this.plugins.find(
          (plugin: Plugins) =>
            plugin.id === id && plugin.arches.includes(arch) && arch !== undefined
        );
      }
    
      async getPluginPath(id: string, arch: string): Promise<string | undefined> {
        if (this.plugins.length === 0) {
          await this.setPlugins();
        }
        const plugin = this.plugins.find((plugin: Plugins) => plugin.id === id);
        if (
          plugin === undefined ||
          !this.isPluginAvailable(id, arch) ||
          process.env.DATA_DIRECTORY === undefined
        ) {
          return undefined;
        }
        const platform = this.architectureManager.getPlatform(arch);
        if (platform === undefined) {
          return undefined;
        }
        return (
          // eslint-disable-next-line
          __dirname +
          '/../..' +
          process.env.DATA_DIRECTORY +
          '/' +
          id +
          '/' +
          platform +
          '/' +
          id +
          '.jpl'
        );
      }
    
      private isPluginAvailable(id: string, arch: string): boolean {
        const plugin = this.plugins.find((plugin: Plugins) => {
          return plugin.id === id && plugin.arches.includes(arch);
        });
        return plugin !== undefined;
      }
    
      async getNewPlugin(
        path: string,
        platforms: string[]
      ): Promise<Plugins | undefined> {
        try {
          const plugin = await this.readManifest(path);
          const timestamp = (await this.fileManager.getStat(path)).mtime.toString();
          const issuer = await this.certificateManager.getIssuer(
            path,
            basename(path, extname(path)) + '.crt'
          );
          const arches = await this.architectureManager.getAllPluginArches(path);
          if (issuer === undefined || arches === undefined) {
            return;
          }
          this.architectureManager.addPluginArch(platforms, arches);
          return {
            id: plugin.id,
            name: plugin.name,
            version: plugin.version,
            description: plugin.description,
            icon: plugin.icon,
            arches,
            timestamp,
            author: issuer.CN,
            background: plugin.background,
            signature: await this.fileManager.readArchive(path, 'signatures.sig'),
          };
        } catch (e) {
          console.error(e);
        }
        // eslint-disable-next-line
        return;
      }
    
      removePlugin(id: string): void {
        this.plugins = this.plugins.filter((plugin: Plugins) => plugin.id !== id);
      }
    
      private async readManifest(path: string): Promise<{
        id: string;
        name: string;
        version: string;
        description: string;
        icon: string;
        background: string;
      }> {
        const manifest = JSON.parse(
          (await this.fileManager.readArchive(path, 'manifest.json')).toString()
        );
        return {
          id: manifest.id === undefined ? manifest.name : manifest.id,
          name: manifest.name,
          version: manifest.version,
          description: manifest.description,
          icon: manifest.iconPath,
          background: manifest.backgroundPath,
        };
      }
    
      async getVersions(): Promise<Array<{id: string; version: string}>> {
        if (this.plugins.length === 0) {
          await this.setPlugins();
        }
        const versions: Array<{id: string; version: string}> = [];
        for (const plugin of this.plugins) {
          const version = plugin.version;
          if (version !== undefined) {
            versions.push({
              id: plugin.id,
              version,
            });
          }
        }
        return versions;
      }
    
      private async setPlugins(): Promise<void> {
        let dataDirectory = process.env.DATA_DIRECTORY;
        if (dataDirectory === undefined) {
          return;
        }
        // eslint-disable-next-line
        dataDirectory = __dirname + '/../..' + dataDirectory;
        const plugins: Plugins[] = [];
        const pluginsPath = await this.fileManager.listFiles(dataDirectory);
        if (pluginsPath === undefined || pluginsPath.length === 0) {
          return;
        }
        for (const pluginPath of pluginsPath) {
          const platformPaths = await this.fileManager.listFiles(
            dataDirectory + '/' + pluginPath
          );
          if (platformPaths === undefined || pluginPath.length === 0) {
            return;
          }
          for (const platformPath of platformPaths) {
            const plugin = await this.getNewPlugin(
              dataDirectory +
                '/' +
                pluginPath +
                '/' +
                platformPath +
                '/' +
                pluginPath +
                '.jpl',
              [platformPath]
            );
            // TODO: should refactor this because it's possible that two plugins in different platform have the same id
            if (plugin === undefined) {
              continue;
            }
            plugins.push(plugin);
          }
        }
        this.plugins = plugins;
      }
    
      private async checkPluginAlreadyPull(pluginPath: string): Promise<boolean> {
        const id = pluginPath.split('/').at(-1)?.split('.').at(0);
        const arches = await this.architectureManager.getAllPluginArches(
          pluginPath
        );
        if (id === undefined || arches === undefined) {
          return false;
        }
        const pluginAlreadyInstalled = await this.findPlugin(id, arches[0]);
        if (pluginAlreadyInstalled === undefined) {
          return true;
        }
        const signature = await this.fileManager.readArchive(
          pluginPath,
          'signatures.sig'
        );
        return !this.certificateManager.verifySignature(
          pluginAlreadyInstalled.signature,
          signature
        );
      }
    }