The worker
Your add-on's executable is a worker. Ledge starts it for each request, sends one JSON request, and reads one JSON response back.
One request, one response
- Ledge starts the executable.
- It writes one JSON request to standard input, then closes it.
AddOnWorker.runreads the request and calls your handler.- The worker writes one JSON response to standard output and exits.
Standard output carries only the response. Write anything else, such as diagnostics, to standard error, and never write tokens or whole requests there.
The request
| Field | Type | Meaning |
|---|---|---|
protocolVersion | Int | Always 1. The worker rejects anything else before your handler runs. |
operation | String | What Ledge wants. Spotify Likes uses read and write. |
trackID | String | The Spotify ID of the song that's playing. |
liked | Bool? | For write, whether the song should be liked. Empty for read. |
accessToken | String | A short-lived token for the provider, from Ledge's sign-in. |
{"protocolVersion":1,"operation":"write","trackID":"4uLU6hMCjMI75M1A2tKUQC","liked":true,"accessToken":"…"}The response
| Field | Type | Meaning |
|---|---|---|
value | Bool? | The answer. For Spotify Likes, whether the song is liked. |
error | String? | A short message for Ledge when the request failed. Leave it empty on success. |
return AddOnResponse(value: true)
return AddOnResponse(value: nil, error: "Unsupported operation.")When something goes wrong
If your handler throws, the worker replies with a generic error, “Add-on could not complete the request.” It never passes on the error's own text, because that can contain a token or a provider's response. Return your own error when you want Ledge to have something more specific, and keep it free of credentials.
Your handler must check its own inputs, such as the operation and the format of IDs, before acting on them.
Limits
- A request can be at most 64 KB. Larger input is rejected before your handler runs.
- Ledge decides how long a request may take and stops the worker when it's done. The worker has no timeout of its own.
Testing without an account
Keep the add-on's logic in a library target and give it a transport you can replace, so tests run without tokens or real accounts. AddOnWorker.respond(to:handler:) runs the whole worker on bytes in memory:
let input = try JSONEncoder().encode(
AddOnRequest(operation: "read", trackID: "abc123", liked: nil, accessToken: "test"))
let response = await AddOnWorker.respond(to: input) { _ in AddOnResponse(value: true) }
#expect(response.value == true)