// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import '../base/file_system.dart'; import '../base/utils.dart'; import '../convert.dart'; /// A pseudo-filesystem stored in memory. /// /// To support output to arbitrary multi-root file schemes, the frontend server /// will output web sources, sourcemaps, and metadata to concatenated single files /// with an additional manifest file containing the correct offsets. class WebMemoryFS { final Map<String, Uint8List> metadataFiles = <String, Uint8List>{}; final Map<String, Uint8List> files = <String, Uint8List>{}; final Map<String, Uint8List> sourcemaps = <String, Uint8List>{}; String? get mergedMetadata => _mergedMetadata; String? _mergedMetadata; /// Update the filesystem with the provided source and manifest files. /// /// Returns the list of updated files. List<String> write( File codeFile, File manifestFile, File sourcemapFile, File metadataFile, ) { final List<String> modules = <String>[]; final Uint8List codeBytes = codeFile.readAsBytesSync(); final Uint8List sourcemapBytes = sourcemapFile.readAsBytesSync(); final Uint8List metadataBytes = metadataFile.readAsBytesSync(); final Map<String, dynamic> manifest = castStringKeyedMap(json.decode(manifestFile.readAsStringSync()))!; for (final String filePath in manifest.keys) { if (filePath == null) { continue; } final Map<String, dynamic> offsets = castStringKeyedMap(manifest[filePath])!; final List<int> codeOffsets = (offsets['code'] as List<dynamic>).cast<int>(); final List<int> sourcemapOffsets = (offsets['sourcemap'] as List<dynamic>).cast<int>(); final List<int> metadataOffsets = (offsets['metadata'] as List<dynamic>).cast<int>(); if (codeOffsets.length != 2 || sourcemapOffsets.length != 2 || metadataOffsets.length != 2) { continue; } final int codeStart = codeOffsets[0]; final int codeEnd = codeOffsets[1]; if (codeStart < 0 || codeEnd > codeBytes.lengthInBytes) { continue; } final Uint8List byteView = Uint8List.view( codeBytes.buffer, codeStart, codeEnd - codeStart, ); final String fileName = filePath.startsWith('/') ? filePath.substring(1) : filePath; files[fileName] = byteView; final int sourcemapStart = sourcemapOffsets[0]; final int sourcemapEnd = sourcemapOffsets[1]; if (sourcemapStart < 0 || sourcemapEnd > sourcemapBytes.lengthInBytes) { continue; } final Uint8List sourcemapView = Uint8List.view( sourcemapBytes.buffer, sourcemapStart, sourcemapEnd - sourcemapStart, ); final String sourcemapName = '$fileName.map'; sourcemaps[sourcemapName] = sourcemapView; final int metadataStart = metadataOffsets[0]; final int metadataEnd = metadataOffsets[1]; if (metadataStart < 0 || metadataEnd > metadataBytes.lengthInBytes) { continue; } final Uint8List metadataView = Uint8List.view( metadataBytes.buffer, metadataStart, metadataEnd - metadataStart, ); final String metadataName = '$fileName.metadata'; metadataFiles[metadataName] = metadataView; modules.add(fileName); } _mergedMetadata = metadataFiles.values .map((Uint8List encoded) => utf8.decode(encoded)) .join('\n'); return modules; } }