@social.io/office

A temporary document editing service. Applications send a DOCX or request a blank document over authenticated TypedSocket, supply users and settings, and receive editing links, presence, revisions and document snapshots. After the last browser leaves, the service delivers the final DOCX and removes its session records and objects only after the application acknowledges durable storage.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Architecture and scope

One service process implements the application protocol, browser admission, Word collaboration and lifecycle. It serves the unmodified Euro Office 9.3.4-hotfix.1 web applications and SDK. A bounded queue invokes the native x2t converter for each import/export. Euro Office's document-server daemons, Redis, RabbitMQ and PostgreSQL do not start. There is no full Euro Office instance per document.

The initial supported editing format is DOCX. The native converter also understands XLSX/PPTX, but the public API refuses them: their collaboration and locking have not been implemented or qualified. Macros, plugins, chat, external URL image imports and advanced export formats are not supported. Local PNG/JPEG uploads and document download endpoints are included. Do not treat format-conversion capability as proof of collaborative editor support.

Session metadata uses @lossless.org/client/nosqldb models; source documents, editor assets, change batches and snapshots use @lossless.org/client/objectstorage. Native converter files exist only in disposable temporary directories. The supplied Compose configuration uses a bounded /tmp tmpfs and a read-only root filesystem.

Run exactly one office replica per database and bucket namespace. The per-document serialization owner is that process; this implementation is not a distributed collaboration cluster. Use separate namespaces for independent instances.

Container

Build with docker compose build. Configure these environment variables using your deployment's secret mechanism:

Variable Value
OFFICE_EDITOR_URL Public HTTPS origin for browser editing, without a path. HTTP is accepted only for loopback development.
OFFICE_TOKEN_SECRET At least 32 bytes of independently generated secret material, stable across restarts.
OFFICE_APPLICATIONS JSON object mapping application IDs to lowercase SHA-256 hashes of their independently provisioned bearer credentials.
OFFICE_DATABASE JSON: mongoDbUrl, mongoDbName, optionally mongoDbUser and mongoDbPass. Use a dedicated namespace.
OFFICE_OBJECTSTORAGE JSON: endpoint, accessKey, accessSecret, bucketName, optionally port, useSsl, region. The bucket must already exist.

Port 8080 serves the browser editor; port 8081 serves TypedSocket. Put both behind HTTPS reverse proxies with WebSocket upgrades. Do not log authorization headers, query strings, link fragments, document bodies or editor messages. The service never accepts identity from unsigned browser fields. User jump URLs are bearer capabilities and must be delivered privately by the caller.

Storage must support the client's verified exact-path purge operation, including historical versions and delete markers. Startup refuses an incompatible backend. The service can purge its live database and object storage; operators must separately ensure that backups, replication, object locks and request logs do not retain documents after deletion. No application data is written to a filesystem as a persistence fallback.

Default limits: 30 MiB per document/object, 8 MiB per image, 100 grants per session, 1,000 editor assets, 100,000 change units, eight queued converter jobs, 120 seconds per conversion. The container has two CPUs, 2 GiB RAM, 128 processes, and 768 MiB tmpfs. Adjust limits through a reviewed code/configuration change and verify with representative documents.

Caller protocol

Use TypedSocket 8, TypedRequest 8, and typedrequest-interfaces 7. Shared contracts are in ts/interfaces.ts. This private service repository is not published as an npm client package.

Register an office.update handler on the application's client router before connecting. Authenticate each physical connection with office.authenticate. On reconnect, authenticate again and call office.subscribe for each still-owned session. One subscribed connection receives updates for a session; subscribing from another connection replaces the previous delivery target.

Request Required input Result
office.authenticate applicationId, plaintext application bearer token over TLS Authenticates this physical connection.
office.create requestId, title, format: 'docx', users, settings, optional send VirtualStream document sessionId, user-specific jumps. Omit the stream for a blank DOCX.
office.addUsers sessionId, users, settings Additional user-specific jumps.
office.subscribe sessionId, afterSequence Current state and replay events; resumes pushed updates and snapshots.
office.snapshot sessionId Schedules/delivers a snapshot and returns its document revision. A currently staged save must finish first.
office.update (server → caller) event, optional send VirtualStream document Caller returns { sequence, sha256?, stored }.

Every create/add-users request must supply settings:

const settings = {
  language: 'en',
  theme: 'light', // or 'dark'
  canDownload: true,
  canPrint: true,
};
const users = [{ id: 'user-123', name: 'Ada', canEdit: true }];

Settings are bound to the issued grant. There are no persistent per-user or per-application editor defaults. A retry of office.create is idempotent while its session exists, scoped to the authenticated application; it must carry identical input and bytes. A different payload with the same request ID is refused. After final acknowledgement and cleanup, even a repeated request ID creates a fresh session and requires the document, settings and users again.

To send document bytes, create a TypedRequest VirtualStream with creator direction send on the client's current transport. Fire office.create with that stream, await stream.opened, send bounded Uint8Array chunks, close it and await its acceptance plus the request response. Use a request/stream acceptance timeout of at least 180 seconds to cover bounded import conversion. Do not put base64 document data in JSON.

For each office.update document stream, drain receive() until EOF, verify the event's byte length and SHA-256 hex digest, commit the bytes durably in the calling application's storage, then call accept(). Return the exact event sequence and digest with stored: true only after that commit. The stream's transport integrity digest uses sha256:<hex>; the event and receipt use plain hex. A transport acceptance alone does not authorize deletion: the matching application receipt is required too.

Updates are delivered at least once. Deduplicate by session ID and event sequence. Metadata events are ready, presence, changed, and error; snapshot and final also carry complete DOCX bytes. Document revision is monotonic and separate from Euro Office's internal change-log index. Snapshots are normally exported at most once per 30 seconds while changes exist. Replay retains the latest 2,000 events. An older cursor resumes from the retained window and returns replayTruncated: true; the caller can request a fresh snapshot. Final handoff is always replayed until its receipt is accepted.

Session lifecycle

  1. Admit the source and convert it into immutable editor assets.
  2. Issue a separate signed jump for each supplied user. Additional users can join later.
  3. Persist complete editor change units before acknowledging them. A retry of an already received save chunk replays its receipt instead of appending again.
  4. Allow a 30-second reconnect grace after browser departure. Presence includes all browser tabs and viewers. Grants expire after 24 hours; an unopened session finalizes after 15 minutes.
  5. Once no users remain after grace, seal any received partial save, stop admitting editors and export the final revision. A closed/crashed browser's changes that never reached the server cannot be reconstructed.
  6. Retain the final revision in awaitingReceipt while the caller is disconnected or refuses delivery. Reconnect and subscribe to receive it again.
  7. After the exact final receipt, enter deleting, purge every owned object including historical versions, then delete session metadata. Old jumps and document requests no longer resolve.

Startup recovers interrupted preparation, finalization and deletion. It marks old browser connections disconnected and gives them reconnect grace. Export failures retain the source and acknowledged changes and emit an error; damaged-change recovery from the native converter is rejected rather than delivering a silently truncated document. A failed final export is retried by the lifecycle owner. Storage deletion errors leave durable cleanup ownership for retry.

Development and verification

Use pnpm install, pnpm build and pnpm run check. pnpm test requires Docker and Chrome. Its wrapper builds the engine and runtime targets, extracts generated editor assets into .nogit/vendor, starts a capped disposable MongoDB, runs the tests and removes its containers. The suite also uses the client's real object-storage test server. For an already prepared fixture, run OFFICE_TEST_MONGO_PORT=<isolated port> pnpm exec tstest test/ --verbose --logfile --timeout 180; never point it at production storage.

pnpm test exercises real browser editing and native export, reconnect, streamed input, TypedSocket handoff, refusal of the final receipt, replay, removal after acknowledgement, and runtime-container startup/shutdown. Its converter runs in a capped, network-disabled disposable container; this test harness is separate from the production process adapter. Screenshots go to .playwright-mcp/, logs to .nogit/.

The independently authored service is licensed under the MIT License; see license.md. Euro Office and other bundled components retain their own licenses. Exact revisions, attribution and Corresponding Source instructions are in thirdparty.md. The editor offers these notices and the running service source through its visible source/legal link. Preserve these routes and upstream About notices in deployments.

The MIT License does not grant permission to use trade names, trademarks, service marks or product names except as required for reasonable attribution. This project is owned and maintained by Task Venture Capital GmbH, registered at District Court Bremen HRB 35230 HB, Germany. Names and logos remain trademarks of their respective owners. Legal inquiries: hello@task.vc. The use of third-party components does not imply endorsement.

S
Description
No description provided
Readme
155 KiB
Languages
TypeScript 97.4%
Shell 1.6%
Dockerfile 1%