Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ solely by the separate external-pad policy, not by these two settings.

The answers of a public share (`/api/v1/public/...`) carry the codes that can come up there - `waiting_binding` and `pad_too_large` - but not `missing_binding` or `missing_frontmatter`: what a client does on those needs a signed-in user. Their messages are translated, one sentence for each kind of trouble.

A response without a `code` may still be machine-readable through its HTTP status and other documented fields — a locked `.pad` answers `503` with `retryable: true`, signed in and public alike, for instance, and so does a request this instance's Etherpad could not be reached for. Every `503` of this app carries `retryable: true`, and it never answers `502` or `504`: the clients take any of the three without it for a proxy's or a maintenance page's. A file's row that another request made first (`BindingNotCreatedException`, two initialisations at once, say) answers `400` with `retryable: true` too: the next open finds that request's pad. The create endpoints answer it with their own sentence and neither field. One Etherpad answered and refused answers `400` without it: trying again gives the same answer. What is never a stable identifier is the `message` text.
A response without a `code` may still be machine-readable through its HTTP status and other documented fields — a locked `.pad` answers `503` with `retryable: true`, signed in and public alike, for instance, and so does a request this instance's Etherpad could not be reached for. Every error this app answers is JSON, it never answers `502` or `504`, and every `503` of its own carries `retryable: true`; what the clients make of a 5xx that is none of these is in `docs/architecture.md` ("Errors of the API"). A file's row that another request made first (`BindingNotCreatedException`, two initialisations at once, say) answers `400` with `retryable: true` too: the next open finds that request's pad. The create endpoints answer it with their own sentence and neither field. One Etherpad answered and refused answers `400` without it: trying again gives the same answer. What is never a stable identifier is the `message` text.

## Cookie Behavior (Protected Pads)

Expand Down Expand Up @@ -490,8 +490,9 @@ solely by the separate external-pad policy, not by these two settings.
- `epnc:host-sync-now`
- `src/embed-create-main.js`
- powers the minimal `/embed/create-by-parent/{parentFolderId}` page.
- uses same-origin `POST /api/v1/pads/create-by-parent`.
- uses same-origin `POST /api/v1/pads/create-by-parent`, without a time limit, as it writes.
- redirects to returned `embed_url` after successful pad creation.
- tells the host `epnc:create-succeeded` or `epnc:create-failed`; after `reason: 'network'` the outcome is not known (`docs/architecture.md`, the create flow).

## URL Control in Files App

Expand Down
11 changes: 9 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,14 @@ Primary flow (minimal blank create launcher page):
5. `PadCreateController::createByParent` performs server-side validation of `name`, `accessMode`, and the writable target folder before creating the `.pad` file and binding.
6. Before redirecting, `src/embed-create-main.js` posts the host page one of two structured events so the surrounding UI can react without scraping the iframe DOM:
- `epnc:create-succeeded` — payload `{embed_url, file_id, pad_id, access_mode}`. Fires once on the success path, immediately before the iframe self-redirects to the embed-open URL.
- `epnc:create-failed` — payload `{reason, status, message}`. Fires on any error. `reason` is one of `'invalid'` (client-side validation), `'conflict'` (HTTP 409 — e.g. duplicate filename), `'server'` (any other 4xx/5xx), or `'network'` (fetch itself failed).
- `epnc:create-failed` — payload `{reason, status, message, code, retryable}`. Fires on any error. `reason` is one of:
- `'invalid'`: client-side validation failed; nothing was sent.
- `'conflict'`: a `409`, a name taken or a file that changed while its pad was set up; `code` tells them apart.
- `'server'`: refused with any other 4xx, by this app or by a proxy before it, so nothing was created; or failed by this app with a 5xx of its own, which it rolls back as far as it can; or answered in a way the page cannot use.
- `'network'`: no answer from this app (see "Errors of the API"), so the pad may have been created anyway. `status` carries what came, if anything, and the message says to look in the folder before trying again.

`code` and `retryable` are the server's (`docs/api-reference.md`), `null` and `false` without them: `retryable` says the same create may work later, a locked folder say.
The create has no client-side time limit, as it writes: a slow create is not a failed one, and the page reports nothing until the server or a proxy answers. A host that stops waiting on its own must assume the pad may still be created.
The inline error rendering inside the iframe is unchanged — `postMessage` is purely additive for hosts that want to act on the outcome. Target-origin is `*` because the page doesn't know the host's origin up-front; the `frame-ancestors` allowlist already constrains who can be the parent.
7. On success the launcher redirects itself to the returned `embed_url`, after which the normal embed-open flow takes over.

Expand Down Expand Up @@ -305,7 +312,7 @@ Primary flow (native viewer):

- `PadControllerErrorMapper` (signed in) and `PublicViewerControllerErrorMapper` (public shares) answer an error with a translated sentence of their own; `code` and `retryable` come from `ApiErrorCode`, and `docs/api-reference.md` lists the codes. An exception's message is for the log; only a refusal translated where it is thrown, and the reason a pad on another server cannot be linked or read, reach the reader as they are.
- The viewer and the embed page offer "Try again" where the same open may work later (`isRetryableOpenError()` in `src/lib/pad-open-flow.js`); the button runs the whole open again. That is every answer with `retryable` (`docs/api-reference.md` lists the cases); `pad_file_changed`, which an open meets only while initialising the file, after the server undid its part; and any step of the open that got no answer, the initialise too. The second try opens first and finds a pad the first try set up, and one still being set up is safe to meet: the server compares the file before it writes, a file has one binding row, and a pad that lost either race is rolled back.
- `fetchJsonWithTimeout()` marks a request that got no answer from this app as `unanswered`: its own timeout, a failed network, also while the body streams in, and a `502`, `503` or `504` without `retryable`, since this app never answers 502 or 504 and every 503 of its own carries it; a proxy or Nextcloud in maintenance answers in its place. It says only that, since whether another try is safe depends on the request. A recovery that got no answer is not offered again: the clients open the file instead, which shows the pad if it went through and the recovery card if not. Both clients say "no answer" in a translated sentence of their own rather than the browser's English.
- `fetchJsonWithTimeout()` marks a request that got no answer from this app as `unanswered`: its own timeout, a failed network, also while the body streams in (the status, if one came, goes along), and a 5xx that cannot be this app's answer by what `docs/api-reference.md` says of its errors: one whose body is not JSON, or a 502, 503 or 504 without `retryable`. A proxy, Nextcloud in maintenance or PHP dying midway sent it. It says only that, since whether another try is safe depends on the request. A recovery that got no answer is not offered again: the clients open the file instead, which shows the pad if it went through and the recovery card if not. Both clients say "no answer" in a translated sentence of their own rather than the browser's English.
- After a click whose button goes away or is disabled with the focus on it, a second try or a recovery, the viewer and the embed page hand the focus to the card's first action, or to its message (`src/lib/hand-focus.js`). Not on the first load. The embed page does it without scrolling, since it sits in another page. The message of each error card is `role="alert"`, so an error is read out when it appears, the first one too.
- This instance's Etherpad not reachable answers `503` with `retryable`; a refusal from it (`EtherpadRefusedException`: a pad or group it does not have, a key it does not take) `400`. A pad on another server (`ExternalPadException`) is neither. Which is which the exceptions say (`EtherpadClientException::isEtherpadUnreachable()`); the answer and the log both go by it.
- Each error answered is logged once, by `ApiErrorLog`; the services under the mappers leave it to it, save the few lines that explain a refusal nothing else would (a name another create has locked, a file a create will not write over, a refused legacy migration):
Expand Down
2 changes: 1 addition & 1 deletion js/etherpad_nextcloud-embed-create-main.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading