Skip to content
13 changes: 13 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# IS_HELIX is a manual local-testing override for URL-based backend selection.
# Leave it unset for normal deployments.
# It accepts three values:
# false = always go to da-admin (service binding), regardless of the document hostname
# true = always go to Helix, rewriting every request to https://api.aem.live
# local = always go to Helix, rewriting every request to http://localhost:3000
# When unset, the backend is chosen by the document URL: api.aem.live -> Helix,
# everything else -> da-admin.
#
# Copy this file to .dev.vars and uncomment a line to force the backend locally.
# Note: values set in .dev.vars are masked as "(hidden)" in wrangler's startup log;
# declare IS_HELIX in wrangler.toml [env.dev.vars] instead if you want it printed.
# IS_HELIX=local
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ To run da-admin locally see https://github.com/adobe/da-admin/blob/main/README.m
1. In a terminal, run `npm run dev` this repo's folder.
1. The da-collab service API is available via http://localhost:4711

#### Local backend testing override

For manual local testing, `IS_HELIX` in `.dev.vars` can force Helix at `api.aem.live` (`true`),
Helix at `localhost:3000` (`local`), or da-admin (`false`). Leave it unset for normal URL-based
backend selection. Forced URL rewriting and the `*** Calling` log are isolated in the
`getLocalTestBackendOverride()` helper.

#### Access via da-live

To access the locally running da-collab via da-live also running locally, first run da-live on your local machine
Expand All @@ -32,6 +39,32 @@ To access da-collab and da-admin running on stage, open this URL in a browser: h
1. When passing in `?da-collab=local&da-admin=local` each service will set a localStorage value and will not clear until you use `?name-of-service=reset`. It is recommended to use an incognito browser window to ensure you don't forget about this setting.

## Additional details
### Helix backend polling
For Helix-backed documents, collab checks the backend ETag with a HEAD request every five seconds
while the session is connected. A changed ETag or a 404/410 response closes the session, cancels
pending local saves, and removes the stored restore anchor. Invalidation therefore does not
flush stale edits back to the backend, and reconnecting sessions reload the source document.
Ordinary connection closure and da-admin invalidation retain their existing save-flushing behavior.

Helix ETags are compared without the weak `W/` prefix. If GET omits its ETag, collab brackets a
fresh read with HEAD requests to establish a matching content/version baseline. Without a
verified baseline, reads remain available but saves report an error instead of overwriting
an unknown version. A successful HEAD can establish a polling-only baseline without disconnecting editors.

Helix saves use the known version in `If-Match` rather than `*`. A 412 is treated as an external
version conflict: pending saves are cancelled and the session is reloaded, not reported as a
deletion. The Helix API must return the written ETag on successful POST; if it does not, collab
logs the missing validator and pauses further protected saves until reload. da-admin retains
its existing multipart PUT and `If-Match: *` behavior. Complete lost-update protection additionally
requires the API to enforce the precondition atomically at the storage write.

Failed HEAD requests and successful responses without an ETag are logged and retried on the next
interval without disconnecting editors. Only one HEAD request is in flight per document, and
responses overlapping a local save or belonging to an old session are ignored. Polling starts
only if initialization finishes for the current, connected document. Destroying the document
clears its polling timer and aborts any pending HEAD request; late initialization cannot restart
polling after disconnect. The idle-but-connected session policy is unchanged.

### Recommendations
1. We recommend running `npm run lint` for linting.

Expand Down
4 changes: 2 additions & 2 deletions src/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export async function handleApiRequest(request, env) {
}

const timingBeforeDaAdminHead = Date.now();
const initialReq = await getBackend(docName, env.daadmin).fetch(docName, opts);
const initialReq = await getBackend(docName, env).fetch(docName, opts);

timingDaAdminHeadDuration = Date.now() - timingBeforeDaAdminHead;

Expand Down Expand Up @@ -429,7 +429,7 @@ export class DocRoom extends DurableObject {
// Helix does not yet report auth actions, so grant collaborators
// read,write; otherwise honour what da-admin reported.
// TODO: remove the isHelixDoc branch once Helix reports auth actions.
const authActions = isHelixDoc(docName)
const authActions = isHelixDoc(docName, this.env)
? 'read,write'
: request.headers.get('X-auth-actions') ?? '';

Expand Down
Loading
Loading