UNPKG

@codesandbox/sandpack-client

Version:

<img style="width:100%" src="https://user-images.githubusercontent.com/4838076/143581035-ebee5ba2-9cb1-4fe8-a05b-2f44bd69bb4b.gif" alt="Component toolkit for live running code editing experiences" />

769 lines 29 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var file_system_1 = require("../core/file_system"); var api_error_1 = require("../core/api_error"); var file_flag_1 = require("../core/file_flag"); var util_1 = require("../core/util"); var file_1 = require("../core/file"); var node_fs_stats_1 = require("../core/node_fs_stats"); var preload_file_1 = require("../generic/preload_file"); var global_1 = require("../core/global"); var node_fs_1 = require("../core/node_fs"); /** * @hidden */ var SpecialArgType; (function (SpecialArgType) { // Callback SpecialArgType[SpecialArgType["CB"] = 0] = "CB"; // File descriptor SpecialArgType[SpecialArgType["FD"] = 1] = "FD"; // API error SpecialArgType[SpecialArgType["API_ERROR"] = 2] = "API_ERROR"; // Stats object SpecialArgType[SpecialArgType["STATS"] = 3] = "STATS"; // Initial probe for file system information. SpecialArgType[SpecialArgType["PROBE"] = 4] = "PROBE"; // FileFlag object. SpecialArgType[SpecialArgType["FILEFLAG"] = 5] = "FILEFLAG"; // Buffer object. SpecialArgType[SpecialArgType["BUFFER"] = 6] = "BUFFER"; // Generic Error object. SpecialArgType[SpecialArgType["ERROR"] = 7] = "ERROR"; })(SpecialArgType || (SpecialArgType = {})); /** * Converts callback arguments into ICallbackArgument objects, and back * again. * @hidden */ var CallbackArgumentConverter = /** @class */ (function () { function CallbackArgumentConverter() { this._callbacks = {}; this._nextId = 0; } CallbackArgumentConverter.prototype.toRemoteArg = function (cb) { var id = this._nextId++; this._callbacks[id] = cb; return { type: SpecialArgType.CB, id: id }; }; CallbackArgumentConverter.prototype.toLocalArg = function (id) { var cb = this._callbacks[id]; delete this._callbacks[id]; return cb; }; return CallbackArgumentConverter; }()); /** * @hidden */ var FileDescriptorArgumentConverter = /** @class */ (function () { function FileDescriptorArgumentConverter() { this._fileDescriptors = {}; this._nextId = 0; } FileDescriptorArgumentConverter.prototype.toRemoteArg = function (fd, p, flag, cb) { var id = this._nextId++; var data; var stat; this._fileDescriptors[id] = fd; // Extract needed information asynchronously. fd.stat(function (err, stats) { if (err) { cb(err); } else { stat = bufferToTransferrableObject(stats.toBuffer()); // If it's a readable flag, we need to grab contents. if (flag.isReadable()) { fd.read(Buffer.alloc(stats.size), 0, stats.size, 0, function (err, bytesRead, buff) { if (err) { cb(err); } else { data = bufferToTransferrableObject(buff); cb(null, { type: SpecialArgType.FD, id: id, data: data, stat: stat, path: p, flag: flag.getFlagString() }); } }); } else { // File is not readable, which means writing to it will append or // truncate/replace existing contents. Return an empty arraybuffer. cb(null, { type: SpecialArgType.FD, id: id, data: new ArrayBuffer(0), stat: stat, path: p, flag: flag.getFlagString() }); } } }); }; FileDescriptorArgumentConverter.prototype.applyFdAPIRequest = function (request, cb) { var _this = this; var fdArg = request.args[0]; this._applyFdChanges(fdArg, function (err, fd) { if (err) { cb(err); } else { // Apply method on now-changed file descriptor. fd[request.method](function (e) { if (request.method === 'close') { delete _this._fileDescriptors[fdArg.id]; } cb(e); }); } }); }; FileDescriptorArgumentConverter.prototype._applyFdChanges = function (remoteFd, cb) { var fd = this._fileDescriptors[remoteFd.id], data = transferrableObjectToBuffer(remoteFd.data), remoteStats = node_fs_stats_1.default.fromBuffer(transferrableObjectToBuffer(remoteFd.stat)); // Write data if the file is writable. var flag = file_flag_1.FileFlag.getFileFlag(remoteFd.flag); if (flag.isWriteable()) { // Appendable: Write to end of file. // Writeable: Replace entire contents of file. fd.write(data, 0, data.length, flag.isAppendable() ? fd.getPos() : 0, function (e) { function applyStatChanges() { // Check if mode changed. fd.stat(function (e, stats) { if (e) { cb(e); } else { if (stats.mode !== remoteStats.mode) { fd.chmod(remoteStats.mode, function (e) { cb(e, fd); }); } else { cb(e, fd); } } }); } if (e) { cb(e); } else { // If writeable & not appendable, we need to ensure file contents are // identical to those from the remote FD. Thus, we truncate to the // length of the remote file. if (!flag.isAppendable()) { fd.truncate(data.length, function () { applyStatChanges(); }); } else { applyStatChanges(); } } }); } else { cb(null, fd); } }; return FileDescriptorArgumentConverter; }()); /** * @hidden */ function apiErrorLocal2Remote(e) { return { type: SpecialArgType.API_ERROR, errorData: bufferToTransferrableObject(e.writeToBuffer()) }; } /** * @hidden */ function apiErrorRemote2Local(e) { return api_error_1.ApiError.fromBuffer(transferrableObjectToBuffer(e.errorData)); } /** * @hidden */ function errorLocal2Remote(e) { return { type: SpecialArgType.ERROR, name: e.name, message: e.message, stack: e.stack }; } /** * @hidden */ function errorRemote2Local(e) { var cnstr = global_1.default[e.name]; if (typeof (cnstr) !== 'function') { cnstr = Error; } var err = new cnstr(e.message); err.stack = e.stack; return err; } /** * @hidden */ function statsLocal2Remote(stats) { return { type: SpecialArgType.STATS, statsData: bufferToTransferrableObject(stats.toBuffer()) }; } /** * @hidden */ function statsRemote2Local(stats) { return node_fs_stats_1.default.fromBuffer(transferrableObjectToBuffer(stats.statsData)); } /** * @hidden */ function fileFlagLocal2Remote(flag) { return { type: SpecialArgType.FILEFLAG, flagStr: flag.getFlagString() }; } /** * @hidden */ function fileFlagRemote2Local(remoteFlag) { return file_flag_1.FileFlag.getFileFlag(remoteFlag.flagStr); } /** * @hidden */ function bufferToTransferrableObject(buff) { return (0, util_1.buffer2ArrayBuffer)(buff); } /** * @hidden */ function transferrableObjectToBuffer(buff) { return (0, util_1.arrayBuffer2Buffer)(buff); } /** * @hidden */ function bufferLocal2Remote(buff) { return { type: SpecialArgType.BUFFER, data: bufferToTransferrableObject(buff) }; } /** * @hidden */ function bufferRemote2Local(buffArg) { return transferrableObjectToBuffer(buffArg.data); } /** * @hidden */ function isAPIRequest(data) { return data && typeof data === 'object' && data.hasOwnProperty('browserfsMessage') && data['browserfsMessage']; } /** * @hidden */ function isAPIResponse(data) { return data && typeof data === 'object' && data.hasOwnProperty('browserfsMessage') && data['browserfsMessage']; } /** * Represents a remote file in a different worker/thread. */ var WorkerFile = /** @class */ (function (_super) { __extends(WorkerFile, _super); function WorkerFile(_fs, _path, _flag, _stat, remoteFdId, contents) { var _this = _super.call(this, _fs, _path, _flag, _stat, contents) || this; _this._remoteFdId = remoteFdId; return _this; } WorkerFile.prototype.getRemoteFdId = function () { return this._remoteFdId; }; /** * @hidden */ WorkerFile.prototype.toRemoteArg = function () { return { type: SpecialArgType.FD, id: this._remoteFdId, data: bufferToTransferrableObject(this.getBuffer()), stat: bufferToTransferrableObject(this.getStats().toBuffer()), path: this.getPath(), flag: this.getFlag().getFlagString() }; }; WorkerFile.prototype.sync = function (cb) { this._syncClose('sync', cb); }; WorkerFile.prototype.close = function (cb) { this._syncClose('close', cb); }; WorkerFile.prototype._syncClose = function (type, cb) { var _this = this; if (this.isDirty()) { this._fs.syncClose(type, this, function (e) { if (!e) { _this.resetDirty(); } cb(e); }); } else { cb(); } }; return WorkerFile; }(preload_file_1.default)); /** * WorkerFS lets you access a BrowserFS instance that is running in a different * JavaScript context (e.g. access BrowserFS in one of your WebWorkers, or * access BrowserFS running on the main page from a WebWorker). * * For example, to have a WebWorker access files in the main browser thread, * do the following: * * MAIN BROWSER THREAD: * * ```javascript * // Listen for remote file system requests. * BrowserFS.FileSystem.WorkerFS.attachRemoteListener(webWorkerObject); * ``` * * WEBWORKER THREAD: * * ```javascript * // Set the remote file system as the root file system. * BrowserFS.configure({ fs: "WorkerFS", options: { worker: self }}, function(e) { * // Ready! * }); * ``` * * Note that synchronous operations are not permitted on the WorkerFS, regardless * of the configuration option of the remote FS. */ var WorkerFS = /** @class */ (function (_super) { __extends(WorkerFS, _super); /** * Constructs a new WorkerFS instance that connects with BrowserFS running on * the specified worker. */ function WorkerFS(worker) { var _this = _super.call(this) || this; _this._callbackConverter = new CallbackArgumentConverter(); _this._isInitialized = false; _this._isReadOnly = false; _this._supportLinks = false; _this._supportProps = false; _this._worker = worker; _this._worker.addEventListener('message', function (e) { var resp = e.data; if (isAPIResponse(resp)) { var i = void 0; var args = resp.args; var fixedArgs = new Array(args.length); // Dispatch event to correct id. for (i = 0; i < fixedArgs.length; i++) { fixedArgs[i] = _this._argRemote2Local(args[i]); } _this._callbackConverter.toLocalArg(resp.cbId).apply(null, fixedArgs); } }); return _this; } WorkerFS.Create = function (opts, cb) { var fs = new WorkerFS(opts.worker); fs._initialize(function () { cb(null, fs); }); }; WorkerFS.isAvailable = function () { return typeof (importScripts) !== 'undefined' || typeof (Worker) !== 'undefined'; }; /** * Attaches a listener to the remote worker for file system requests. */ WorkerFS.attachRemoteListener = function (worker) { var fdConverter = new FileDescriptorArgumentConverter(); function argLocal2Remote(arg, requestArgs, cb) { switch (typeof arg) { case 'object': if (arg instanceof node_fs_stats_1.default) { cb(null, statsLocal2Remote(arg)); } else if (arg instanceof api_error_1.ApiError) { cb(null, apiErrorLocal2Remote(arg)); } else if (arg instanceof file_1.BaseFile) { // Pass in p and flags from original request. cb(null, fdConverter.toRemoteArg(arg, requestArgs[0], requestArgs[1], cb)); } else if (arg instanceof file_flag_1.FileFlag) { cb(null, fileFlagLocal2Remote(arg)); } else if (arg instanceof Buffer) { cb(null, bufferLocal2Remote(arg)); } else if (arg instanceof Error) { cb(null, errorLocal2Remote(arg)); } else { cb(null, arg); } break; default: cb(null, arg); break; } } function argRemote2Local(arg, fixedRequestArgs) { if (!arg) { return arg; } switch (typeof arg) { case 'object': if (typeof arg['type'] === 'number') { var specialArg = arg; switch (specialArg.type) { case SpecialArgType.CB: var cbId_1 = arg.id; return function () { var i; var fixedArgs = new Array(arguments.length); var message, countdown = arguments.length; function abortAndSendError(err) { if (countdown > 0) { countdown = -1; message = { browserfsMessage: true, cbId: cbId_1, args: [apiErrorLocal2Remote(err)] }; worker.postMessage(message); } } for (i = 0; i < arguments.length; i++) { // Capture i and argument. (function (i, arg) { argLocal2Remote(arg, fixedRequestArgs, function (err, fixedArg) { fixedArgs[i] = fixedArg; if (err) { abortAndSendError(err); } else if (--countdown === 0) { message = { browserfsMessage: true, cbId: cbId_1, args: fixedArgs }; worker.postMessage(message); } }); })(i, arguments[i]); } if (arguments.length === 0) { message = { browserfsMessage: true, cbId: cbId_1, args: fixedArgs }; worker.postMessage(message); } }; case SpecialArgType.API_ERROR: return apiErrorRemote2Local(specialArg); case SpecialArgType.STATS: return statsRemote2Local(specialArg); case SpecialArgType.FILEFLAG: return fileFlagRemote2Local(specialArg); case SpecialArgType.BUFFER: return bufferRemote2Local(specialArg); case SpecialArgType.ERROR: return errorRemote2Local(specialArg); default: // No idea what this is. return arg; } } else { return arg; } default: return arg; } } worker.addEventListener('message', function (e) { var request = e.data; if (isAPIRequest(request)) { var args_1 = request.args, fixedArgs = new Array(args_1.length); switch (request.method) { case 'close': case 'sync': (function () { // File descriptor-relative methods. var remoteCb = args_1[1]; fdConverter.applyFdAPIRequest(request, function (err) { // Send response. var response = { browserfsMessage: true, cbId: remoteCb.id, args: err ? [apiErrorLocal2Remote(err)] : [] }; worker.postMessage(response); }); })(); break; case 'probe': (function () { var rootFs = node_fs_1.default.getRootFS(), remoteCb = args_1[1], probeResponse = { type: SpecialArgType.PROBE, isReadOnly: rootFs.isReadOnly(), supportsLinks: rootFs.supportsLinks(), supportsProps: rootFs.supportsProps() }, response = { browserfsMessage: true, cbId: remoteCb.id, args: [probeResponse] }; worker.postMessage(response); })(); break; default: // File system methods. for (var i = 0; i < args_1.length; i++) { fixedArgs[i] = argRemote2Local(args_1[i], fixedArgs); } var rootFS = node_fs_1.default.getRootFS(); rootFS[request.method].apply(rootFS, fixedArgs); break; } } }); }; WorkerFS.prototype.getName = function () { return WorkerFS.Name; }; WorkerFS.prototype.isReadOnly = function () { return this._isReadOnly; }; WorkerFS.prototype.supportsSynch = function () { return false; }; WorkerFS.prototype.supportsLinks = function () { return this._supportLinks; }; WorkerFS.prototype.supportsProps = function () { return this._supportProps; }; WorkerFS.prototype.rename = function (oldPath, newPath, cb) { this._rpc('rename', arguments); }; WorkerFS.prototype.stat = function (p, isLstat, cb) { this._rpc('stat', arguments); }; WorkerFS.prototype.open = function (p, flag, mode, cb) { this._rpc('open', arguments); }; WorkerFS.prototype.unlink = function (p, cb) { this._rpc('unlink', arguments); }; WorkerFS.prototype.rmdir = function (p, cb) { this._rpc('rmdir', arguments); }; WorkerFS.prototype.mkdir = function (p, mode, cb) { this._rpc('mkdir', arguments); }; WorkerFS.prototype.readdir = function (p, cb) { this._rpc('readdir', arguments); }; WorkerFS.prototype.exists = function (p, cb) { this._rpc('exists', arguments); }; WorkerFS.prototype.realpath = function (p, cache, cb) { this._rpc('realpath', arguments); }; WorkerFS.prototype.truncate = function (p, len, cb) { this._rpc('truncate', arguments); }; WorkerFS.prototype.readFile = function (fname, encoding, flag, cb) { this._rpc('readFile', arguments); }; WorkerFS.prototype.writeFile = function (fname, data, encoding, flag, mode, cb) { this._rpc('writeFile', arguments); }; WorkerFS.prototype.appendFile = function (fname, data, encoding, flag, mode, cb) { this._rpc('appendFile', arguments); }; WorkerFS.prototype.chmod = function (p, isLchmod, mode, cb) { this._rpc('chmod', arguments); }; WorkerFS.prototype.chown = function (p, isLchown, uid, gid, cb) { this._rpc('chown', arguments); }; WorkerFS.prototype.utimes = function (p, atime, mtime, cb) { this._rpc('utimes', arguments); }; WorkerFS.prototype.link = function (srcpath, dstpath, cb) { this._rpc('link', arguments); }; WorkerFS.prototype.symlink = function (srcpath, dstpath, type, cb) { this._rpc('symlink', arguments); }; WorkerFS.prototype.readlink = function (p, cb) { this._rpc('readlink', arguments); }; WorkerFS.prototype.syncClose = function (method, fd, cb) { this._worker.postMessage({ browserfsMessage: true, method: method, args: [fd.toRemoteArg(), this._callbackConverter.toRemoteArg(cb)] }); }; /** * Called once both local and remote sides are set up. */ WorkerFS.prototype._initialize = function (cb) { var _this = this; if (!this._isInitialized) { var message = { browserfsMessage: true, method: 'probe', args: [this._argLocal2Remote((0, util_1.emptyBuffer)()), this._callbackConverter.toRemoteArg(function (probeResponse) { _this._isInitialized = true; _this._isReadOnly = probeResponse.isReadOnly; _this._supportLinks = probeResponse.supportsLinks; _this._supportProps = probeResponse.supportsProps; cb(); })] }; this._worker.postMessage(message); } else { cb(); } }; WorkerFS.prototype._argRemote2Local = function (arg) { if (!arg) { return arg; } switch (typeof arg) { case 'object': if (typeof arg['type'] === 'number') { var specialArg = arg; switch (specialArg.type) { case SpecialArgType.API_ERROR: return apiErrorRemote2Local(specialArg); case SpecialArgType.FD: var fdArg = specialArg; return new WorkerFile(this, fdArg.path, file_flag_1.FileFlag.getFileFlag(fdArg.flag), node_fs_stats_1.default.fromBuffer(transferrableObjectToBuffer(fdArg.stat)), fdArg.id, transferrableObjectToBuffer(fdArg.data)); case SpecialArgType.STATS: return statsRemote2Local(specialArg); case SpecialArgType.FILEFLAG: return fileFlagRemote2Local(specialArg); case SpecialArgType.BUFFER: return bufferRemote2Local(specialArg); case SpecialArgType.ERROR: return errorRemote2Local(specialArg); default: return arg; } } else { return arg; } default: return arg; } }; WorkerFS.prototype._rpc = function (methodName, args) { var fixedArgs = new Array(args.length); for (var i = 0; i < args.length; i++) { fixedArgs[i] = this._argLocal2Remote(args[i]); } var message = { browserfsMessage: true, method: methodName, args: fixedArgs }; this._worker.postMessage(message); }; /** * Converts a local argument into a remote argument. Public so WorkerFile objects can call it. */ WorkerFS.prototype._argLocal2Remote = function (arg) { if (!arg) { return arg; } switch (typeof arg) { case "object": if (arg instanceof node_fs_stats_1.default) { return statsLocal2Remote(arg); } else if (arg instanceof api_error_1.ApiError) { return apiErrorLocal2Remote(arg); } else if (arg instanceof WorkerFile) { return arg.toRemoteArg(); } else if (arg instanceof file_flag_1.FileFlag) { return fileFlagLocal2Remote(arg); } else if (arg instanceof Buffer) { return bufferLocal2Remote(arg); } else if (arg instanceof Error) { return errorLocal2Remote(arg); } else { return "Unknown argument"; } case "function": return this._callbackConverter.toRemoteArg(arg); default: return arg; } }; WorkerFS.Name = "WorkerFS"; WorkerFS.Options = { worker: { type: "object", description: "The target worker that you want to connect to, or the current worker if in a worker context.", validator: function (v, cb) { // Check for a `postMessage` function. if (v['postMessage']) { cb(); } else { cb(new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, "option must be a Web Worker instance.")); } } } }; return WorkerFS; }(file_system_1.BaseFileSystem)); exports.default = WorkerFS;