UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

262 lines (258 loc) 11.1 kB
import * as i0 from '@angular/core'; import { Injectable } from '@angular/core'; import * as i2 from '@c8y/client'; import { ApplicationType, ApplicationAvailability } from '@c8y/client'; import * as i1 from '@c8y/ngx-components'; class StaticAssetsService { constructor(appstate, appService, fetchClient, fileService, zip) { this.appstate = appstate; this.appService = appService; this.fetchClient = fetchClient; this.fileService = fileService; this.zip = zip; this.fileNames = { contents: 'contents.json', manifest: 'cumulocity.json', exportZipName: 'static-assets.zip' }; this.listFilesCachePromises = {}; } /** * Lists all files of the given type. * Forces a cache renewal. */ async listFiles(type) { return this.listFilesCached(type, true); } /** * Lists all files of the given type. * Uses a cache. */ async listFilesCached(type, forceCacheRenewal = false) { if (!this.listFilesCachePromises[type] || forceCacheRenewal) { this.listFilesCachePromises[type] = this._listFiles(type); } return await this.listFilesCachePromises[type]; } /** * Clones all assets of the given type from parent tenants into the current tenant. */ async cloneAssetsIntoTenant(type) { const { data: apps } = await this.appService.list({ availability: ApplicationAvailability.SHARED, type: ApplicationType.HOSTED, pageSize: 2000 }); const tenantIdsFromOtherAssetApps = apps .filter(app => { const result = app.owner?.tenant?.id && app.owner?.tenant?.id !== this.appstate.currentTenant.value?.name && app.contextPath === this.getContextPath(type, app.owner?.tenant?.id); return result; }) .map(app => app.owner?.tenant?.id); let filesOfCurrentTenant = await this._listFiles(type); let filesToUpload = new Array(); const oldAssets = new Array(); for (const tenantId of tenantIdsFromOtherAssetApps) { try { const filesOfApp = await this.getContentJSONForType(type, tenantId); oldAssets.push(...filesOfApp); for (const file of filesOfApp) { try { const response = await this.fetchClient.fetch(file.path); if (response.status !== 200) { console.warn(`Failed to get file "${file.fileName}" from path ${file.path}`); continue; } const fileContent = await response.blob(); // remove existing file with same name filesOfCurrentTenant = filesOfCurrentTenant.filter(existingFile => existingFile.fileName !== file.fileName); filesToUpload = filesToUpload.filter(existingFile => existingFile.name !== file.fileName); filesToUpload.push(new File([fileContent], file.fileName, { type: file.type })); } catch (e) { console.warn(`Failed to add file "${file.fileName}" from tenant ${tenantId}`, e); } } } catch (e) { console.warn(`Failed to get asset files from tenant ${tenantId}`); } } if (!filesToUpload.length) { return; } const newAssets = await this.addFilesToStaticAssets(type, filesToUpload, filesOfCurrentTenant); return { oldAssets, newAssets }; } /** * Adds the given files to the static assets of the given type. */ async addFilesToStaticAssets(type, files, existingFiles, throwErrorOnExistingFiles = true) { const app = await this.getAppForTenant(type); const filesToAddToContents = new Array(); const addedAt = new Date().toISOString(); const filesToUpload = []; for (const droppedFile of files) { const file = droppedFile instanceof File ? droppedFile : droppedFile.file; const fileName = file.name.toLowerCase().replace(/[^a-zA-Z0-9\-\_\.]/g, ''); const newFile = new File([file], fileName, { type: file.type, lastModified: file.lastModified }); if (existingFiles.find(existingFile => existingFile.fileName === fileName)) { if (throwErrorOnExistingFiles) { throw Error(`File with name "${fileName}" is already existing.`); } existingFiles = existingFiles.filter(existingFile => existingFile.fileName !== fileName); } const fileExtension = file.name.split('.').pop(); filesToAddToContents.push({ addedAt, lastModified: file.lastModified, extension: fileExtension, fileName: fileName, originalFileName: file.name, path: `/apps/public/${app.contextPath}/${fileName}`, size: newFile.size, type: newFile.type, hashSum: await this.fileService.getHashSumOfFile(newFile) }); filesToUpload.push({ contents: newFile, path: fileName }); } const newFiles = [...existingFiles, ...filesToAddToContents]; filesToUpload.push({ contents: new File([JSON.stringify(newFiles)], this.fileNames.contents, { type: 'application/json' }), path: this.fileNames.contents }); await this.appService.binary(app).updateFiles(filesToUpload); await this.ensureAddedFilesArePresent(filesToAddToContents); return newFiles; } /** * Gets the static assets app for the given type. */ async getAppForTenant(type) { const appName = this.getContextPath(type, false); const { data: apps } = await this.appService.listByName(appName); if (!apps.length) { throw Error(`No app with name ${appName} found.`); } return apps[0]; } getContentJSONForType(type, appendTenantId) { const path = this.getPathForContent(type, appendTenantId); return this.getContentJSONFromPath(path); } async getContentJSONFromPath(path) { const response = await this.fetchClient.fetch(path); if (response.status !== 200) { throw Error(`Failed to retrieve ${path}`); } const content = await response.json(); return content; } async ensureAddedFilesArePresent(files) { for (const file of files) { await this.ensureFileIsPresent(file); } } async ensureFileIsPresent(file) { for (let i = 0; i < 12; i++) { try { const currentDate = new Date(); const response = await this.fetchClient.fetch(`${file.path}?nocache=${currentDate.getTime()}`); if (response.status !== 200) { await this.sleep(5_000); continue; } const blob = await response.blob(); const hashSum = await this.fileService.getHashSumOfFile(blob); if (hashSum !== file.hashSum) { await this.sleep(5_000); continue; } return; } catch (e) { console.warn(e); continue; } } throw Error(`Unable to retrieve file from ${file.path}`); } sleep(duration) { return new Promise(resolve => setTimeout(() => resolve(), duration)); } async _listFiles(type) { try { const files = await this.getContentJSONForType(type); return files; } catch (e) { console.warn(e); } return await this.createEmptyApp(type); } getPathForContent(type, appendTenantId) { const currentDate = new Date(); const contextPath = this.getContextPath(type, appendTenantId); return `/apps/public/${contextPath}/${this.fileNames.contents}?nocache=${currentDate.getTime()}`; } getContextPath(type, appendTenantId) { let contextPath = type + '-assets'; const tenantId = appendTenantId === undefined ? this.appstate.currentTenant.value?.name : appendTenantId; if (tenantId) { contextPath = `${contextPath}-${tenantId}`; } return contextPath; } async createEmptyApp(type, appendTenantId) { const name = this.getContextPath(type, false); const contextPath = this.getContextPath(type, appendTenantId); const key = `${contextPath}-key`; const manifest = { name, contextPath, key, description: `Application containing static assets used for ${type}.`, noAppSwitcher: true, type: ApplicationType.HOSTED, availability: ApplicationAvailability.MARKET }; const content = []; const zipFile = await this.zip.createZip([ { fileName: `${this.fileNames.manifest}`, blob: new Blob([JSON.stringify(manifest)]) }, { fileName: `${this.fileNames.contents}`, blob: new Blob([JSON.stringify(content)]) } ]); const { data: app } = await this.appService.create(manifest); const { data: appBinary } = await this.appService .binary(app) .upload(zipFile, `empty-${name}.zip`); await this.appService.update({ id: app.id, activeVersionId: appBinary.id }); return []; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: StaticAssetsService, deps: [{ token: i1.AppStateService }, { token: i2.ApplicationService }, { token: i2.FetchClient }, { token: i1.FilesService }, { token: i1.ZipService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: StaticAssetsService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: StaticAssetsService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.AppStateService }, { type: i2.ApplicationService }, { type: i2.FetchClient }, { type: i1.FilesService }, { type: i1.ZipService }] }); /** * Generated bundle index. Do not edit. */ export { StaticAssetsService }; //# sourceMappingURL=c8y-ngx-components-static-assets-data.mjs.map