Browse Source

refactor(iot-hub): use blob object URLs for device package zip images

Switch from base64 data URLs to URL.createObjectURL for images extracted from the device package zip. Revokes them in ngOnDestroy to avoid leaking memory, and avoids embedding multi-megabyte data URLs in the DOM.
pull/15787/head
Igor Kulikov 4 months ago
parent
commit
402732377a
  1. 32
      ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts

32
ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Inject, OnInit, Type, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import {
@ -40,9 +40,6 @@ import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import { generateSecret } from '@core/utils';
import {
installMethodIcons as INSTALL_METHOD_ICONS,
installMethodLabels as INSTALL_METHOD_LABELS,
peOnlyInstallMethods,
DeviceInstallStep,
DevicePackageInfo,
ENTITY_STEP_TYPES,
@ -50,7 +47,10 @@ import {
EntityStepProgress,
FormFieldDefinition,
FormFieldType,
installMethodIcons as INSTALL_METHOD_ICONS,
installMethodLabels as INSTALL_METHOD_LABELS,
InstallStepType,
peOnlyInstallMethods,
stepTypeAliasMap
} from '@shared/models/iot-hub/device-package.models';
import { mergeMap } from 'rxjs/operators';
@ -89,7 +89,7 @@ const DEFAULT_RANDOM_SIZE = 20;
templateUrl: './device-install-dialog.component.html',
styleUrls: ['./device-install-dialog.component.scss']
})
export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInstallDialogComponent> implements OnInit {
export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInstallDialogComponent> implements OnInit, OnDestroy {
@ViewChild('installStepper', {static: false}) stepper: MatStepper;
@ -148,9 +148,9 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
if (!entry.dir) {
const ext = path.split('.').pop()?.toLowerCase();
if (imageExtensions.has(ext)) {
const base64 = await entry.async('base64');
const mimeType = ext === 'jpg' ? 'image/jpeg' : ext === 'svg' ? 'image/svg+xml' : `image/${ext}`;
this.zipImages.set(path, `data:${mimeType};base64,${base64}`);
const blob = await entry.async('blob');
const blobUrl = URL.createObjectURL(blob);
this.zipImages.set(path, blobUrl);
} else {
const content = await entry.async('string');
this.zipFiles.set(path, content);
@ -200,6 +200,13 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
this.cdr.detectChanges();
}
ngOnDestroy() {
for (const url of this.zipImages.values()) {
URL.revokeObjectURL(url);
}
super.ngOnDestroy();
}
// --- Connectivity ---
selectConnectivity(ct: string): void {
@ -237,8 +244,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
const ws = this.wizardSteps[index];
if (!ws) return;
if (ws.type === 'instruction' && !ws.markdown) {
const raw = this.zipFiles.get(ws.rawSteps[0].file) || '';
ws.markdown = raw;
ws.markdown = this.zipFiles.get(ws.rawSteps[0].file) || '';
} else if (ws.type === 'progress' && !ws.progressDone) {
this.showCompletedEntitySteps(ws);
}
@ -546,8 +552,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
// Pre-activate all steps for tab mode (tabs render lazily on first select)
for (const ws of this.wizardSteps) {
if (ws.type === 'instruction') {
const raw = this.zipFiles.get(ws.rawSteps[0].file) || '';
ws.markdown = raw;
ws.markdown = this.zipFiles.get(ws.rawSteps[0].file) || '';
} else if (ws.type === 'progress') {
this.showCompletedEntitySteps(ws);
}
@ -677,8 +682,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
return;
}
if (step.type === 'instruction') {
const raw = this.zipFiles.get(step.rawSteps[0].file) || '';
step.markdown = raw;
step.markdown = this.zipFiles.get(step.rawSteps[0].file) || '';
} else if (step.type === 'progress' && !step.progressDone) {
if (this.reviewMode) {
this.showCompletedEntitySteps(step);

Loading…
Cancel
Save