@automerge/automerge-repo 2.5.2 → 2.5.3
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/presence/PeerPresenceInfo.d.ts +2 -4
- package/dist/presence/PeerPresenceInfo.d.ts.map +1 -1
- package/dist/presence/PeerPresenceInfo.js +2 -4
- package/dist/presence/PeerStateView.d.ts +48 -57
- package/dist/presence/PeerStateView.d.ts.map +1 -1
- package/dist/presence/PeerStateView.js +66 -115
- package/dist/presence/Presence.d.ts +2 -8
- package/dist/presence/Presence.d.ts.map +1 -1
- package/dist/presence/Presence.js +1 -16
- package/dist/presence/types.d.ts +4 -12
- package/dist/presence/types.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -2
- package/src/presence/PeerPresenceInfo.ts +3 -15
- package/src/presence/PeerStateView.ts +98 -126
- package/src/presence/Presence.ts +1 -28
- package/src/presence/types.ts +4 -14
- package/src/synchronizer/CollectionSynchronizer.ts +1 -1
- package/test/Presence.test.ts +0 -4
- package/test/Repo.test.ts +92 -0
- package/test/helpers/connectRepos.ts +26 -1
- package/dist/helpers/array.d.ts +0 -2
- package/dist/helpers/array.d.ts.map +0 -1
- package/dist/helpers/array.js +0 -3
- package/src/helpers/array.ts +0 -3
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { isValidAutomergeUrl, isValidDocumentId, parseAutomergeUrl, stringifyAut
|
|
|
30
30
|
export { Repo } from "./Repo.js";
|
|
31
31
|
export { Presence } from "./presence/Presence.js";
|
|
32
32
|
export { PeerStateView } from "./presence/PeerStateView.js";
|
|
33
|
-
export type { PeerState, PresenceState, PresenceConfig,
|
|
33
|
+
export type { PeerState, PresenceState, PresenceConfig, } from "./presence/types.js";
|
|
34
34
|
export { NetworkAdapter } from "./network/NetworkAdapter.js";
|
|
35
35
|
export type { NetworkAdapterInterface } from "./network/NetworkAdapterInterface.js";
|
|
36
36
|
export { isRepoMessage } from "./network/messages.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,WAAW,GACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,YAAY,EACV,SAAS,EACT,aAAa,EACb,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PeerId } from "../types.js";
|
|
2
2
|
import { PeerStateView } from "./PeerStateView.js";
|
|
3
|
-
import {
|
|
3
|
+
import { PresenceState } from "./types.js";
|
|
4
4
|
export declare class PeerPresenceInfo<State extends PresenceState> {
|
|
5
5
|
#private;
|
|
6
6
|
readonly ttl: number;
|
|
@@ -25,10 +25,8 @@ export declare class PeerPresenceInfo<State extends PresenceState> {
|
|
|
25
25
|
* @param peerId
|
|
26
26
|
* @param value
|
|
27
27
|
*/
|
|
28
|
-
update({ peerId,
|
|
28
|
+
update({ peerId, value }: {
|
|
29
29
|
peerId: PeerId;
|
|
30
|
-
deviceId?: DeviceId;
|
|
31
|
-
userId?: UserId;
|
|
32
30
|
value: Partial<State>;
|
|
33
31
|
}): void;
|
|
34
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PeerPresenceInfo.d.ts","sourceRoot":"","sources":["../../src/presence/PeerPresenceInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"PeerPresenceInfo.d.ts","sourceRoot":"","sources":["../../src/presence/PeerPresenceInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,qBAAa,gBAAgB,CAAC,KAAK,SAAS,aAAa;;IAS3C,QAAQ,CAAC,GAAG,EAAE,MAAM;IANhC;;;;;OAKG;gBACkB,GAAG,EAAE,MAAM;IAEhC,GAAG,CAAC,MAAM,EAAE,MAAM;IAIlB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM;IAcvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;KAAE;IAkBnE;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM;IAUrB;;;OAGG;IACH,KAAK;IAiBL;;OAEG;IACH,IAAI,MAAM,yBAET;CACF"}
|
|
@@ -39,7 +39,7 @@ export class PeerPresenceInfo {
|
|
|
39
39
|
* @param peerId
|
|
40
40
|
* @param value
|
|
41
41
|
*/
|
|
42
|
-
update({ peerId,
|
|
42
|
+
update({ peerId, value }) {
|
|
43
43
|
const peerState = this.#peerStates.value[peerId];
|
|
44
44
|
const existingState = peerState?.value ?? {};
|
|
45
45
|
const now = Date.now();
|
|
@@ -47,10 +47,8 @@ export class PeerPresenceInfo {
|
|
|
47
47
|
...this.#peerStates.value,
|
|
48
48
|
[peerId]: {
|
|
49
49
|
peerId,
|
|
50
|
-
deviceId,
|
|
51
|
-
userId,
|
|
52
|
-
lastActiveAt: now,
|
|
53
50
|
lastSeenAt: now,
|
|
51
|
+
lastActiveAt: now,
|
|
54
52
|
value: {
|
|
55
53
|
...existingState,
|
|
56
54
|
...value,
|
|
@@ -1,69 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare class PeerStateView<State extends PresenceState> {
|
|
4
|
-
readonly value: PeerStatesValue<State>;
|
|
5
|
-
constructor(value: PeerStatesValue<State>);
|
|
6
|
-
/**
|
|
7
|
-
* Get all users.
|
|
8
|
-
*
|
|
9
|
-
* @returns Array of user presence {@link State}s
|
|
10
|
-
*/
|
|
11
|
-
get users(): (import("./types.js").PeerState<State> | undefined)[];
|
|
1
|
+
import { PeerState, PeerStatesValue, PresenceState } from "./types.js";
|
|
2
|
+
export type GetStatesOpts<State extends PresenceState, SummaryState> = {
|
|
12
3
|
/**
|
|
13
|
-
*
|
|
4
|
+
* Function to derive a grouping key from a peer state. This can be used to
|
|
5
|
+
* group peers and consider presence activity by an arbitrary attribute of the
|
|
6
|
+
* presence state (e.g., user or device) rather than by peer.
|
|
14
7
|
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
get devices(): (import("./types.js").PeerState<State> | undefined)[];
|
|
18
|
-
/**
|
|
19
|
-
* Get all peers.
|
|
20
|
-
*
|
|
21
|
-
* @returns Array of peer presence {@link State}s
|
|
22
|
-
*/
|
|
23
|
-
get peers(): import("./types.js").PeerState<State>[];
|
|
24
|
-
/**
|
|
25
|
-
* Get all peer ids for this user.
|
|
8
|
+
* This is useful when a user has multiple devices, or multiple peers (e.g.,
|
|
9
|
+
* tabs) on a single device.
|
|
26
10
|
*
|
|
27
|
-
* @param
|
|
28
|
-
* @returns
|
|
11
|
+
* @param state state of a peer
|
|
12
|
+
* @returns key that should be used to consolidate activity from that peer
|
|
29
13
|
*/
|
|
30
|
-
|
|
14
|
+
groupingFn?: (state: PeerState<State>) => PropertyKey;
|
|
31
15
|
/**
|
|
32
|
-
*
|
|
16
|
+
* Function to summarize the presence activity from several different peers in
|
|
17
|
+
* a group.
|
|
33
18
|
*
|
|
34
|
-
* @param
|
|
35
|
-
* @returns
|
|
19
|
+
* @param states states of all peers in a group, as grouped by {@param keyFn}
|
|
20
|
+
* @returns a value summarizing presence for this group
|
|
36
21
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Return the peer from this group that sent a state update most recently
|
|
47
|
-
*
|
|
48
|
-
* @param peers
|
|
49
|
-
* @returns id of most recently seen peer
|
|
50
|
-
*/
|
|
51
|
-
getLastActivePeer(peers: PeerId[]): PeerId | undefined;
|
|
52
|
-
/**
|
|
53
|
-
* Get current ephemeral state value for this user's most-recently-active
|
|
54
|
-
* peer.
|
|
55
|
-
*
|
|
56
|
-
* @param userId
|
|
57
|
-
* @returns user's {@link State}
|
|
58
|
-
*/
|
|
59
|
-
getUserState(userId: UserId): import("./types.js").PeerState<State> | undefined;
|
|
22
|
+
summaryFn?: (states: PeerState<State>[]) => SummaryState;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A grouped view of peer states.
|
|
26
|
+
*/
|
|
27
|
+
export declare class PeerStateView<State extends PresenceState> {
|
|
28
|
+
readonly value: PeerStatesValue<State>;
|
|
29
|
+
constructor(value: PeerStatesValue<State>);
|
|
60
30
|
/**
|
|
61
|
-
* Get
|
|
62
|
-
*
|
|
31
|
+
* Get the presence state of all peers. By default, each peer is its own
|
|
32
|
+
* group, but presence activity can be aggregated by arbitrary criteria.
|
|
63
33
|
*
|
|
64
|
-
* @param
|
|
65
|
-
* @returns
|
|
34
|
+
* @param opts
|
|
35
|
+
* @returns presence state for all groups
|
|
66
36
|
*/
|
|
67
|
-
|
|
37
|
+
getStates<SummaryState = PeerState<State>>(opts?: GetStatesOpts<State, SummaryState>): Record<PropertyKey, SummaryState>;
|
|
68
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the peerId of this peer.
|
|
41
|
+
*
|
|
42
|
+
* @param peer
|
|
43
|
+
* @returns peer id
|
|
44
|
+
*/
|
|
45
|
+
export declare function peerIdentity<State extends PresenceState>(peer: PeerState<State>): import("../types.js").PeerId;
|
|
46
|
+
/**
|
|
47
|
+
* Find the peer that most recently sent a state update.
|
|
48
|
+
*
|
|
49
|
+
* @param peers
|
|
50
|
+
* @returns id of most recently active peer
|
|
51
|
+
*/
|
|
52
|
+
export declare function getLastActivePeer<State extends PresenceState>(peers: PeerState<State>[]): PeerState<State> | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Find the peer that most recently sent a heartbeat.
|
|
55
|
+
*
|
|
56
|
+
* @param peers
|
|
57
|
+
* @returns id of most recently seen peer
|
|
58
|
+
*/
|
|
59
|
+
export declare function getLastSeenPeer<State extends PresenceState>(peers: PeerState<State>[]): PeerState<State> | undefined;
|
|
69
60
|
//# sourceMappingURL=PeerStateView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PeerStateView.d.ts","sourceRoot":"","sources":["../../src/presence/PeerStateView.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PeerStateView.d.ts","sourceRoot":"","sources":["../../src/presence/PeerStateView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEtE,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,aAAa,EAAE,YAAY,IAAI;IACrE;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,WAAW,CAAA;IACrD;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,YAAY,CAAA;CACzD,CAAA;AAED;;GAEG;AACH,qBAAa,aAAa,CAAC,KAAK,SAAS,aAAa;IACpD,QAAQ,CAAC,KAAK,yBAAA;gBAEF,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC;IAIzC;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,EACvC,IAAI,CAAC,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC;CAoB5C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,aAAa,EACtD,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,gCAGvB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,SAAS,aAAa,EAC3D,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,gCAgB1B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAS,aAAa,EACzD,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,gCAgB1B"}
|
|
@@ -1,131 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A grouped view of peer states.
|
|
3
|
+
*/
|
|
2
4
|
export class PeerStateView {
|
|
3
5
|
value;
|
|
4
6
|
constructor(value) {
|
|
5
7
|
this.value = value;
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
8
|
-
* Get all
|
|
10
|
+
* Get the presence state of all peers. By default, each peer is its own
|
|
11
|
+
* group, but presence activity can be aggregated by arbitrary criteria.
|
|
9
12
|
*
|
|
10
|
-
* @
|
|
13
|
+
* @param opts
|
|
14
|
+
* @returns presence state for all groups
|
|
11
15
|
*/
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
|
-
get devices() {
|
|
22
|
-
const deviceIds = unique(Object.values(this.value).map(peerState => peerState.deviceId));
|
|
23
|
-
return deviceIds.map(d => this.getDeviceState(d));
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Get all peers.
|
|
27
|
-
*
|
|
28
|
-
* @returns Array of peer presence {@link State}s
|
|
29
|
-
*/
|
|
30
|
-
get peers() {
|
|
31
|
-
return Object.values(this.value);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Get all peer ids for this user.
|
|
35
|
-
*
|
|
36
|
-
* @param userId
|
|
37
|
-
* @returns Array of peer ids for this user
|
|
38
|
-
*/
|
|
39
|
-
getUserPeers(userId) {
|
|
40
|
-
return Object.values(this.value)
|
|
41
|
-
.filter(peerState => peerState.userId === userId)
|
|
42
|
-
.map(peerState => peerState.peerId);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Get all peers for this device.
|
|
46
|
-
*
|
|
47
|
-
* @param deviceId
|
|
48
|
-
* @returns Array of peer ids for this device
|
|
49
|
-
*/
|
|
50
|
-
getDevicePeers(deviceId) {
|
|
51
|
-
return Object.values(this.value)
|
|
52
|
-
.filter(peerState => peerState.deviceId === deviceId)
|
|
53
|
-
.map(peerState => peerState.peerId);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Return the most-recently-seen peer from this group.
|
|
57
|
-
*
|
|
58
|
-
* @param peers
|
|
59
|
-
* @returns id of most recently seen peer
|
|
60
|
-
*/
|
|
61
|
-
getLastSeenPeer(peers) {
|
|
62
|
-
let freshestLastSeenAt;
|
|
63
|
-
return peers.reduce((freshest, curr) => {
|
|
64
|
-
const lastSeenAt = this.value[curr]?.lastSeenAt;
|
|
65
|
-
if (!lastSeenAt) {
|
|
66
|
-
return freshest;
|
|
16
|
+
getStates(opts) {
|
|
17
|
+
const groupingFn = opts?.groupingFn ?? peerIdentity;
|
|
18
|
+
const summaryFn = opts?.summaryFn ??
|
|
19
|
+
getLastActivePeer;
|
|
20
|
+
const statesByKey = Object.values(this.value).reduce((byKey, curr) => {
|
|
21
|
+
const key = groupingFn(curr);
|
|
22
|
+
if (!(key in byKey)) {
|
|
23
|
+
byKey[key] = [];
|
|
67
24
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
25
|
+
byKey[key].push(curr);
|
|
26
|
+
return byKey;
|
|
27
|
+
}, {});
|
|
28
|
+
return Object.entries(statesByKey).reduce((result, [key, states]) => {
|
|
29
|
+
result[key] = summaryFn(states);
|
|
30
|
+
return result;
|
|
31
|
+
}, {});
|
|
74
32
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the peerId of this peer.
|
|
36
|
+
*
|
|
37
|
+
* @param peer
|
|
38
|
+
* @returns peer id
|
|
39
|
+
*/
|
|
40
|
+
export function peerIdentity(peer) {
|
|
41
|
+
return peer.peerId;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find the peer that most recently sent a state update.
|
|
45
|
+
*
|
|
46
|
+
* @param peers
|
|
47
|
+
* @returns id of most recently active peer
|
|
48
|
+
*/
|
|
49
|
+
export function getLastActivePeer(peers) {
|
|
50
|
+
let freshestLastActiveAt;
|
|
51
|
+
return peers.reduce((freshest, curr) => {
|
|
52
|
+
const lastActiveAt = curr.lastActiveAt;
|
|
53
|
+
if (!lastActiveAt) {
|
|
92
54
|
return freshest;
|
|
93
|
-
}, undefined);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Get current ephemeral state value for this user's most-recently-active
|
|
97
|
-
* peer.
|
|
98
|
-
*
|
|
99
|
-
* @param userId
|
|
100
|
-
* @returns user's {@link State}
|
|
101
|
-
*/
|
|
102
|
-
getUserState(userId) {
|
|
103
|
-
const peers = this.getUserPeers(userId);
|
|
104
|
-
if (!peers) {
|
|
105
|
-
return undefined;
|
|
106
55
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
56
|
+
if (!freshest || lastActiveAt > freshestLastActiveAt) {
|
|
57
|
+
freshestLastActiveAt = lastActiveAt;
|
|
58
|
+
return curr;
|
|
110
59
|
}
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
60
|
+
return freshest;
|
|
61
|
+
}, undefined);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Find the peer that most recently sent a heartbeat.
|
|
65
|
+
*
|
|
66
|
+
* @param peers
|
|
67
|
+
* @returns id of most recently seen peer
|
|
68
|
+
*/
|
|
69
|
+
export function getLastSeenPeer(peers) {
|
|
70
|
+
let freshestLastSeenAt;
|
|
71
|
+
return peers.reduce((freshest, curr) => {
|
|
72
|
+
const lastSeenAt = curr.lastSeenAt;
|
|
73
|
+
if (!lastSeenAt) {
|
|
74
|
+
return freshest;
|
|
124
75
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
76
|
+
if (!freshest || lastSeenAt > freshestLastSeenAt) {
|
|
77
|
+
freshestLastSeenAt = lastSeenAt;
|
|
78
|
+
return curr;
|
|
128
79
|
}
|
|
129
|
-
return
|
|
130
|
-
}
|
|
80
|
+
return freshest;
|
|
81
|
+
}, undefined);
|
|
131
82
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { DocHandle } from "../DocHandle.js";
|
|
3
|
-
import {
|
|
3
|
+
import { PresenceConfig, PresenceEvents, PresenceState } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Presence encapsulates ephemeral state communication for a specific doc
|
|
6
6
|
* handle. It tracks caller-provided local state and broadcasts that state to
|
|
@@ -14,20 +14,14 @@ import { DeviceId, PresenceConfig, PresenceEvents, PresenceState, UserId } from
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class Presence<State extends PresenceState, DocType = any> extends EventEmitter<PresenceEvents> {
|
|
16
16
|
#private;
|
|
17
|
-
readonly deviceId?: DeviceId;
|
|
18
|
-
readonly userId?: UserId;
|
|
19
17
|
/**
|
|
20
18
|
* Create a new Presence to share ephemeral state with peers.
|
|
21
19
|
*
|
|
22
20
|
* @param config see {@link PresenceConfig}
|
|
23
21
|
* @returns
|
|
24
22
|
*/
|
|
25
|
-
constructor({ handle
|
|
23
|
+
constructor({ handle }: {
|
|
26
24
|
handle: DocHandle<DocType>;
|
|
27
|
-
/** Our device id (like userId, this is unverified; peers can send anything) */
|
|
28
|
-
deviceId?: DeviceId;
|
|
29
|
-
/** Our user id (this is unverified; peers can send anything) */
|
|
30
|
-
userId?: UserId;
|
|
31
25
|
});
|
|
32
26
|
/**
|
|
33
27
|
* Start listening to ephemeral messages on the handle, broadcast initial
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Presence.d.ts","sourceRoot":"","sources":["../../src/presence/Presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,EAAE,SAAS,EAAoC,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"Presence.d.ts","sourceRoot":"","sources":["../../src/presence/Presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,EAAE,SAAS,EAAoC,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EACL,cAAc,EACd,cAAc,EAGd,aAAa,EACd,MAAM,YAAY,CAAA;AAQnB;;;;;;;;;;GAUG;AACH,qBAAa,QAAQ,CACnB,KAAK,SAAS,aAAa,EAC3B,OAAO,GAAG,GAAG,CACb,SAAQ,YAAY,CAAC,cAAc,CAAC;;IAgBpC;;;;;OAKG;gBACS,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;KAAE;IAOtD;;;OAGG;IACH,KAAK,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,cAAc,CAAC,KAAK,CAAC;IAqErE;;OAEG;IACH,aAAa;IAIb;;OAEG;IACH,aAAa;IAIb;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,SAAS,MAAM,KAAK,EACnC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;IAQvB;;;OAGG;IACH,IAAI,OAAO,YAEV;IAED;;;;;;;;;;OAUG;IACH,IAAI;IAeJ,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -14,8 +14,6 @@ import { PeerPresenceInfo } from "./PeerPresenceInfo.js";
|
|
|
14
14
|
*/
|
|
15
15
|
export class Presence extends EventEmitter {
|
|
16
16
|
#handle;
|
|
17
|
-
deviceId;
|
|
18
|
-
userId;
|
|
19
17
|
#peers;
|
|
20
18
|
#localState;
|
|
21
19
|
#heartbeatMs;
|
|
@@ -30,13 +28,11 @@ export class Presence extends EventEmitter {
|
|
|
30
28
|
* @param config see {@link PresenceConfig}
|
|
31
29
|
* @returns
|
|
32
30
|
*/
|
|
33
|
-
constructor({ handle
|
|
31
|
+
constructor({ handle }) {
|
|
34
32
|
super();
|
|
35
33
|
this.#handle = handle;
|
|
36
34
|
this.#peers = new PeerPresenceInfo(DEFAULT_PEER_TTL_MS);
|
|
37
35
|
this.#localState = {};
|
|
38
|
-
this.userId = userId;
|
|
39
|
-
this.deviceId = deviceId;
|
|
40
36
|
}
|
|
41
37
|
/**
|
|
42
38
|
* Start listening to ephemeral messages on the handle, broadcast initial
|
|
@@ -60,7 +56,6 @@ export class Presence extends EventEmitter {
|
|
|
60
56
|
return;
|
|
61
57
|
}
|
|
62
58
|
const message = envelope[PRESENCE_MESSAGE_MARKER];
|
|
63
|
-
const { deviceId, userId } = message;
|
|
64
59
|
if (!this.#peers.has(peerId)) {
|
|
65
60
|
this.announce();
|
|
66
61
|
}
|
|
@@ -76,15 +71,11 @@ export class Presence extends EventEmitter {
|
|
|
76
71
|
case "update":
|
|
77
72
|
this.#peers.update({
|
|
78
73
|
peerId,
|
|
79
|
-
deviceId,
|
|
80
|
-
userId,
|
|
81
74
|
value: { [message.channel]: message.value },
|
|
82
75
|
});
|
|
83
76
|
this.emit("update", {
|
|
84
77
|
type: "update",
|
|
85
78
|
peerId,
|
|
86
|
-
deviceId,
|
|
87
|
-
userId,
|
|
88
79
|
channel: message.channel,
|
|
89
80
|
value: message.value,
|
|
90
81
|
});
|
|
@@ -92,15 +83,11 @@ export class Presence extends EventEmitter {
|
|
|
92
83
|
case "snapshot":
|
|
93
84
|
this.#peers.update({
|
|
94
85
|
peerId,
|
|
95
|
-
deviceId,
|
|
96
|
-
userId,
|
|
97
86
|
value: message.state,
|
|
98
87
|
});
|
|
99
88
|
this.emit("snapshot", {
|
|
100
89
|
type: "snapshot",
|
|
101
90
|
peerId,
|
|
102
|
-
deviceId,
|
|
103
|
-
userId,
|
|
104
91
|
state: message.state,
|
|
105
92
|
});
|
|
106
93
|
break;
|
|
@@ -195,8 +182,6 @@ export class Presence extends EventEmitter {
|
|
|
195
182
|
}
|
|
196
183
|
doBroadcast(type, extra) {
|
|
197
184
|
this.send({
|
|
198
|
-
userId: this.userId,
|
|
199
|
-
deviceId: this.deviceId,
|
|
200
185
|
type,
|
|
201
186
|
...extra,
|
|
202
187
|
});
|
package/dist/presence/types.d.ts
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import { PeerId } from "../types.js";
|
|
2
2
|
import { PRESENCE_MESSAGE_MARKER } from "./constants.js";
|
|
3
|
-
export type UserId = unknown;
|
|
4
|
-
export type DeviceId = unknown;
|
|
5
3
|
export type PresenceState = Record<string, any>;
|
|
6
4
|
export type PeerStatesValue<State extends PresenceState> = Record<PeerId, PeerState<State>>;
|
|
7
5
|
export type PeerState<State extends PresenceState> = {
|
|
8
6
|
peerId: PeerId;
|
|
9
7
|
lastActiveAt: number;
|
|
10
8
|
lastSeenAt: number;
|
|
11
|
-
deviceId?: DeviceId;
|
|
12
|
-
userId?: UserId;
|
|
13
9
|
value: State;
|
|
14
10
|
};
|
|
15
|
-
type
|
|
16
|
-
deviceId?: DeviceId;
|
|
17
|
-
userId?: UserId;
|
|
18
|
-
};
|
|
19
|
-
type PresenceMessageUpdate = PresenceMessageBase & {
|
|
11
|
+
type PresenceMessageUpdate = {
|
|
20
12
|
type: "update";
|
|
21
13
|
channel: string;
|
|
22
14
|
value: any;
|
|
23
15
|
};
|
|
24
|
-
type PresenceMessageSnapshot =
|
|
16
|
+
type PresenceMessageSnapshot = {
|
|
25
17
|
type: "snapshot";
|
|
26
18
|
state: any;
|
|
27
19
|
};
|
|
28
|
-
type PresenceMessageHeartbeat =
|
|
20
|
+
type PresenceMessageHeartbeat = {
|
|
29
21
|
type: "heartbeat";
|
|
30
22
|
};
|
|
31
|
-
type PresenceMessageGoodbye =
|
|
23
|
+
type PresenceMessageGoodbye = {
|
|
32
24
|
type: "goodbye";
|
|
33
25
|
};
|
|
34
26
|
export type PresenceMessage = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/presence/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAExD,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/presence/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAExD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAE/C,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,aAAa,IAAI,MAAM,CAC/D,MAAM,EACN,SAAS,CAAC,KAAK,CAAC,CACjB,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,aAAa,IAAI;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAED,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,GAAG,CAAA;CACX,CAAA;AAED,KAAK,uBAAuB,GAAG;IAC7B,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,GAAG,CAAA;CACX,CAAA;AAED,KAAK,wBAAwB,GAAG;IAC9B,IAAI,EAAE,WAAW,CAAA;CAClB,CAAA;AAED,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,uBAAuB,CAAC,EACrB,qBAAqB,GACrB,uBAAuB,GACvB,wBAAwB,GACxB,sBAAsB,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC7B,eAAe,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,KAAK,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpC,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG,UAAU,CAAA;AACpE,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,GAAG,UAAU,CAAA;AACxE,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,GAAG,UAAU,CAAA;AAC1E,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG,UAAU,CAAA;AACtE,MAAM,MAAM,oBAAoB,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEvD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACxC;;OAEG;IACH,QAAQ,EAAE,CAAC,CAAC,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAC5C;;OAEG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAC9C;;OAEG;IACH,OAAO,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC1C;;OAEG;IACH,OAAO,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,aAAa,IAAI;IACxD,mDAAmD;IACnD,YAAY,EAAE,KAAK,CAAA;IACnB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA"}
|
|
@@ -87,7 +87,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
87
87
|
// Record the request so that even if access is denied now, we know that the
|
|
88
88
|
// peer requested the document so that if the share policy changes we know
|
|
89
89
|
// to begin syncing with this peer
|
|
90
|
-
if (message.type === "request") {
|
|
90
|
+
if (message.type === "request" || message.type === "sync") {
|
|
91
91
|
if (!this.#hasRequested.has(documentId)) {
|
|
92
92
|
this.#hasRequested.set(documentId, new Set());
|
|
93
93
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "A repository object to manage a collection of automerge documents",
|
|
5
5
|
"repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
|
|
6
6
|
"author": "Peter van Hardenberg <pvh@pvh.ca>",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "082d2134aa296b7a966210bc2ec744096733e273"
|
|
63
63
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PeerId } from "../types.js"
|
|
2
2
|
import { PeerStateView } from "./PeerStateView.js"
|
|
3
|
-
import {
|
|
3
|
+
import { PresenceState } from "./types.js"
|
|
4
4
|
|
|
5
5
|
export class PeerPresenceInfo<State extends PresenceState> {
|
|
6
6
|
#peerStates = new PeerStateView<State>({})
|
|
@@ -43,17 +43,7 @@ export class PeerPresenceInfo<State extends PresenceState> {
|
|
|
43
43
|
* @param peerId
|
|
44
44
|
* @param value
|
|
45
45
|
*/
|
|
46
|
-
update({
|
|
47
|
-
peerId,
|
|
48
|
-
deviceId,
|
|
49
|
-
userId,
|
|
50
|
-
value,
|
|
51
|
-
}: {
|
|
52
|
-
peerId: PeerId
|
|
53
|
-
deviceId?: DeviceId
|
|
54
|
-
userId?: UserId
|
|
55
|
-
value: Partial<State>
|
|
56
|
-
}) {
|
|
46
|
+
update({ peerId, value }: { peerId: PeerId; value: Partial<State> }) {
|
|
57
47
|
const peerState = this.#peerStates.value[peerId]
|
|
58
48
|
const existingState = peerState?.value ?? ({} as State)
|
|
59
49
|
const now = Date.now()
|
|
@@ -61,10 +51,8 @@ export class PeerPresenceInfo<State extends PresenceState> {
|
|
|
61
51
|
...this.#peerStates.value,
|
|
62
52
|
[peerId]: {
|
|
63
53
|
peerId,
|
|
64
|
-
deviceId,
|
|
65
|
-
userId,
|
|
66
|
-
lastActiveAt: now,
|
|
67
54
|
lastSeenAt: now,
|
|
55
|
+
lastActiveAt: now,
|
|
68
56
|
value: {
|
|
69
57
|
...existingState,
|
|
70
58
|
...value,
|
|
@@ -1,154 +1,126 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PeerId } from "../types.js"
|
|
3
|
-
import { DeviceId, PeerStatesValue, PresenceState, UserId } from "./types.js"
|
|
4
|
-
|
|
5
|
-
export class PeerStateView<State extends PresenceState> {
|
|
6
|
-
readonly value
|
|
7
|
-
|
|
8
|
-
constructor(value: PeerStatesValue<State>) {
|
|
9
|
-
this.value = value
|
|
10
|
-
}
|
|
1
|
+
import { PeerState, PeerStatesValue, PresenceState } from "./types.js"
|
|
11
2
|
|
|
3
|
+
export type GetStatesOpts<State extends PresenceState, SummaryState> = {
|
|
12
4
|
/**
|
|
13
|
-
*
|
|
5
|
+
* Function to derive a grouping key from a peer state. This can be used to
|
|
6
|
+
* group peers and consider presence activity by an arbitrary attribute of the
|
|
7
|
+
* presence state (e.g., user or device) rather than by peer.
|
|
14
8
|
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
get users() {
|
|
18
|
-
const userIds = unique(
|
|
19
|
-
Object.values(this.value).map(peerState => peerState.userId)
|
|
20
|
-
)
|
|
21
|
-
return userIds.map(u => this.getUserState(u))
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get all devices.
|
|
9
|
+
* This is useful when a user has multiple devices, or multiple peers (e.g.,
|
|
10
|
+
* tabs) on a single device.
|
|
26
11
|
*
|
|
27
|
-
* @
|
|
12
|
+
* @param state state of a peer
|
|
13
|
+
* @returns key that should be used to consolidate activity from that peer
|
|
28
14
|
*/
|
|
29
|
-
|
|
30
|
-
const deviceIds = unique(
|
|
31
|
-
Object.values(this.value).map(peerState => peerState.deviceId)
|
|
32
|
-
)
|
|
33
|
-
return deviceIds.map(d => this.getDeviceState(d))
|
|
34
|
-
}
|
|
35
|
-
|
|
15
|
+
groupingFn?: (state: PeerState<State>) => PropertyKey
|
|
36
16
|
/**
|
|
37
|
-
*
|
|
17
|
+
* Function to summarize the presence activity from several different peers in
|
|
18
|
+
* a group.
|
|
38
19
|
*
|
|
39
|
-
* @
|
|
20
|
+
* @param states states of all peers in a group, as grouped by {@param keyFn}
|
|
21
|
+
* @returns a value summarizing presence for this group
|
|
40
22
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
23
|
+
summaryFn?: (states: PeerState<State>[]) => SummaryState
|
|
24
|
+
}
|
|
44
25
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
getUserPeers(userId: UserId) {
|
|
52
|
-
return Object.values(this.value)
|
|
53
|
-
.filter(peerState => peerState.userId === userId)
|
|
54
|
-
.map(peerState => peerState.peerId)
|
|
55
|
-
}
|
|
26
|
+
/**
|
|
27
|
+
* A grouped view of peer states.
|
|
28
|
+
*/
|
|
29
|
+
export class PeerStateView<State extends PresenceState> {
|
|
30
|
+
readonly value
|
|
56
31
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
* @param deviceId
|
|
61
|
-
* @returns Array of peer ids for this device
|
|
62
|
-
*/
|
|
63
|
-
getDevicePeers(deviceId: DeviceId) {
|
|
64
|
-
return Object.values(this.value)
|
|
65
|
-
.filter(peerState => peerState.deviceId === deviceId)
|
|
66
|
-
.map(peerState => peerState.peerId)
|
|
32
|
+
constructor(value: PeerStatesValue<State>) {
|
|
33
|
+
this.value = value
|
|
67
34
|
}
|
|
68
35
|
|
|
69
36
|
/**
|
|
70
|
-
*
|
|
37
|
+
* Get the presence state of all peers. By default, each peer is its own
|
|
38
|
+
* group, but presence activity can be aggregated by arbitrary criteria.
|
|
71
39
|
*
|
|
72
|
-
* @param
|
|
73
|
-
* @returns
|
|
40
|
+
* @param opts
|
|
41
|
+
* @returns presence state for all groups
|
|
74
42
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
43
|
+
getStates<SummaryState = PeerState<State>>(
|
|
44
|
+
opts?: GetStatesOpts<State, SummaryState>
|
|
45
|
+
) {
|
|
46
|
+
const groupingFn = opts?.groupingFn ?? peerIdentity
|
|
47
|
+
const summaryFn =
|
|
48
|
+
opts?.summaryFn ??
|
|
49
|
+
(getLastActivePeer as (states: PeerState<State>[]) => SummaryState)
|
|
50
|
+
const statesByKey = Object.values(this.value).reduce((byKey, curr) => {
|
|
51
|
+
const key = groupingFn(curr)
|
|
52
|
+
if (!(key in byKey)) {
|
|
53
|
+
byKey[key] = []
|
|
81
54
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return
|
|
89
|
-
},
|
|
55
|
+
byKey[key].push(curr)
|
|
56
|
+
|
|
57
|
+
return byKey
|
|
58
|
+
}, {} as Record<PropertyKey, PeerState<State>[]>)
|
|
59
|
+
return Object.entries(statesByKey).reduce((result, [key, states]) => {
|
|
60
|
+
result[key] = summaryFn(states)
|
|
61
|
+
return result
|
|
62
|
+
}, {} as Record<PropertyKey, SummaryState>)
|
|
90
63
|
}
|
|
64
|
+
}
|
|
91
65
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return freshest
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!freshest || lastActiveAt > freshestLastActiveAt) {
|
|
107
|
-
freshestLastActiveAt = lastActiveAt
|
|
108
|
-
return curr
|
|
109
|
-
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the peerId of this peer.
|
|
68
|
+
*
|
|
69
|
+
* @param peer
|
|
70
|
+
* @returns peer id
|
|
71
|
+
*/
|
|
72
|
+
export function peerIdentity<State extends PresenceState>(
|
|
73
|
+
peer: PeerState<State>
|
|
74
|
+
) {
|
|
75
|
+
return peer.peerId
|
|
76
|
+
}
|
|
110
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Find the peer that most recently sent a state update.
|
|
80
|
+
*
|
|
81
|
+
* @param peers
|
|
82
|
+
* @returns id of most recently active peer
|
|
83
|
+
*/
|
|
84
|
+
export function getLastActivePeer<State extends PresenceState>(
|
|
85
|
+
peers: PeerState<State>[]
|
|
86
|
+
) {
|
|
87
|
+
let freshestLastActiveAt: number
|
|
88
|
+
return peers.reduce((freshest, curr) => {
|
|
89
|
+
const lastActiveAt = curr.lastActiveAt
|
|
90
|
+
if (!lastActiveAt) {
|
|
111
91
|
return freshest
|
|
112
|
-
}, undefined)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get current ephemeral state value for this user's most-recently-active
|
|
117
|
-
* peer.
|
|
118
|
-
*
|
|
119
|
-
* @param userId
|
|
120
|
-
* @returns user's {@link State}
|
|
121
|
-
*/
|
|
122
|
-
getUserState(userId: UserId) {
|
|
123
|
-
const peers = this.getUserPeers(userId)
|
|
124
|
-
if (!peers) {
|
|
125
|
-
return undefined
|
|
126
92
|
}
|
|
127
|
-
|
|
128
|
-
if (!
|
|
129
|
-
|
|
93
|
+
|
|
94
|
+
if (!freshest || lastActiveAt > freshestLastActiveAt) {
|
|
95
|
+
freshestLastActiveAt = lastActiveAt
|
|
96
|
+
return curr
|
|
130
97
|
}
|
|
131
98
|
|
|
132
|
-
return
|
|
133
|
-
}
|
|
99
|
+
return freshest
|
|
100
|
+
}, undefined as PeerState<State> | undefined)
|
|
101
|
+
}
|
|
134
102
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Find the peer that most recently sent a heartbeat.
|
|
105
|
+
*
|
|
106
|
+
* @param peers
|
|
107
|
+
* @returns id of most recently seen peer
|
|
108
|
+
*/
|
|
109
|
+
export function getLastSeenPeer<State extends PresenceState>(
|
|
110
|
+
peers: PeerState<State>[]
|
|
111
|
+
) {
|
|
112
|
+
let freshestLastSeenAt: number
|
|
113
|
+
return peers.reduce((freshest, curr) => {
|
|
114
|
+
const lastSeenAt = curr.lastSeenAt
|
|
115
|
+
if (!lastSeenAt) {
|
|
116
|
+
return freshest
|
|
146
117
|
}
|
|
147
|
-
|
|
148
|
-
if (!
|
|
149
|
-
|
|
118
|
+
|
|
119
|
+
if (!freshest || lastSeenAt > freshestLastSeenAt) {
|
|
120
|
+
freshestLastSeenAt = lastSeenAt
|
|
121
|
+
return curr
|
|
150
122
|
}
|
|
151
123
|
|
|
152
|
-
return
|
|
153
|
-
}
|
|
124
|
+
return freshest
|
|
125
|
+
}, undefined as PeerState<State> | undefined)
|
|
154
126
|
}
|
package/src/presence/Presence.ts
CHANGED
|
@@ -2,13 +2,11 @@ import { EventEmitter } from "eventemitter3"
|
|
|
2
2
|
|
|
3
3
|
import { DocHandle, DocHandleEphemeralMessagePayload } from "../DocHandle.js"
|
|
4
4
|
import {
|
|
5
|
-
DeviceId,
|
|
6
5
|
PresenceConfig,
|
|
7
6
|
PresenceEvents,
|
|
8
7
|
PresenceMessage,
|
|
9
8
|
PresenceMessageType,
|
|
10
9
|
PresenceState,
|
|
11
|
-
UserId,
|
|
12
10
|
} from "./types.js"
|
|
13
11
|
import {
|
|
14
12
|
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
@@ -33,8 +31,6 @@ export class Presence<
|
|
|
33
31
|
DocType = any
|
|
34
32
|
> extends EventEmitter<PresenceEvents> {
|
|
35
33
|
#handle: DocHandle<DocType>
|
|
36
|
-
readonly deviceId?: DeviceId
|
|
37
|
-
readonly userId?: UserId
|
|
38
34
|
#peers: PeerPresenceInfo<State>
|
|
39
35
|
#localState: State
|
|
40
36
|
#heartbeatMs?: number
|
|
@@ -55,23 +51,11 @@ export class Presence<
|
|
|
55
51
|
* @param config see {@link PresenceConfig}
|
|
56
52
|
* @returns
|
|
57
53
|
*/
|
|
58
|
-
constructor({
|
|
59
|
-
handle,
|
|
60
|
-
deviceId,
|
|
61
|
-
userId,
|
|
62
|
-
}: {
|
|
63
|
-
handle: DocHandle<DocType>
|
|
64
|
-
/** Our device id (like userId, this is unverified; peers can send anything) */
|
|
65
|
-
deviceId?: DeviceId
|
|
66
|
-
/** Our user id (this is unverified; peers can send anything) */
|
|
67
|
-
userId?: UserId
|
|
68
|
-
}) {
|
|
54
|
+
constructor({ handle }: { handle: DocHandle<DocType> }) {
|
|
69
55
|
super()
|
|
70
56
|
this.#handle = handle
|
|
71
57
|
this.#peers = new PeerPresenceInfo<State>(DEFAULT_PEER_TTL_MS)
|
|
72
58
|
this.#localState = {} as State
|
|
73
|
-
this.userId = userId
|
|
74
|
-
this.deviceId = deviceId
|
|
75
59
|
}
|
|
76
60
|
|
|
77
61
|
/**
|
|
@@ -102,7 +86,6 @@ export class Presence<
|
|
|
102
86
|
}
|
|
103
87
|
|
|
104
88
|
const message = envelope[PRESENCE_MESSAGE_MARKER]
|
|
105
|
-
const { deviceId, userId } = message
|
|
106
89
|
|
|
107
90
|
if (!this.#peers.has(peerId)) {
|
|
108
91
|
this.announce()
|
|
@@ -120,15 +103,11 @@ export class Presence<
|
|
|
120
103
|
case "update":
|
|
121
104
|
this.#peers.update({
|
|
122
105
|
peerId,
|
|
123
|
-
deviceId,
|
|
124
|
-
userId,
|
|
125
106
|
value: { [message.channel]: message.value } as Partial<State>,
|
|
126
107
|
})
|
|
127
108
|
this.emit("update", {
|
|
128
109
|
type: "update",
|
|
129
110
|
peerId,
|
|
130
|
-
deviceId,
|
|
131
|
-
userId,
|
|
132
111
|
channel: message.channel,
|
|
133
112
|
value: message.value,
|
|
134
113
|
})
|
|
@@ -136,15 +115,11 @@ export class Presence<
|
|
|
136
115
|
case "snapshot":
|
|
137
116
|
this.#peers.update({
|
|
138
117
|
peerId,
|
|
139
|
-
deviceId,
|
|
140
|
-
userId,
|
|
141
118
|
value: message.state as State,
|
|
142
119
|
})
|
|
143
120
|
this.emit("snapshot", {
|
|
144
121
|
type: "snapshot",
|
|
145
122
|
peerId,
|
|
146
|
-
deviceId,
|
|
147
|
-
userId,
|
|
148
123
|
state: message.state,
|
|
149
124
|
})
|
|
150
125
|
break
|
|
@@ -256,8 +231,6 @@ export class Presence<
|
|
|
256
231
|
extra?: Record<string, unknown>
|
|
257
232
|
) {
|
|
258
233
|
this.send({
|
|
259
|
-
userId: this.userId,
|
|
260
|
-
deviceId: this.deviceId,
|
|
261
234
|
type,
|
|
262
235
|
...extra,
|
|
263
236
|
})
|
package/src/presence/types.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { PeerId } from "../types.js"
|
|
2
2
|
import { PRESENCE_MESSAGE_MARKER } from "./constants.js"
|
|
3
3
|
|
|
4
|
-
export type UserId = unknown
|
|
5
|
-
export type DeviceId = unknown
|
|
6
|
-
|
|
7
4
|
export type PresenceState = Record<string, any>
|
|
8
5
|
|
|
9
6
|
export type PeerStatesValue<State extends PresenceState> = Record<
|
|
@@ -15,32 +12,25 @@ export type PeerState<State extends PresenceState> = {
|
|
|
15
12
|
peerId: PeerId
|
|
16
13
|
lastActiveAt: number
|
|
17
14
|
lastSeenAt: number
|
|
18
|
-
deviceId?: DeviceId
|
|
19
|
-
userId?: UserId
|
|
20
15
|
value: State
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
type
|
|
24
|
-
deviceId?: DeviceId
|
|
25
|
-
userId?: UserId
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type PresenceMessageUpdate = PresenceMessageBase & {
|
|
18
|
+
type PresenceMessageUpdate = {
|
|
29
19
|
type: "update"
|
|
30
20
|
channel: string
|
|
31
21
|
value: any
|
|
32
22
|
}
|
|
33
23
|
|
|
34
|
-
type PresenceMessageSnapshot =
|
|
24
|
+
type PresenceMessageSnapshot = {
|
|
35
25
|
type: "snapshot"
|
|
36
26
|
state: any
|
|
37
27
|
}
|
|
38
28
|
|
|
39
|
-
type PresenceMessageHeartbeat =
|
|
29
|
+
type PresenceMessageHeartbeat = {
|
|
40
30
|
type: "heartbeat"
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
type PresenceMessageGoodbye =
|
|
33
|
+
type PresenceMessageGoodbye = {
|
|
44
34
|
type: "goodbye"
|
|
45
35
|
}
|
|
46
36
|
|
|
@@ -112,7 +112,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
112
112
|
// Record the request so that even if access is denied now, we know that the
|
|
113
113
|
// peer requested the document so that if the share policy changes we know
|
|
114
114
|
// to begin syncing with this peer
|
|
115
|
-
if (message.type === "request") {
|
|
115
|
+
if (message.type === "request" || message.type === "sync") {
|
|
116
116
|
if (!this.#hasRequested.has(documentId)) {
|
|
117
117
|
this.#hasRequested.set(documentId, new Set())
|
|
118
118
|
}
|
package/test/Presence.test.ts
CHANGED
|
@@ -31,15 +31,11 @@ describe("Presence", () => {
|
|
|
31
31
|
})
|
|
32
32
|
const alicePresence = new Presence<PresenceState>({
|
|
33
33
|
handle: aliceHandle,
|
|
34
|
-
userId: "alice",
|
|
35
|
-
deviceId: "phone",
|
|
36
34
|
})
|
|
37
35
|
|
|
38
36
|
const bobHandle = await bob.find(aliceHandle.url)
|
|
39
37
|
const bobPresence = new Presence<PresenceState>({
|
|
40
38
|
handle: bobHandle,
|
|
41
|
-
userId: "bob",
|
|
42
|
-
deviceId: "phone",
|
|
43
39
|
})
|
|
44
40
|
|
|
45
41
|
return {
|
package/test/Repo.test.ts
CHANGED
|
@@ -1247,6 +1247,98 @@ describe("Repo", () => {
|
|
|
1247
1247
|
assert.deepStrictEqual(bobHandle.doc(), { foo: "bar" })
|
|
1248
1248
|
})
|
|
1249
1249
|
|
|
1250
|
+
it("sync messages (not just requests) are recorded as peer interest for later share policy changes", async () => {
|
|
1251
|
+
// This test covers a scenario where two peers sync via a sync server.
|
|
1252
|
+
// Initially the sync server allows peerA but denies peerB. PeerB already
|
|
1253
|
+
// has the document (obtained directly from peerA), so when it connects to
|
|
1254
|
+
// the server it sends a "sync" message (not a "request"). The server
|
|
1255
|
+
// denies access. Later the server's share policy changes to allow peerB.
|
|
1256
|
+
// The server should then start pushing updates to peerB. Before the fix
|
|
1257
|
+
// in CollectionSynchronizer, only "request" messages were recorded as
|
|
1258
|
+
// expressing peer interest in a document. So when the share policy
|
|
1259
|
+
// changed, the server had no record that peerB wanted the document and
|
|
1260
|
+
// would not begin syncing — peerB would only receive updates after it
|
|
1261
|
+
// sent another sync message of its own (i.e. after making a write).
|
|
1262
|
+
|
|
1263
|
+
// Track whether peerB is allowed to sync with the server
|
|
1264
|
+
let peerBAllowed = false
|
|
1265
|
+
|
|
1266
|
+
const peerA = new Repo({
|
|
1267
|
+
peerId: "peerA" as PeerId,
|
|
1268
|
+
shareConfig: {
|
|
1269
|
+
announce: async () => true,
|
|
1270
|
+
access: async () => true,
|
|
1271
|
+
},
|
|
1272
|
+
})
|
|
1273
|
+
|
|
1274
|
+
const syncServer = new Repo({
|
|
1275
|
+
peerId: "syncServer" as PeerId,
|
|
1276
|
+
shareConfig: {
|
|
1277
|
+
// Server mode: never proactively announces docs to peers
|
|
1278
|
+
announce: async () => false,
|
|
1279
|
+
// Initially only peerA is allowed; peerB is denied
|
|
1280
|
+
access: async peerId => peerId === "peerA" || peerBAllowed,
|
|
1281
|
+
},
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
const peerB = new Repo({
|
|
1285
|
+
peerId: "peerB" as PeerId,
|
|
1286
|
+
shareConfig: {
|
|
1287
|
+
announce: async () => true,
|
|
1288
|
+
access: async () => true,
|
|
1289
|
+
},
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
// Connect peerA to syncServer so the server gets the document
|
|
1293
|
+
await connectRepos(peerA, syncServer)
|
|
1294
|
+
|
|
1295
|
+
// Create the document on peerA and sync it to the server
|
|
1296
|
+
const handle = peerA.create<TestDoc>({ foo: "bar" })
|
|
1297
|
+
await pause(50) // allow sync to propagate to server
|
|
1298
|
+
|
|
1299
|
+
// Connect peerA to peerB directly so peerB can obtain the document
|
|
1300
|
+
const peerABConnection = await connectRepos(peerA, peerB)
|
|
1301
|
+
const peerBHandle = await withTimeout(
|
|
1302
|
+
peerB.find<TestDoc>(handle.url),
|
|
1303
|
+
500
|
|
1304
|
+
)
|
|
1305
|
+
assert.ok(
|
|
1306
|
+
peerBHandle,
|
|
1307
|
+
"peerB should have obtained the document from peerA"
|
|
1308
|
+
)
|
|
1309
|
+
assert.deepStrictEqual(peerBHandle!.doc(), { foo: "bar" })
|
|
1310
|
+
|
|
1311
|
+
// Connect peerB to the server. PeerB already has the document so it sends
|
|
1312
|
+
// a "sync" message (not a "request"). The server denies access and —
|
|
1313
|
+
// without the fix — does NOT record peerB's interest.
|
|
1314
|
+
await connectRepos(peerB, syncServer)
|
|
1315
|
+
|
|
1316
|
+
// Sever the direct peerA <-> peerB connection so the only path between
|
|
1317
|
+
// them is through the sync server
|
|
1318
|
+
peerABConnection.disconnect()
|
|
1319
|
+
await pause(10)
|
|
1320
|
+
|
|
1321
|
+
// Now allow peerB on the server and notify it of the policy change
|
|
1322
|
+
peerBAllowed = true
|
|
1323
|
+
syncServer.shareConfigChanged()
|
|
1324
|
+
await pause(50) // allow reevaluateDocumentShare to run
|
|
1325
|
+
|
|
1326
|
+
// peerA makes a new change
|
|
1327
|
+
handle.change(d => {
|
|
1328
|
+
d.foo = "baz"
|
|
1329
|
+
})
|
|
1330
|
+
await pause(150) // allow time for the change to flow peerA → server → peerB
|
|
1331
|
+
|
|
1332
|
+
// With the fix: the server recorded peerB's sync message as expressing
|
|
1333
|
+
// interest, so reevaluateDocumentShare started syncing with peerB, and
|
|
1334
|
+
// peerA's new change flows through the server to peerB.
|
|
1335
|
+
assert.deepStrictEqual(
|
|
1336
|
+
peerBHandle!.doc(),
|
|
1337
|
+
{ foo: "baz" },
|
|
1338
|
+
"peerB should have received peerA's change via the sync server after the share policy changed"
|
|
1339
|
+
)
|
|
1340
|
+
})
|
|
1341
|
+
|
|
1250
1342
|
it("a previously unavailable document becomes available if the network adapter initially has no peers", async () => {
|
|
1251
1343
|
// It is possible for a network adapter to be ready without any peer
|
|
1252
1344
|
// being announced (e.g. the BroadcastChannelNetworkAdapter). In this
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { DummyNetworkAdapter } from "../../src/helpers/DummyNetworkAdapter.js"
|
|
2
2
|
import { Repo } from "../../src/Repo.js"
|
|
3
|
+
import { PeerId } from "../../src/types.js"
|
|
3
4
|
import pause from "./pause.js"
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
+
export type Connection = {
|
|
7
|
+
disconnect: () => void
|
|
8
|
+
reconnect: () => Promise<void>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default async function connectRepos(
|
|
12
|
+
left: Repo,
|
|
13
|
+
right: Repo
|
|
14
|
+
): Promise<Connection> {
|
|
6
15
|
const [leftToRight, rightToLeft] = DummyNetworkAdapter.createConnectedPair({
|
|
7
16
|
latency: 0,
|
|
8
17
|
})
|
|
@@ -15,4 +24,20 @@ export default async function connectRepos(left: Repo, right: Repo) {
|
|
|
15
24
|
right.networkSubsystem.whenReady(),
|
|
16
25
|
])
|
|
17
26
|
await pause(10)
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
disconnect: () => {
|
|
30
|
+
leftToRight.emit("peer-disconnected", { peerId: right.peerId as PeerId })
|
|
31
|
+
rightToLeft.emit("peer-disconnected", { peerId: left.peerId as PeerId })
|
|
32
|
+
},
|
|
33
|
+
reconnect: async () => {
|
|
34
|
+
leftToRight.peerCandidate(right.peerId)
|
|
35
|
+
rightToLeft.peerCandidate(left.peerId)
|
|
36
|
+
await Promise.all([
|
|
37
|
+
left.networkSubsystem.whenReady(),
|
|
38
|
+
right.networkSubsystem.whenReady(),
|
|
39
|
+
])
|
|
40
|
+
await pause(10)
|
|
41
|
+
},
|
|
42
|
+
}
|
|
18
43
|
}
|
package/dist/helpers/array.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/helpers/array.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAEnC"}
|
package/dist/helpers/array.js
DELETED
package/src/helpers/array.ts
DELETED