@manifest-editor/server-vault 2.2.1-next.0.eefc2e3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -97
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,136 +1,128 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/digirati-co-uk/iiif-manifest-editor"><img src="https://raw.githubusercontent.com/digirati-co-uk/iiif-manifest-editor/main/apps/docs/content/img/logo.svg" width="360" alt="Manifest Editor"></a>
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://www.npmjs.com/package/@manifest-editor/server-vault"><img src="https://img.shields.io/npm/v/%40manifest-editor%2Fserver-vault" alt="npm version"></a>
|
|
6
|
+
<a href="https://github.com/digirati-co-uk/iiif-manifest-editor/actions/workflows/build-test.yml"><img src="https://img.shields.io/github/actions/workflow/status/digirati-co-uk/iiif-manifest-editor/build-test.yml?branch=main&label=build" alt="Build status"></a>
|
|
7
|
+
<a href="https://manifest-editor-docs.netlify.app/"><img src="https://img.shields.io/badge/docs-Manifest_Editor-blue" alt="Documentation"></a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
10
|
+
# @manifest-editor/server-vault
|
|
3
11
|
|
|
12
|
+
## Getting started with Manifest Editor
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
For the complete editor, start with [`manifest-editor`](https://www.npmjs.com/package/manifest-editor) and its [installation and integration guide](https://github.com/digirati-co-uk/iiif-manifest-editor/tree/main/packages/manifest-editor#readme). It combines the editor UI, presets, creators, and editing APIs. This package is one part of that workspace.
|
|
15
|
+
|
|
16
|
+
## About
|
|
17
|
+
|
|
18
|
+
Experimental Node.js WebSocket service for sharing in-memory Vault state between clients.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @manifest-editor/server-vault
|
|
13
24
|
```
|
|
14
25
|
|
|
26
|
+
```ts
|
|
27
|
+
import { MultiVaultServer } from "@manifest-editor/server-vault";
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
const server = new MultiVaultServer();
|
|
30
|
+
server.listen(8081, "127.0.0.1");
|
|
31
|
+
// Clients connect to ws://localhost:8081/vault/demo.
|
|
32
|
+
```
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
read-write user. The read-write user can make changes to the manifest and save it back to the vault. These changes will
|
|
21
|
-
be reflected in the vault and will be available to all users. The changes will also be reflected in the file system
|
|
22
|
-
where the manifest is stored.
|
|
34
|
+
This implementation keeps state in memory and does not implement the authentication, durable storage, or GitHub integration described in earlier design notes. Keep this example local; a deployed host must supply its own access controls and persistence.
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
of the action. The vault will also broadcast the action to all connected clients. The clients can then update their
|
|
26
|
-
state based on the action. If the actions are applied in the same order on all clients then the state of the clients
|
|
27
|
-
will be the same.
|
|
36
|
+
## Architecture and lifecycle
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
clients. Clients are required to send the previous action id with each action. The vault will then check that the
|
|
31
|
-
previous action id matches the last action id that was applied to the vault. If the previous action id does not match
|
|
32
|
-
then the action will be rejected. The client will then need to update its state to match the vault and then try again.
|
|
38
|
+
A `MultiVaultServer` owns an HTTP server and a map of named `ServerVault` instances. Each `ServerVault` contains a normal `@iiif/helpers` Vault plus a WebSocket server. The browser uses [`@manifest-editor/client-vault`](../client-vault) to receive the initial state and exchange Vault actions.
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
1. A client connects to `/vault/<id>`.
|
|
41
|
+
2. The server looks up that ID and creates an empty in-memory Vault if necessary.
|
|
42
|
+
3. The server sends an `init-response` containing its current IIIF store.
|
|
43
|
+
4. Clients send atomic Vault actions; the server applies them and broadcasts them to other clients.
|
|
44
|
+
5. The originating client receives an acknowledgement, or a rejection if applying the action throws.
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
iframe on the client. The client will then be able to connect to the vault and send actions to the vault. The vault
|
|
38
|
-
will then broadcast the actions to all connected clients. The client will then update its state based on the actions.
|
|
46
|
+
`GET /vault` returns the IDs currently in the server's map. There is no project database or user-specific project listing behind that route.
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
on the same server as the vault. However, the server will need to allow cross-origin requests from the client, and will
|
|
42
|
-
ensure that only select hosts are allowed to connect to the vault.
|
|
48
|
+
## Start with an existing manifest
|
|
43
49
|
|
|
44
|
-
For
|
|
45
|
-
that has the server in an iframe. When an event originates from the client, the following happens:
|
|
50
|
+
For a local demonstration, seed the server's Vault before clients connect:
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- The server broadcasts the message to all connected clients via web-sockets
|
|
50
|
-
- The server iframe receives the message from the server via web-sockets
|
|
51
|
-
- The server iframe sends the message to the other clients via post-message
|
|
52
|
+
```ts
|
|
53
|
+
import { MultiVaultServer, ServerVault } from "@manifest-editor/server-vault";
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
const server = new MultiVaultServer();
|
|
56
|
+
const shared = new ServerVault();
|
|
57
|
+
await shared.vault.loadManifest("https://wellcomelibrary.org/iiif/b18035723/manifest");
|
|
58
|
+
server.vaults.set("demo", shared);
|
|
59
|
+
server.listen(8081, "127.0.0.1");
|
|
60
|
+
```
|
|
56
61
|
|
|
62
|
+
In a separate browser application:
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- ServerWebSocketVault.dispatch() -> sends message to all clients
|
|
64
|
+
```ts
|
|
65
|
+
import { createClientVault } from "@manifest-editor/client-vault";
|
|
61
66
|
|
|
67
|
+
const vault = await createClientVault("ws://localhost:8081/vault/demo");
|
|
68
|
+
// Pass this Vault to ManifestEditor or a VaultProvider.
|
|
69
|
+
```
|
|
62
70
|
|
|
71
|
+
The client factory waits for initial state. It does not load a manifest from disk or create a project record on the server. The underlying `shared.vault` is available for server-side loading and serialization, but the server's broadcasts are driven by incoming client messages; do not assume arbitrary server-side Vault mutations are automatically broadcast.
|
|
63
72
|
|
|
64
|
-
##
|
|
73
|
+
## Wire protocol
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
repository. The vault will then be able to load the manifest from the GitHub repository. The vault will also be able
|
|
68
|
-
to save the manifest to the GitHub repository.
|
|
75
|
+
Messages are JSON. Action messages carry `_id`, `_type`, `_lastActionId`, and `action`; initialisation messages carry the complete IIIF store in `data`.
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
| Message | Direction | Behavior |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `init-request` | Client → server | Request current state. |
|
|
80
|
+
| `init-response` | Server → client | Send the normalised store and action position. |
|
|
81
|
+
| `vault-action` | Client → server → other clients | Apply and distribute a Vault action or batch. |
|
|
82
|
+
| `vault-action-confirmation` | Server → originating client | Acknowledge the action. |
|
|
83
|
+
| `vault-action-rejection` | Server → originating client | Report an action the server could not apply. |
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
then load the manifest from the new branch. The client will then be able to make changes to the manifest. Alternatively
|
|
75
|
-
the client can request a read-only instance of the vault. The client will then be able to view the manifest but will
|
|
76
|
-
not be able to make changes to the manifest.
|
|
85
|
+
The older design proposed rejecting actions whose `_lastActionId` does not match the server's current position. The implementation carries that field but **does not enforce this conflict check**. It is not a conflict-resolution protocol or a CRDT. Likewise, the exported `initialise-with-resource`/`empty-vault-response` types should not be read as proof of a complete server-side initialisation flow: the handler currently processes `init-request` and `vault-action`.
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
will then be able to make changes to the manifest. The client will also be able to save the changes to the existing
|
|
80
|
-
branch. If other clients are also connected to the vault then they will be able to see the changes that the client has
|
|
81
|
-
made.
|
|
87
|
+
## Connections and cleanup
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
`ServerVaultOptions.pingTimeout` controls the heartbeat interval in milliseconds (default 30,000). Unresponsive clients are terminated. `onClose` runs when the WebSocket server closes, and `await shared.close()` closes that server and clears its timer. Close connected clients and the HTTP server as part of host shutdown too.
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
Initially this will only be for authentication and will grant access to manifests hosted in SQLite on the server. Eventually
|
|
87
|
-
this will also allow the server to access manifests hosted in GitHub repositories.
|
|
91
|
+
Do not rely on the last client disconnecting to persist or automatically dispose of a named Vault. A host should explicitly decide retention, cleanup, and storage policy.
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
create an iFrame that can be used to communicate with the vault.
|
|
93
|
+
## Iframes and MessagePort previews
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
The older README described a server-hosted iframe proxying messages between a parent page and WebSockets. That complete transport/proxy service is not implemented here.
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
list of vaults from the server. This is also done using a post message. The server will then respond with a list of
|
|
96
|
-
vaults that the user has access to. The client will then be able to connect to the vault.
|
|
97
|
+
There is now a separate browser-side preview path: the shell exports `createIframeVaultBridge(vault, port)`, and client-vault exports `MessagePortClientVault`. The shell bridge sends initial state and host updates but rejects actions from the preview, making it a read-only preview bridge. It returns a cleanup function. The embedding host still has to create/transfer the MessagePort and establish the iframe relationship.
|
|
97
98
|
|
|
99
|
+
## Persistence and hosted collaboration: design notes
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
The original direction remains useful for hosts extending this package, but these features are **not provided by the current server**:
|
|
100
102
|
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
103
|
+
- A `.vaultfile` mapping a source manifest to files, periodic snapshots, and loading/saving to disk.
|
|
104
|
+
- Authentication, allowed-origin checks, project ownership, and distinct read-only/read-write permissions.
|
|
105
|
+
- Durable action history, reconnect/replay, and rejection/reconciliation of stale edits.
|
|
106
|
+
- GitHub login, repository-backed manifests, editing branches, commits, and pull requests.
|
|
107
|
+
- A user-scoped project API and authenticated iframe bootstrap.
|
|
106
108
|
|
|
109
|
+
One proposed file mapping looked like this (a design example, **not** a configuration file this package reads):
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"source": { "id": "https://example.org/manifest", "type": "Manifest" },
|
|
114
|
+
"resources": { "https://example.org/manifest": "./manifest.json" }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
109
117
|
|
|
110
|
-
-
|
|
111
|
-
- Client vault
|
|
118
|
+
For durable storage, a host could serialize `shared.vault.toPresentation3(reference)` and restore it on startup. For GitHub-backed work, that serialization would become the input to the host's commit/PR workflow. These are integration responsibilities, not automatic behavior of `MultiVaultServer`.
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
1. Creates normal internal vault instance
|
|
115
|
-
2. Loads manifest from file system
|
|
116
|
-
3. Handles client opening and closing connections
|
|
117
|
-
4. Accepts client actions
|
|
118
|
-
- Applies action to internal vault instance
|
|
119
|
-
- Responds to client with result of action
|
|
120
|
-
- Broadcasts action to all connected clients
|
|
121
|
-
5. Saves manifest to file system periodically
|
|
120
|
+
The current implementation accepts Vault actions from connected clients without authentication. Bind a development instance to loopback, as shown above; deploying a collaborative service requires implementing the access and validation boundaries described here.
|
|
122
121
|
|
|
122
|
+
## API overview
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- Loads initial state from server vault, including last action id
|
|
128
|
-
2. Sends actions to server vault
|
|
129
|
-
- Includes last action id
|
|
130
|
-
- Waits for response from server vault
|
|
131
|
-
- Updates state based on response from server vault
|
|
132
|
-
- If response is an error then update state to match server vault
|
|
133
|
-
- May retry action if response is a conflict
|
|
134
|
-
3. Receives actions from server vault
|
|
135
|
-
4. Applies actions to internal vault instance
|
|
124
|
+
- `MultiVaultServer`: HTTP server, `/vault` listing, and WebSocket upgrades under `/vault/<id>`.
|
|
125
|
+
- `ServerVault`: one shared Vault, action broadcasting, connection heartbeat, and `close()`.
|
|
126
|
+
- `ServerVaultOptions`, `ClientAction`, and `ServerAction`: configuration and message types.
|
|
136
127
|
|
|
128
|
+
See the [source exports](https://github.com/digirati-co-uk/iiif-manifest-editor/tree/main/packages/server-vault/src) for the complete API and TypeScript definitions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manifest-editor/server-vault",
|
|
3
|
-
"version": "2.2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bugs": "https://github.com/digirati-co-uk/iiif-manifest-editor/issues",
|
|
6
6
|
"repository": {
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "903673e7c35feed673fc100cb0efe34733057c91"
|
|
61
61
|
}
|