| # DartPad SDK Protocol |
| |
| This document specifies what a "DartPad SDK" is, how it is instantiated, and how |
| one interacts with it. `package:dartpad` is the official client for this |
| protocol. |
| |
| At a high-level a _DartPad SDK_ provides a worker that a dartpad-like |
| environment can use to fetch dependencies, analyze, compile and run Dart code. |
| |
| **Concepts:** |
| * _Session_, when a client connects to the worker, a session is created. |
| Sessions do share the same file-system and thread. But are otherwise |
| independent, and should be using differnet parts of the virtual file-system. |
| * _Workspace_, a workspace is a folder and associated resources for |
| running language-servers, compiling source code and running Dart code. |
| * _Language-server_, a process running within a workspace that provides a |
| language-server-protocol server for Dart. |
| * _Sandbox_, a sandboxed iframe within which compiled Dart code is executed. |
| |
| |
| ## DartPad SDK |
| |
| A _DartPad SDK_ is an `assetBaseUrl` that points to a directory that hosts: |
| * `worker.js`, script for running a dartpad environment in the browser. |
| * `sandbox.js`, script for running compiled code in a sandboxed iframe. |
| * SDK specific assets referenced by `worker.js` and `sandbox.js`. |
| |
| The `worker.js` script must export a `Worker` class that can be instantiated as |
| follows: |
| |
| ```js |
| import {Worker} from 'worker.js'; |
| const worker = await Worker.create(); |
| |
| // Create a session communicating over workerMessagePort |
| worker.session(workerMessagePort); |
| ``` |
| |
| Once instantiated, one or more sessions can be created using `worker.session()`, |
| which will communicate over the given [MessagePort][2] using the protocol |
| specified in this document. |
| |
| The `sandbox.js` script is to be injected into a _sandboxed iframe_ as follows: |
| ```html |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <script src="sandbox.js"></script> |
| </head> |
| <body></body> |
| </html> |
| ``` |
| |
| The `sandbox.js` script must use [window.postMessage][4] to send: |
| * `{action: 'error', message: '...'}`, if loading failed, |
| * `{action: 'connect', port: <MessagePort>}` with a [MessagePort][2] attached, |
| if loading succeeded, and, |
| * `{action: 'disconnected'}`, when `<MessagePort>` from connect is closed from |
| the remote side. |
| |
| The attached [MessagePort][2] must be forwarded to the worker as outline in the |
| protocol below. The communication protocol between `sandbox.js` and `worker.js` |
| is internal, though messages will never carry a `MessagePort`, thus, they can |
| be serialized (with care taken to wrap `Uint8Array` instances). |
| |
| |
| ## JSON-RPC 2.0 over `MessagePort` |
| |
| Communication with the worker is conducted over a [MessagePort][2] using an |
| extension of the [JSON-RPC 2.0][3] protocol. |
| |
| While the official JSON-RPC 2.0 specification mandates that messages be JSON, |
| we want to leverage the browser's [Structured Clone][5] to transfer |
| `MessagePort` and `Uint8Array` along with messages. To do this, we encode |
| messages as follows: |
| |
| ```js |
| { |
| "payload": JSON.stringify(message), |
| "port": port, /* [Optional] MessagePort instance */ |
| "bytes": bytes, /* [Optional] Uint8Array instance */ |
| } |
| ``` |
| |
| Once the `message` has been decoded as JSON, `port` is inserted into |
| `params.port` (for requests) and `result.port` (for responses). |
| Similarly, `bytes`, is inserted into `params.bytes` (for requests) and |
| `result.bytes` (for responses). |
| |
| Consequently, when reading this protocol `port`/`bytes` in `params`/`result` |
| should be treated as special properties that are send alongside the encoded |
| JSON message. |
| |
| The use of `port` and `bytes` is not allowed when making batch requests. |
| |
| [JSON-RPC 2.0][3] requests are usually on the form: |
| ```js |
| { |
| "jsonrpc": "2.0", |
| "method": "<name-of-method>", |
| "params": { |
| // parameters for the method |
| "port": /* [Optional] MessagePort instance */ |
| }, |
| "id": 42 // unique ID per request, omitted for notifications! |
| } |
| ``` |
| |
| If the `id` property is omitted, then the message is said to be a _notification_ |
| and no response will be sent. For _requests_ the `id` must be unique and must |
| be used by the client to find the matching _response_ for the _request_. |
| |
| Response objects are usually on the form: |
| ```js |
| { |
| "jsonrpc": "2.0", |
| "result": { |
| // result values from the method |
| "port": /* [Optional] MessagePort instance */ |
| }, |
| "id": 42 // ID from the request |
| } |
| ``` |
| |
| If an error occured when handling a _request_, then an error will be returned. |
| Errors will never be returned for _notifications_, since notifications don't |
| carry an `id` property, they never produce a _response_ or an _error_. |
| |
| Errors are usually on the form: |
| ```js |
| { |
| "jsonrpc": "2.0", |
| "error": { |
| "code": 1000, // Integer error code (negative numbers are reserved) |
| "message": "<human readable message>", |
| "data": { |
| // Arbitrary data associated from the error handler. |
| } |
| }, |
| "id": 42 // ID from the request |
| } |
| ``` |
| |
| When sending requests and notifications is possible to batch multiple messages |
| into a single message by sending an array of requests and notifications. |
| |
| For further details about JSON-RPC 2.0, refer to the [specification][3]. |
| |
| |
| ## Server Methods and Notifications |
| |
| Methods are prefixed based on what objects they operate on. Thus, all methods |
| prefixed `workspace/` require a `workspaceId` parameter. |
| |
| |
| | **Method Prefix** | **Required identifiers** | |
| | :--- | :--- | |
| | `workspace/` | `workspaceId` | |
| | `workspace/languageServer/` | `workspaceId` and `languageServerId` | |
| | `workspace/watcher/` | `workspaceId` and `watcherId` | |
| | `workspace/sandbox/` | `workspaceId` and `sandboxId` | |
| |
| |
| ### Method `createWorkspace` |
| |
| Creates workspace with a dedicated `workspaceFolder`. |
| |
| **Params:** |
| ```js |
| {} // No parameters! |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| // The workspaceId is a unique number identifying the workspace created |
| "workspaceId": 42, |
| // Folder on the shared file-system dedicated to this workspace |
| "workspaceFolder": "file:///workspace/pad_42/", |
| } |
| ``` |
| |
| ### Method `workspace/dispose` |
| Deletes the workspace and all associated resources. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/writeFileFromText` |
| Write a `text` string to a file as UTF-8. |
| Parent directories will be automatically created. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| // URI of the file that you want to write. |
| // Can be absolute file:// or relative to workspaceFolder |
| "uri": "bin/hello.dart", |
| // Text that should be written to the file. |
| // This will be written as UTF-8. |
| "text": "void main() => print('hello world');", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/writeFileFromBytes` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "bin/hello.dart", |
| // Bytes that should be written to the file as a special `bytes` parameter. |
| "bytes": /* Uint8Array instance */, |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/readFileAsText` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "bin/hello.dart", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "text": "<contents of the file as UTF-8>" |
| } |
| ``` |
| |
| ### Method `workspace/readFileAsBytes` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "bin/hello.dart", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "bytes": /* Uint8Array instance from the file */ |
| } |
| ``` |
| |
| ### Method `workspace/deleteFileSystemEntity` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| // URI of the file or folder that you want to delete. |
| "uri": "bin/hello.dart", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/stat` |
| Get information about a file or folder. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "bin/hello.dart", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| // Type of the entity: "file", "folder" or "other" |
| "type": "file" | "folder" | "other", |
| // Size in bytes (only for files) |
| "size": 1024 |
| } |
| ``` |
| |
| ### Method `workspace/createFolder` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "lib", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/listDirectory` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": "lib", |
| // Whether to list recursively (default: false) |
| "recursive": true, |
| // Whether to ignore hidden files (starting with .) (default: false) |
| "ignoreHidden": true |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| // List of entries. Paths are relative to the uri listed. |
| "entries": [ |
| {"path": "main.dart", "type": "file"}, |
| {"path": "src", "type": "folder"} |
| ] |
| } |
| ``` |
| |
| ### Method `workspace/importTarArchive` |
| Import a tar archive (uncompressed) into the workspace. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| // Path where to extract the archive. |
| "uri": ".", |
| // Tar archive as a special `bytes` parameter. |
| "bytes": /* Uint8Array instance */ |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/exportTarArchive` |
| Export a directory as a tar archive (uncompressed). |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| // Directory to export. |
| "uri": "." |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| // Tar archive as a special `bytes` parameter. |
| "bytes": /* Uint8Array instance */ |
| } |
| ``` |
| |
| ### Method `workspace/pub` |
| Runs `pub` in the specified directory. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| // Directory to run pub in. |
| "uri": ".", |
| // Command to run. |
| "command": "get" | "add" | "downgrade" | "outdated" | "upgrade" | "remove" | "unpack", |
| // Arguments to pass to the pub command (optional) |
| "args": ["--dry-run"] |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "log": "<output from pub get>", |
| } |
| ``` |
| |
| ### Method `workspace/startLanguageServer` |
| Start a language-server. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "languageServerId": 36, // identifier for the language-server just started |
| } |
| ``` |
| |
| ### Method `workspace/languageServer/message` |
| Sends an LSP message to a running language server. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "languageServerId": 36, |
| "message": { |
| // JSON-RPC 2.0 message for the language-server |
| "jsonrpc": "2.0", |
| "method": "...", |
| "params": {...}, |
| "id": ..., |
| }, |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/languageServer/stop` |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "languageServerId": 36, |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| |
| ### Method `workspace/startWatcher` |
| |
| Initiates a file system watcher for a given URI. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "uri": ".", |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "watcherId": 1 |
| } |
| ``` |
| |
| ### Method `workspace/watcher/stop` |
| |
| Terminates an active watcher. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "watcherId": 1 |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ### Method `workspace/connectSandbox` |
| Connects a `MessagePort` from `sandbox.js` to the workspace, returning |
| a `sandboxId` used to control the sandbox and the available run modes. |
| |
| A _DartPad SDK_ defines its own modes, typical modes include `'console'`. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "port": /* MessagePort instance */ |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "sandboxId": 1, |
| "modes": ["console", "app"] |
| } |
| ``` |
| |
| ### Method `workspace/sandbox/run` |
| Compiles and runs a Dart or Flutter entrypoint in the sandbox using the |
| specified mode. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "path": "bin/hello.dart", |
| "mode": "console" |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "log": "<output log string>" |
| } |
| ``` |
| |
| ### Method `workspace/sandbox/hotReload` |
| Hot-reloads the currently running application in the sandbox. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1 |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "log": "<output log string>" |
| } |
| ``` |
| |
| ### Method `workspace/sandbox/hotRestart` |
| Hot-restarts the currently running application in the sandbox. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1 |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "log": "<output log string>" |
| } |
| ``` |
| |
| ### Method `workspace/sandbox/invokeExtension` |
| Invokes a Dart extension method in the sandbox. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "method": "ext.myExtension", |
| "args": { |
| "key": "value" |
| } |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| { |
| "result": "<json encoded result string>" |
| } |
| ``` |
| |
| ### Method `workspace/sandbox/close` |
| Closes the sandbox, severing its `MessagePort` and releasing resources. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1 |
| } |
| ``` |
| |
| **Result:** |
| ```js |
| {} // empty result |
| ``` |
| |
| ## Client Notifications |
| |
| ### Notification `workspace/watcher/events` |
| |
| Sent when changes occur within the watched paths. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "watcherId": 1, |
| "events": [ |
| { |
| "type": "add" | "modify" | "remove", |
| "uri": "file:///workspace/pad_42/lib/main.dart" |
| } |
| ] |
| } |
| ``` |
| |
| ### Notification `workspace/languageServer/message` |
| Sent by the worker when the language server produces a message. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "languageServerId": 36, |
| "message": { |
| // JSON-RPC 2.0 message from the language-server |
| } |
| } |
| ``` |
| |
| ### Notification `workspace/languageServer/exited` |
| Sent by the worker when a language server process terminates. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "languageServerId": 36 |
| } |
| ``` |
| |
| ### Notification `workspace/sandbox/console` |
| Sent by the worker when the sandbox produces a console message. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "message": "Hello world" |
| } |
| ``` |
| |
| ### Notification `workspace/sandbox/error` |
| Sent by the worker when the sandbox produces an error message. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "message": "Error details..." |
| } |
| ``` |
| |
| ### Notification `workspace/sandbox/unhandledRejection` |
| Sent by the worker when a Promise is unhandled in the sandbox. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "message": "Rejection details..." |
| } |
| ``` |
| |
| ### Notification `workspace/sandbox/extensionEvent` |
| Sent by the worker when an extension event is fired in the sandbox. |
| |
| **Params:** |
| ```js |
| { |
| "workspaceId": 42, |
| "sandboxId": 1, |
| "kind": "my.event.kind", |
| "data": { /* JSON object */ } |
| } |
| ``` |
| |
| ## Error codes |
| Errors returned by the worker use the following codes. |
| |
| <!-- BEGIN GENERATED ERROR CODE TABLE --> |
| | Code | Name | Description | |
| | :--- | :--- | :--- | |
| | 2001 | `workspaceNotFound` | The provided `workspaceId` does not exist. | |
| | 4001 | `fileNotFound` | The requested file or directory does not exist. | |
| | 4002 | `fileWriteConflict` | Could not write file (e.g. parent is a file). | |
| | 4003 | `fileDeletionFailed` | Could not delete the requested entity. | |
| | 5001 | `languageServerNotFound` | The `languageServerId` does not exist in this workspace. | |
| | 6001 | `pubCommandFailed` | The pub command failed to execute successfully. | |
| | 6064 | `pubUsage` | The command was used incorrectly. | |
| | 6065 | `pubData` | The input data was incorrect. | |
| | 6066 | `pubNoInput` | An input file did not exist or was unreadable. | |
| | 6067 | `pubNoUser` | The user specified did not exist. | |
| | 6068 | `pubNoHost` | The host specified did not exist. | |
| | 6069 | `pubUnavailable` | A service is unavailable. | |
| | 6070 | `pubSoftware` | An internal software error has been detected. | |
| | 6071 | `pubOs` | An operating system error has been detected. | |
| | 6072 | `pubOsFile` | Some system file did not exist or was unreadable. | |
| | 6073 | `pubCantCreate` | A user-specified output file cannot be created. | |
| | 6074 | `pubIo` | An error occurred while doing I/O on some file. | |
| | 6075 | `pubTempFail` | Temporary failure, indicating something that is not really an error. | |
| | 6076 | `pubProtocol` | The remote system returned something invalid during a protocol exchange. | |
| | 6077 | `pubNoPerm` | The user did not have sufficient permissions. | |
| | 6078 | `pubConfig` | Something was unconfigured or mis-configured. | |
| | 7001 | `sandboxNotFound` | Sandbox with the given `sandboxId` was not found. | |
| | 7002 | `invalidSandboxState` | Sandbox methods have not been called in correct order. | |
| | 7101 | `compilationFailed` | Failed to compile code, usually due to an issue in the code being compiled. | |
| | 7102 | `packageConfigNotFound` | Unable to find `.dart_tool/package_config.json` in any parent directory. | |
| | 7103 | `hotReloadRejected` | The hot reload request was rejected by the compiler. | |
| | 7201 | `executionFailed` | Error happened when running `main()` from user-code. | |
| |
| <!-- END GENERATED ERROR CODE TABLE --> |
| |
| [1]: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker |
| [2]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort |
| [3]: https://www.jsonrpc.org/specification |
| [4]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage |
| [5]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm |