@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
262 lines (258 loc) • 11.1 kB
JavaScript
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