@fluid-internal/presence-runtime 2.118.0 → 3.0.0
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/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runtime/extension/containerPresence.d.ts +1 -1
- package/lib/runtime/extension/containerPresence.d.ts.map +1 -1
- package/lib/runtime/extension/containerPresence.js +5 -3
- package/lib/runtime/extension/containerPresence.js.map +1 -1
- package/lib/runtime/presenceDatastoreManager.js +71 -37
- package/lib/runtime/presenceDatastoreManager.js.map +1 -1
- package/lib/runtime/presenceManager.js +13 -8
- package/lib/runtime/presenceManager.js.map +1 -1
- package/lib/runtime/systemWorkspace.js +20 -13
- package/lib/runtime/systemWorkspace.js.map +1 -1
- package/lib/states/latestMapValueManager.d.ts.map +1 -1
- package/lib/states/latestMapValueManager.js +12 -1
- package/lib/states/latestMapValueManager.js.map +1 -1
- package/lib/states/latestValueManager.js +6 -1
- package/lib/states/latestValueManager.js.map +1 -1
- package/lib/states/notificationsManager.js +34 -32
- package/lib/states/notificationsManager.js.map +1 -1
- package/lib/utils/broadcastControls.js +4 -0
- package/lib/utils/broadcastControls.js.map +1 -1
- package/lib/utils/internalUtils.d.ts.map +1 -1
- package/lib/utils/timerManager.js +4 -5
- package/lib/utils/timerManager.js.map +1 -1
- package/lib/workspace/presenceStates.js +6 -0
- package/lib/workspace/presenceStates.js.map +1 -1
- package/package.json +28 -60
- package/dist/package.json +0 -4
- package/dist/packageVersion.d.ts +0 -9
- package/dist/packageVersion.d.ts.map +0 -1
- package/dist/packageVersion.js +0 -12
- package/dist/packageVersion.js.map +0 -1
- package/dist/runtime/extension/containerPresence.d.ts +0 -53
- package/dist/runtime/extension/containerPresence.d.ts.map +0 -1
- package/dist/runtime/extension/containerPresence.js +0 -90
- package/dist/runtime/extension/containerPresence.js.map +0 -1
- package/dist/runtime/extension/index.d.ts +0 -6
- package/dist/runtime/extension/index.d.ts.map +0 -1
- package/dist/runtime/extension/index.js +0 -11
- package/dist/runtime/extension/index.js.map +0 -1
- package/dist/runtime/presenceDatastoreManager.d.ts +0 -146
- package/dist/runtime/presenceDatastoreManager.d.ts.map +0 -1
- package/dist/runtime/presenceDatastoreManager.js +0 -666
- package/dist/runtime/presenceDatastoreManager.js.map +0 -1
- package/dist/runtime/presenceManager.d.ts +0 -16
- package/dist/runtime/presenceManager.d.ts.map +0 -1
- package/dist/runtime/presenceManager.js +0 -140
- package/dist/runtime/presenceManager.js.map +0 -1
- package/dist/runtime/protocol.d.ts +0 -126
- package/dist/runtime/protocol.d.ts.map +0 -1
- package/dist/runtime/protocol.js +0 -20
- package/dist/runtime/protocol.js.map +0 -1
- package/dist/runtime/runtimeTypes.d.ts +0 -26
- package/dist/runtime/runtimeTypes.d.ts.map +0 -1
- package/dist/runtime/runtimeTypes.js +0 -7
- package/dist/runtime/runtimeTypes.js.map +0 -1
- package/dist/runtime/systemWorkspace.d.ts +0 -39
- package/dist/runtime/systemWorkspace.d.ts.map +0 -1
- package/dist/runtime/systemWorkspace.js +0 -264
- package/dist/runtime/systemWorkspace.js.map +0 -1
- package/dist/runtime/test.d.ts +0 -12
- package/dist/runtime/test.d.ts.map +0 -1
- package/dist/runtime/test.js +0 -15
- package/dist/runtime/test.js.map +0 -1
- package/dist/states/index.d.ts +0 -7
- package/dist/states/index.d.ts.map +0 -1
- package/dist/states/index.js +0 -12
- package/dist/states/index.js.map +0 -1
- package/dist/states/latestMapValueManager.d.ts +0 -10
- package/dist/states/latestMapValueManager.d.ts.map +0 -1
- package/dist/states/latestMapValueManager.js +0 -248
- package/dist/states/latestMapValueManager.js.map +0 -1
- package/dist/states/latestValueManager.d.ts +0 -10
- package/dist/states/latestValueManager.d.ts.map +0 -1
- package/dist/states/latestValueManager.js +0 -115
- package/dist/states/latestValueManager.js.map +0 -1
- package/dist/states/notificationsManager.d.ts +0 -26
- package/dist/states/notificationsManager.d.ts.map +0 -1
- package/dist/states/notificationsManager.js +0 -97
- package/dist/states/notificationsManager.js.map +0 -1
- package/dist/states/presence.d.ts +0 -11
- package/dist/states/presence.d.ts.map +0 -1
- package/dist/states/presence.js +0 -7
- package/dist/states/presence.js.map +0 -1
- package/dist/states/stateFactory.d.ts +0 -23
- package/dist/states/stateFactory.d.ts.map +0 -1
- package/dist/states/stateFactory.js +0 -27
- package/dist/states/stateFactory.js.map +0 -1
- package/dist/states/validatedGetter.d.ts +0 -18
- package/dist/states/validatedGetter.d.ts.map +0 -1
- package/dist/states/validatedGetter.js +0 -43
- package/dist/states/validatedGetter.js.map +0 -1
- package/dist/utils/broadcastControls.d.ts +0 -37
- package/dist/utils/broadcastControls.d.ts.map +0 -1
- package/dist/utils/broadcastControls.js +0 -60
- package/dist/utils/broadcastControls.js.map +0 -1
- package/dist/utils/index.d.ts +0 -9
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -26
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/internalUtils.d.ts +0 -125
- package/dist/utils/internalUtils.d.ts.map +0 -1
- package/dist/utils/internalUtils.js +0 -99
- package/dist/utils/internalUtils.js.map +0 -1
- package/dist/utils/timerManager.d.ts +0 -37
- package/dist/utils/timerManager.d.ts.map +0 -1
- package/dist/utils/timerManager.js +0 -65
- package/dist/utils/timerManager.js.map +0 -1
- package/dist/utils/valueManager.d.ts +0 -14
- package/dist/utils/valueManager.d.ts.map +0 -1
- package/dist/utils/valueManager.js +0 -22
- package/dist/utils/valueManager.js.map +0 -1
- package/dist/workspace/index.d.ts +0 -7
- package/dist/workspace/index.d.ts.map +0 -1
- package/dist/workspace/index.js +0 -14
- package/dist/workspace/index.js.map +0 -1
- package/dist/workspace/presenceStates.d.ts +0 -61
- package/dist/workspace/presenceStates.d.ts.map +0 -1
- package/dist/workspace/presenceStates.js +0 -235
- package/dist/workspace/presenceStates.js.map +0 -1
- package/dist/workspace/stateDatastore.d.ts +0 -17
- package/dist/workspace/stateDatastore.d.ts.map +0 -1
- package/dist/workspace/stateDatastore.js +0 -24
- package/dist/workspace/stateDatastore.js.map +0 -1
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createSystemWorkspace = void 0;
|
|
8
|
-
const presence_definitions_1 = require("@fluid-internal/presence-definitions");
|
|
9
|
-
const internal_1 = require("@fluidframework/core-utils/internal");
|
|
10
|
-
const utils_1 = require("@fluid-internal/presence-runtime/utils");
|
|
11
|
-
class SessionClient {
|
|
12
|
-
constructor(attendeeId,
|
|
13
|
-
/**
|
|
14
|
-
* Order is used to track the most recent client connection
|
|
15
|
-
* during a session.
|
|
16
|
-
*/
|
|
17
|
-
order = 0, connectionId = undefined) {
|
|
18
|
-
this.attendeeId = attendeeId;
|
|
19
|
-
this.order = order;
|
|
20
|
-
this.connectionId = connectionId;
|
|
21
|
-
this.connectionStatus = presence_definitions_1.AttendeeStatus.Disconnected;
|
|
22
|
-
}
|
|
23
|
-
getConnectionId() {
|
|
24
|
-
if (this.connectionId === undefined) {
|
|
25
|
-
throw new Error("Client has never been connected");
|
|
26
|
-
}
|
|
27
|
-
return this.connectionId;
|
|
28
|
-
}
|
|
29
|
-
getConnectionStatus() {
|
|
30
|
-
return this.connectionStatus;
|
|
31
|
-
}
|
|
32
|
-
setConnected() {
|
|
33
|
-
this.connectionStatus = presence_definitions_1.AttendeeStatus.Connected;
|
|
34
|
-
}
|
|
35
|
-
setDisconnected() {
|
|
36
|
-
this.connectionStatus = presence_definitions_1.AttendeeStatus.Disconnected;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
class SystemWorkspaceImpl {
|
|
40
|
-
constructor(attendeeId, datastore, events, audience) {
|
|
41
|
-
this.datastore = datastore;
|
|
42
|
-
this.events = events;
|
|
43
|
-
this.audience = audience;
|
|
44
|
-
/**
|
|
45
|
-
* `attendees` is this client's understanding of the attendees in the
|
|
46
|
-
* session. The map covers entries for both session ids and connection
|
|
47
|
-
* ids, which are never expected to collide, but if they did for same
|
|
48
|
-
* client that would be fine.
|
|
49
|
-
* An entry is for session ID if the value's `attendeeId` matches the key.
|
|
50
|
-
*/
|
|
51
|
-
this.attendees = new Map();
|
|
52
|
-
// When local client disconnects, we lose the connectivity status updates for remote attendees in the session.
|
|
53
|
-
// Upon reconnect, we mark all other attendees connections as stale and update their status to disconnected after 30 seconds of inactivity.
|
|
54
|
-
this.staleConnectionClients = new Set();
|
|
55
|
-
this.staleConnectionTimer = new utils_1.TimerManager();
|
|
56
|
-
this.selfAttendee = new SessionClient(attendeeId);
|
|
57
|
-
this.attendees.set(attendeeId, this.selfAttendee);
|
|
58
|
-
}
|
|
59
|
-
ensureContent(_content) {
|
|
60
|
-
throw new Error("Method not implemented.");
|
|
61
|
-
}
|
|
62
|
-
processUpdate(_received, _timeModifier,
|
|
63
|
-
/**
|
|
64
|
-
* Remote datastore typed to match {@link PresenceStatesInternal.processUpdate}'s
|
|
65
|
-
* `ValueUpdateRecord` type that uses {@link InternalTypes.ValueRequiredState}
|
|
66
|
-
* and expects an Opaque JSON type. (We get away with a non-`unknown` value type
|
|
67
|
-
* per TypeScript's method parameter bivariance.) Proper type would be
|
|
68
|
-
* {@link ConnectionValueState} directly.
|
|
69
|
-
* {@link ClientConnectionId} use for index is also a deviation, but conveniently
|
|
70
|
-
* the accurate {@link AttendeeId} type is just a branded string, and
|
|
71
|
-
* {@link ClientConnectionId} is just `string`.
|
|
72
|
-
*/
|
|
73
|
-
remoteDatastore, senderConnectionId) {
|
|
74
|
-
const audienceMembers = this.audience.getMembers();
|
|
75
|
-
const postUpdateActions = [];
|
|
76
|
-
for (const [clientConnectionId, value] of Object.entries((0, utils_1.revealOpaqueJson)(remoteDatastore.clientToSessionId))) {
|
|
77
|
-
const attendeeId = value.value;
|
|
78
|
-
const { attendee, isJoining } = this.ensureAttendee({
|
|
79
|
-
attendeeId,
|
|
80
|
-
clientConnectionId,
|
|
81
|
-
order: value.rev,
|
|
82
|
-
isSender: senderConnectionId === clientConnectionId,
|
|
83
|
-
isInAudience: audienceMembers.has(clientConnectionId),
|
|
84
|
-
});
|
|
85
|
-
// If the attendee is joining the session, add them to the list of joining attendees to be announced later.
|
|
86
|
-
if (isJoining) {
|
|
87
|
-
postUpdateActions.push(() => this.events.emit("attendeeConnected", attendee));
|
|
88
|
-
}
|
|
89
|
-
const knownSessionId = this.datastore.clientToSessionId[clientConnectionId];
|
|
90
|
-
if (knownSessionId === undefined) {
|
|
91
|
-
this.datastore.clientToSessionId[clientConnectionId] = value;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
(0, internal_1.assert)(knownSessionId.value === value.value, 0xa5a /* Mismatched SessionId */);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return postUpdateActions;
|
|
98
|
-
}
|
|
99
|
-
onConnectionAdded(clientConnectionId, audienceOutOfDate) {
|
|
100
|
-
(0, internal_1.assert)(this.selfAttendee.getConnectionStatus() === presence_definitions_1.AttendeeStatus.Disconnected, 0xaad /* Local client should be 'Disconnected' before adding new connection. */);
|
|
101
|
-
const selfInAudience = this.audience.getMember(clientConnectionId) !== undefined;
|
|
102
|
-
(0, internal_1.assert)(selfInAudience || audienceOutOfDate, 0xcc0 /* Local client must be in audience for presence to handle added connection. */);
|
|
103
|
-
if (!(clientConnectionId in this.datastore.clientToSessionId)) {
|
|
104
|
-
this.datastore.clientToSessionId[clientConnectionId] = {
|
|
105
|
-
rev: this.selfAttendee.order++,
|
|
106
|
-
timestamp: Date.now(),
|
|
107
|
-
value: this.selfAttendee.attendeeId,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// Update the self attendee connection information, but not connection
|
|
111
|
-
// status yet. Connection status is updated once self is in audience -
|
|
112
|
-
// see later. It is only once our connection is known to audience that
|
|
113
|
-
// audience can be used to track other attendees' connection statuses
|
|
114
|
-
// and we seek to present a consistent view locally.
|
|
115
|
-
this.selfAttendee.connectionId = clientConnectionId;
|
|
116
|
-
this.attendees.set(clientConnectionId, this.selfAttendee);
|
|
117
|
-
if (selfInAudience) {
|
|
118
|
-
// Mark 'Connected' remote attendees connections as stale
|
|
119
|
-
// Performance note: This will visit attendees multiple times as the
|
|
120
|
-
// attendee map has attendeeIds and connectionIds entries that point to
|
|
121
|
-
// the same attendee. But the getConnectionStatus check is cheap and
|
|
122
|
-
// staleConnectionClients.add will handle duplicates.
|
|
123
|
-
this.staleConnectionClients.clear();
|
|
124
|
-
for (const staleConnectionClient of this.attendees.values()) {
|
|
125
|
-
if (staleConnectionClient.getConnectionStatus() === presence_definitions_1.AttendeeStatus.Connected) {
|
|
126
|
-
this.staleConnectionClients.add(staleConnectionClient);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
this.staleConnectionTimer.setTimeout(this.resolveStaleConnections.bind(this), 30_000);
|
|
130
|
-
this.selfAttendee.setConnected();
|
|
131
|
-
this.events.emit("attendeeConnected", this.selfAttendee);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
resolveStaleConnections() {
|
|
135
|
-
const consideredDisconnected = [];
|
|
136
|
-
for (const client of this.staleConnectionClients) {
|
|
137
|
-
// Confirm that audience no longer has connection. It is possible
|
|
138
|
-
// but unlikely that no one mentioned the attendee in this period
|
|
139
|
-
// and that they were never disconnected.
|
|
140
|
-
if (this.audience.getMember(client.getConnectionId()) === undefined) {
|
|
141
|
-
consideredDisconnected.push(client);
|
|
142
|
-
client.setDisconnected();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
for (const client of consideredDisconnected) {
|
|
146
|
-
this.events.emit("attendeeDisconnected", client);
|
|
147
|
-
}
|
|
148
|
-
this.staleConnectionClients.clear();
|
|
149
|
-
}
|
|
150
|
-
removeClientConnectionId(clientConnectionId) {
|
|
151
|
-
const attendee = this.attendees.get(clientConnectionId);
|
|
152
|
-
if (!attendee) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
// If the local connection is being removed, clear the stale connection timer
|
|
156
|
-
if (attendee === this.selfAttendee) {
|
|
157
|
-
this.staleConnectionTimer.clearTimeout();
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
// When self is not connected, audience may go through a refresh that
|
|
161
|
-
// removes members and adds them back. Defer any removals until self
|
|
162
|
-
// is connected implying audience is stable.
|
|
163
|
-
if (this.selfAttendee.getConnectionStatus() !== presence_definitions_1.AttendeeStatus.Connected) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,
|
|
168
|
-
// therefore we should not change the attendee connection status or emit a disconnect event.
|
|
169
|
-
const attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;
|
|
170
|
-
const connected = attendee.getConnectionStatus() === presence_definitions_1.AttendeeStatus.Connected;
|
|
171
|
-
if (!attendeeReconnected && connected) {
|
|
172
|
-
attendee.setDisconnected();
|
|
173
|
-
this.events.emit("attendeeDisconnected", attendee);
|
|
174
|
-
this.staleConnectionClients.delete(attendee);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
getAttendees() {
|
|
178
|
-
return new Set(this.attendees.values());
|
|
179
|
-
}
|
|
180
|
-
getAttendee(clientId) {
|
|
181
|
-
const attendee = this.attendees.get(clientId);
|
|
182
|
-
if (attendee) {
|
|
183
|
-
return attendee;
|
|
184
|
-
}
|
|
185
|
-
// TODO: Restore option to add attendee on demand to handle internal
|
|
186
|
-
// lookup cases that must come from internal data.
|
|
187
|
-
// There aren't any resiliency mechanisms in place to handle a missed
|
|
188
|
-
// ClientJoin right now.
|
|
189
|
-
throw new Error("Attendee not found");
|
|
190
|
-
}
|
|
191
|
-
getMyself() {
|
|
192
|
-
return this.selfAttendee;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Make sure the given client session and connection ID pair are represented
|
|
196
|
-
* in the attendee map. If not present, SessionClient is created and added
|
|
197
|
-
* to map. If present, make sure the current connection ID is updated.
|
|
198
|
-
*/
|
|
199
|
-
ensureAttendee({ attendeeId, clientConnectionId, order, isSender, isInAudience, }) {
|
|
200
|
-
let attendee = this.attendees.get(attendeeId);
|
|
201
|
-
let isConnected = false;
|
|
202
|
-
let isJoining = false;
|
|
203
|
-
if (attendee === undefined) {
|
|
204
|
-
// New attendee. Create SessionClient and add session ID based
|
|
205
|
-
// entry to map.
|
|
206
|
-
attendee = new SessionClient(attendeeId, order, clientConnectionId);
|
|
207
|
-
this.attendees.set(attendeeId, attendee);
|
|
208
|
-
// If the attendee update is from the sending remote client itself
|
|
209
|
-
// OR if the attendee is present in audience,
|
|
210
|
-
// then the attendee is considered connected. (Otherwise, leave
|
|
211
|
-
// state as disconnected - default.)
|
|
212
|
-
if (isSender || isInAudience) {
|
|
213
|
-
isConnected = true;
|
|
214
|
-
attendee.setConnected();
|
|
215
|
-
isJoining = true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
// Known attendee is considered connected if
|
|
220
|
-
isConnected =
|
|
221
|
-
// this information is at least up to date with current knowledge
|
|
222
|
-
order >= attendee.order &&
|
|
223
|
-
// AND in the audience OR
|
|
224
|
-
(isInAudience ||
|
|
225
|
-
// not in audience, but client is the sender and has newer
|
|
226
|
-
// info. (Assume that audience is out of date and attendee
|
|
227
|
-
// is joining.)
|
|
228
|
-
(isSender && order > attendee.order));
|
|
229
|
-
if (order > attendee.order) {
|
|
230
|
-
// The given association is newer than the one we have.
|
|
231
|
-
// Update the order and current connection ID.
|
|
232
|
-
attendee.order = order;
|
|
233
|
-
attendee.connectionId = clientConnectionId;
|
|
234
|
-
}
|
|
235
|
-
// Known attendee is joining the session if they are currently disconnected.
|
|
236
|
-
if (isConnected && attendee.getConnectionStatus() === presence_definitions_1.AttendeeStatus.Disconnected) {
|
|
237
|
-
attendee.setConnected();
|
|
238
|
-
isJoining = true;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (isConnected) {
|
|
242
|
-
// If the attendee is connected, remove them from the stale connection set
|
|
243
|
-
this.staleConnectionClients.delete(attendee);
|
|
244
|
-
}
|
|
245
|
-
// Always update entry for the connection ID. (Okay if already set.)
|
|
246
|
-
this.attendees.set(clientConnectionId, attendee);
|
|
247
|
-
return { attendee, isJoining };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Instantiates the system workspace.
|
|
252
|
-
*/
|
|
253
|
-
function createSystemWorkspace(attendeeId, datastore, events, audience) {
|
|
254
|
-
const workspace = new SystemWorkspaceImpl(attendeeId, datastore, events, audience);
|
|
255
|
-
return {
|
|
256
|
-
workspace,
|
|
257
|
-
statesEntry: {
|
|
258
|
-
internal: workspace,
|
|
259
|
-
public: undefined,
|
|
260
|
-
},
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
exports.createSystemWorkspace = createSystemWorkspace;
|
|
264
|
-
//# sourceMappingURL=systemWorkspace.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"systemWorkspace.js","sourceRoot":"","sources":["../../src/runtime/systemWorkspace.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tAttendee,\n\tAttendeeId,\n\tAttendeesEvents,\n\tClientConnectionId,\n\tPresence,\n\tStatesWorkspaceSchema,\n} from \"@fluid-internal/presence-definitions\";\nimport { AttendeeStatus } from \"@fluid-internal/presence-definitions\";\nimport type {\n\tAnyWorkspace,\n\tInternalTypes,\n\tPostUpdateAction,\n} from \"@fluid-internal/presence-definitions/internal\";\nimport type { PresenceStatesInternal } from \"@fluid-internal/presence-definitions/internal/workspace-runtime\";\nimport type { IAudience } from \"@fluidframework/container-definitions\";\nimport type { IEmitter, Listenable } from \"@fluidframework/core-interfaces/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\n\nimport { revealOpaqueJson, TimerManager } from \"@fluid-internal/presence-runtime/utils\";\n\nimport type { SystemWorkspaceDatastore } from \"./protocol.js\";\n\nclass SessionClient implements Attendee {\n\tprivate connectionStatus: AttendeeStatus = AttendeeStatus.Disconnected;\n\n\tpublic constructor(\n\t\tpublic readonly attendeeId: AttendeeId,\n\t\t/**\n\t\t * Order is used to track the most recent client connection\n\t\t * during a session.\n\t\t */\n\t\tpublic order: number = 0,\n\t\tpublic connectionId: ClientConnectionId | undefined = undefined,\n\t) {}\n\n\tpublic getConnectionId(): ClientConnectionId {\n\t\tif (this.connectionId === undefined) {\n\t\t\tthrow new Error(\"Client has never been connected\");\n\t\t}\n\t\treturn this.connectionId;\n\t}\n\n\tpublic getConnectionStatus(): AttendeeStatus {\n\t\treturn this.connectionStatus;\n\t}\n\n\tpublic setConnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Connected;\n\t}\n\n\tpublic setDisconnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Disconnected;\n\t}\n}\n\n/**\n * Internal workspace that manages metadata for session attendees.\n */\nexport interface SystemWorkspace\n\t// Portion of Presence that is handled by SystemWorkspace along with\n\t// responsibility for emitting \"attendeeConnected\" events.\n\textends Exclude<Presence[\"attendees\"], never> {\n\t/**\n\t * Must be called when the current client acquires a new connection.\n\t *\n\t * @param clientConnectionId - The new client connection ID.\n\t * @param audienceOutOfDate - When true, audience cannot be used as authoritative.\n\t */\n\tonConnectionAdded(clientConnectionId: ClientConnectionId, audienceOutOfDate: boolean): void;\n\n\t/**\n\t * Removes the client connection ID from the system workspace.\n\t *\n\t * @param clientConnectionId - The client connection ID to remove.\n\t */\n\tremoveClientConnectionId(clientConnectionId: ClientConnectionId): void;\n}\n\nclass SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {\n\tprivate readonly selfAttendee: SessionClient;\n\t/**\n\t * `attendees` is this client's understanding of the attendees in the\n\t * session. The map covers entries for both session ids and connection\n\t * ids, which are never expected to collide, but if they did for same\n\t * client that would be fine.\n\t * An entry is for session ID if the value's `attendeeId` matches the key.\n\t */\n\tprivate readonly attendees = new Map<ClientConnectionId | AttendeeId, SessionClient>();\n\n\t// When local client disconnects, we lose the connectivity status updates for remote attendees in the session.\n\t// Upon reconnect, we mark all other attendees connections as stale and update their status to disconnected after 30 seconds of inactivity.\n\tprivate readonly staleConnectionClients = new Set<SessionClient>();\n\n\tprivate readonly staleConnectionTimer = new TimerManager();\n\n\tpublic constructor(\n\t\tattendeeId: AttendeeId,\n\t\tprivate readonly datastore: SystemWorkspaceDatastore,\n\t\tpublic readonly events: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\t\tprivate readonly audience: IAudience,\n\t) {\n\t\tthis.selfAttendee = new SessionClient(attendeeId);\n\t\tthis.attendees.set(attendeeId, this.selfAttendee);\n\t}\n\n\tpublic ensureContent<TSchemaAdditional extends StatesWorkspaceSchema>(\n\t\t_content: TSchemaAdditional,\n\t): never {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tpublic processUpdate(\n\t\t_received: number,\n\t\t_timeModifier: number,\n\t\t/**\n\t\t * Remote datastore typed to match {@link PresenceStatesInternal.processUpdate}'s\n\t\t * `ValueUpdateRecord` type that uses {@link InternalTypes.ValueRequiredState}\n\t\t * and expects an Opaque JSON type. (We get away with a non-`unknown` value type\n\t\t * per TypeScript's method parameter bivariance.) Proper type would be\n\t\t * {@link ConnectionValueState} directly.\n\t\t * {@link ClientConnectionId} use for index is also a deviation, but conveniently\n\t\t * the accurate {@link AttendeeId} type is just a branded string, and\n\t\t * {@link ClientConnectionId} is just `string`.\n\t\t */\n\t\tremoteDatastore: {\n\t\t\tclientToSessionId: {\n\t\t\t\t[ConnectionId: ClientConnectionId]: InternalTypes.ValueRequiredState<AttendeeId>;\n\t\t\t};\n\t\t},\n\t\tsenderConnectionId: ClientConnectionId,\n\t): PostUpdateAction[] {\n\t\tconst audienceMembers = this.audience.getMembers();\n\t\tconst postUpdateActions: PostUpdateAction[] = [];\n\t\tfor (const [clientConnectionId, value] of Object.entries(\n\t\t\trevealOpaqueJson(remoteDatastore.clientToSessionId),\n\t\t)) {\n\t\t\tconst attendeeId = value.value;\n\t\t\tconst { attendee, isJoining } = this.ensureAttendee({\n\t\t\t\tattendeeId,\n\t\t\t\tclientConnectionId,\n\t\t\t\torder: value.rev,\n\t\t\t\tisSender: senderConnectionId === clientConnectionId,\n\t\t\t\tisInAudience: audienceMembers.has(clientConnectionId),\n\t\t\t});\n\t\t\t// If the attendee is joining the session, add them to the list of joining attendees to be announced later.\n\t\t\tif (isJoining) {\n\t\t\t\tpostUpdateActions.push(() => this.events.emit(\"attendeeConnected\", attendee));\n\t\t\t}\n\n\t\t\tconst knownSessionId = this.datastore.clientToSessionId[clientConnectionId];\n\t\t\tif (knownSessionId === undefined) {\n\t\t\t\tthis.datastore.clientToSessionId[clientConnectionId] = value;\n\t\t\t} else {\n\t\t\t\tassert(knownSessionId.value === value.value, 0xa5a /* Mismatched SessionId */);\n\t\t\t}\n\t\t}\n\n\t\treturn postUpdateActions;\n\t}\n\n\tpublic onConnectionAdded(\n\t\tclientConnectionId: ClientConnectionId,\n\t\taudienceOutOfDate: boolean,\n\t): void {\n\t\tassert(\n\t\t\tthis.selfAttendee.getConnectionStatus() === AttendeeStatus.Disconnected,\n\t\t\t0xaad /* Local client should be 'Disconnected' before adding new connection. */,\n\t\t);\n\n\t\tconst selfInAudience = this.audience.getMember(clientConnectionId) !== undefined;\n\t\tassert(\n\t\t\tselfInAudience || audienceOutOfDate,\n\t\t\t0xcc0 /* Local client must be in audience for presence to handle added connection. */,\n\t\t);\n\n\t\tif (!(clientConnectionId in this.datastore.clientToSessionId)) {\n\t\t\tthis.datastore.clientToSessionId[clientConnectionId] = {\n\t\t\t\trev: this.selfAttendee.order++,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tvalue: this.selfAttendee.attendeeId,\n\t\t\t};\n\t\t}\n\n\t\t// Update the self attendee connection information, but not connection\n\t\t// status yet. Connection status is updated once self is in audience -\n\t\t// see later. It is only once our connection is known to audience that\n\t\t// audience can be used to track other attendees' connection statuses\n\t\t// and we seek to present a consistent view locally.\n\t\tthis.selfAttendee.connectionId = clientConnectionId;\n\t\tthis.attendees.set(clientConnectionId, this.selfAttendee);\n\n\t\tif (selfInAudience) {\n\t\t\t// Mark 'Connected' remote attendees connections as stale\n\t\t\t// Performance note: This will visit attendees multiple times as the\n\t\t\t// attendee map has attendeeIds and connectionIds entries that point to\n\t\t\t// the same attendee. But the getConnectionStatus check is cheap and\n\t\t\t// staleConnectionClients.add will handle duplicates.\n\t\t\tthis.staleConnectionClients.clear();\n\t\t\tfor (const staleConnectionClient of this.attendees.values()) {\n\t\t\t\tif (staleConnectionClient.getConnectionStatus() === AttendeeStatus.Connected) {\n\t\t\t\t\tthis.staleConnectionClients.add(staleConnectionClient);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.staleConnectionTimer.setTimeout(this.resolveStaleConnections.bind(this), 30_000);\n\n\t\t\tthis.selfAttendee.setConnected();\n\t\t\tthis.events.emit(\"attendeeConnected\", this.selfAttendee);\n\t\t}\n\t}\n\n\tprivate resolveStaleConnections(): void {\n\t\tconst consideredDisconnected = [];\n\t\tfor (const client of this.staleConnectionClients) {\n\t\t\t// Confirm that audience no longer has connection. It is possible\n\t\t\t// but unlikely that no one mentioned the attendee in this period\n\t\t\t// and that they were never disconnected.\n\t\t\tif (this.audience.getMember(client.getConnectionId()) === undefined) {\n\t\t\t\tconsideredDisconnected.push(client);\n\t\t\t\tclient.setDisconnected();\n\t\t\t}\n\t\t}\n\t\tfor (const client of consideredDisconnected) {\n\t\t\tthis.events.emit(\"attendeeDisconnected\", client);\n\t\t}\n\t\tthis.staleConnectionClients.clear();\n\t}\n\n\tpublic removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tconst attendee = this.attendees.get(clientConnectionId);\n\t\tif (!attendee) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If the local connection is being removed, clear the stale connection timer\n\t\tif (attendee === this.selfAttendee) {\n\t\t\tthis.staleConnectionTimer.clearTimeout();\n\t\t} else {\n\t\t\t// When self is not connected, audience may go through a refresh that\n\t\t\t// removes members and adds them back. Defer any removals until self\n\t\t\t// is connected implying audience is stable.\n\t\t\tif (this.selfAttendee.getConnectionStatus() !== AttendeeStatus.Connected) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,\n\t\t// therefore we should not change the attendee connection status or emit a disconnect event.\n\t\tconst attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;\n\t\tconst connected = attendee.getConnectionStatus() === AttendeeStatus.Connected;\n\t\tif (!attendeeReconnected && connected) {\n\t\t\tattendee.setDisconnected();\n\t\t\tthis.events.emit(\"attendeeDisconnected\", attendee);\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\t}\n\n\tpublic getAttendees(): ReadonlySet<Attendee> {\n\t\treturn new Set(this.attendees.values());\n\t}\n\n\tpublic getAttendee(clientId: ClientConnectionId | AttendeeId): Attendee {\n\t\tconst attendee = this.attendees.get(clientId);\n\t\tif (attendee) {\n\t\t\treturn attendee;\n\t\t}\n\n\t\t// TODO: Restore option to add attendee on demand to handle internal\n\t\t// lookup cases that must come from internal data.\n\t\t// There aren't any resiliency mechanisms in place to handle a missed\n\t\t// ClientJoin right now.\n\t\tthrow new Error(\"Attendee not found\");\n\t}\n\n\tpublic getMyself(): Attendee {\n\t\treturn this.selfAttendee;\n\t}\n\n\t/**\n\t * Make sure the given client session and connection ID pair are represented\n\t * in the attendee map. If not present, SessionClient is created and added\n\t * to map. If present, make sure the current connection ID is updated.\n\t */\n\tprivate ensureAttendee({\n\t\tattendeeId,\n\t\tclientConnectionId,\n\t\torder,\n\t\tisSender,\n\t\tisInAudience,\n\t}: {\n\t\tattendeeId: AttendeeId;\n\t\tclientConnectionId: ClientConnectionId;\n\t\torder: number;\n\t\tisSender: boolean;\n\t\tisInAudience: boolean;\n\t}): { attendee: SessionClient; isJoining: boolean } {\n\t\tlet attendee = this.attendees.get(attendeeId);\n\t\tlet isConnected = false;\n\t\tlet isJoining = false;\n\n\t\tif (attendee === undefined) {\n\t\t\t// New attendee. Create SessionClient and add session ID based\n\t\t\t// entry to map.\n\t\t\tattendee = new SessionClient(attendeeId, order, clientConnectionId);\n\t\t\tthis.attendees.set(attendeeId, attendee);\n\t\t\t// If the attendee update is from the sending remote client itself\n\t\t\t// OR if the attendee is present in audience,\n\t\t\t// then the attendee is considered connected. (Otherwise, leave\n\t\t\t// state as disconnected - default.)\n\t\t\tif (isSender || isInAudience) {\n\t\t\t\tisConnected = true;\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t} else {\n\t\t\t// Known attendee is considered connected if\n\t\t\tisConnected =\n\t\t\t\t// this information is at least up to date with current knowledge\n\t\t\t\torder >= attendee.order &&\n\t\t\t\t// AND in the audience OR\n\t\t\t\t(isInAudience ||\n\t\t\t\t\t// not in audience, but client is the sender and has newer\n\t\t\t\t\t// info. (Assume that audience is out of date and attendee\n\t\t\t\t\t// is joining.)\n\t\t\t\t\t(isSender && order > attendee.order));\n\n\t\t\tif (order > attendee.order) {\n\t\t\t\t// The given association is newer than the one we have.\n\t\t\t\t// Update the order and current connection ID.\n\t\t\t\tattendee.order = order;\n\t\t\t\tattendee.connectionId = clientConnectionId;\n\t\t\t}\n\n\t\t\t// Known attendee is joining the session if they are currently disconnected.\n\t\t\tif (isConnected && attendee.getConnectionStatus() === AttendeeStatus.Disconnected) {\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t}\n\n\t\tif (isConnected) {\n\t\t\t// If the attendee is connected, remove them from the stale connection set\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\n\t\t// Always update entry for the connection ID. (Okay if already set.)\n\t\tthis.attendees.set(clientConnectionId, attendee);\n\n\t\treturn { attendee, isJoining };\n\t}\n}\n\n/**\n * Instantiates the system workspace.\n */\nexport function createSystemWorkspace(\n\tattendeeId: AttendeeId,\n\tdatastore: SystemWorkspaceDatastore,\n\tevents: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\taudience: IAudience,\n): {\n\tworkspace: SystemWorkspace;\n\tstatesEntry: {\n\t\tinternal: PresenceStatesInternal;\n\t\tpublic: AnyWorkspace<StatesWorkspaceSchema>;\n\t};\n} {\n\tconst workspace = new SystemWorkspaceImpl(attendeeId, datastore, events, audience);\n\treturn {\n\t\tworkspace,\n\t\tstatesEntry: {\n\t\t\tinternal: workspace,\n\t\t\tpublic: undefined as unknown as AnyWorkspace<StatesWorkspaceSchema>,\n\t\t},\n\t};\n}\n"]}
|
package/dist/runtime/test.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Test-only exports.
|
|
7
|
-
*/
|
|
8
|
-
export { broadcastJoinResponseDelaysMs } from "./presenceDatastoreManager.js";
|
|
9
|
-
export { createPresenceManager } from "./presenceManager.js";
|
|
10
|
-
export type { GeneralDatastoreMessageContent, InboundClientJoinMessage, InboundDatastoreUpdateMessage, InternalWorkspaceAddress, OutboundClientJoinMessage, OutboundDatastoreUpdateMessage, } from "./protocol.js";
|
|
11
|
-
export type { IEphemeralRuntime } from "./runtimeTypes.js";
|
|
12
|
-
//# sourceMappingURL=test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/runtime/test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EACX,8BAA8B,EAC9B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,yBAAyB,EACzB,8BAA8B,GAC9B,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/runtime/test.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createPresenceManager = exports.broadcastJoinResponseDelaysMs = void 0;
|
|
8
|
-
/**
|
|
9
|
-
* Test-only exports.
|
|
10
|
-
*/
|
|
11
|
-
var presenceDatastoreManager_js_1 = require("./presenceDatastoreManager.js");
|
|
12
|
-
Object.defineProperty(exports, "broadcastJoinResponseDelaysMs", { enumerable: true, get: function () { return presenceDatastoreManager_js_1.broadcastJoinResponseDelaysMs; } });
|
|
13
|
-
var presenceManager_js_1 = require("./presenceManager.js");
|
|
14
|
-
Object.defineProperty(exports, "createPresenceManager", { enumerable: true, get: function () { return presenceManager_js_1.createPresenceManager; } });
|
|
15
|
-
//# sourceMappingURL=test.js.map
|
package/dist/runtime/test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/runtime/test.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;GAEG;AAEH,6EAA8E;AAArE,4IAAA,6BAA6B,OAAA;AAEtC,2DAA6D;AAApD,2HAAA,qBAAqB,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Test-only exports.\n */\n\nexport { broadcastJoinResponseDelaysMs } from \"./presenceDatastoreManager.js\";\n\nexport { createPresenceManager } from \"./presenceManager.js\";\n\nexport type {\n\tGeneralDatastoreMessageContent,\n\tInboundClientJoinMessage,\n\tInboundDatastoreUpdateMessage,\n\tInternalWorkspaceAddress,\n\tOutboundClientJoinMessage,\n\tOutboundDatastoreUpdateMessage,\n} from \"./protocol.js\";\n\nexport type { IEphemeralRuntime } from \"./runtimeTypes.js\";\n"]}
|
package/dist/states/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
export { Notifications } from "./notificationsManager.js";
|
|
6
|
-
export { StateFactory } from "./stateFactory.js";
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/states/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/states/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.StateFactory = exports.Notifications = void 0;
|
|
8
|
-
var notificationsManager_js_1 = require("./notificationsManager.js");
|
|
9
|
-
Object.defineProperty(exports, "Notifications", { enumerable: true, get: function () { return notificationsManager_js_1.Notifications; } });
|
|
10
|
-
var stateFactory_js_1 = require("./stateFactory.js");
|
|
11
|
-
Object.defineProperty(exports, "StateFactory", { enumerable: true, get: function () { return stateFactory_js_1.StateFactory; } });
|
|
12
|
-
//# sourceMappingURL=index.js.map
|
package/dist/states/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/states/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qEAA0D;AAAjD,wHAAA,aAAa,OAAA;AAEtB,qDAAiD;AAAxC,+GAAA,YAAY,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { Notifications } from \"./notificationsManager.js\";\n\nexport { StateFactory } from \"./stateFactory.js\";\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import type { LatestMapFactory } from "@fluid-internal/presence-definitions";
|
|
6
|
-
/**
|
|
7
|
-
* Factory for creating a {@link LatestMap} or {@link LatestMapRaw} State object.
|
|
8
|
-
*/
|
|
9
|
-
export declare const latestMap: LatestMapFactory;
|
|
10
|
-
//# sourceMappingURL=latestMapValueManager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"latestMapValueManager.d.ts","sourceRoot":"","sources":["../../src/states/latestMapValueManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAUX,gBAAgB,EAOhB,MAAM,sCAAsC,CAAC;AAsT9C;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,gBA6DvB,CAAC"}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.latestMap = void 0;
|
|
8
|
-
const client_utils_1 = require("@fluid-internal/client-utils");
|
|
9
|
-
const utils_1 = require("@fluid-internal/presence-runtime/utils");
|
|
10
|
-
const workspace_1 = require("@fluid-internal/presence-runtime/workspace");
|
|
11
|
-
const validatedGetter_js_1 = require("./validatedGetter.js");
|
|
12
|
-
class ValueMapImpl {
|
|
13
|
-
constructor(value, emitter, localUpdate) {
|
|
14
|
-
this.value = value;
|
|
15
|
-
this.emitter = emitter;
|
|
16
|
-
this.localUpdate = localUpdate;
|
|
17
|
-
// All initial items are expected to be defined.
|
|
18
|
-
// TODO assert all defined and/or update type.
|
|
19
|
-
this.countDefined = Object.keys(value.items).length;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Note: caller must ensure key exists in this.value.items.
|
|
23
|
-
*/
|
|
24
|
-
updateItem(key, value) {
|
|
25
|
-
this.value.rev += 1;
|
|
26
|
-
// Caller is required to ensure key exists.
|
|
27
|
-
const item = this.value.items[key];
|
|
28
|
-
item.rev += 1;
|
|
29
|
-
item.timestamp = Date.now();
|
|
30
|
-
if (value === undefined) {
|
|
31
|
-
delete item.value;
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
item.value = value;
|
|
35
|
-
}
|
|
36
|
-
const update = { rev: this.value.rev, items: { [key]: item } };
|
|
37
|
-
this.localUpdate(update);
|
|
38
|
-
}
|
|
39
|
-
clear() {
|
|
40
|
-
throw new Error("Method not implemented.");
|
|
41
|
-
}
|
|
42
|
-
delete(key) {
|
|
43
|
-
const { items } = this.value;
|
|
44
|
-
const hasKey = items[key]?.value !== undefined;
|
|
45
|
-
if (hasKey) {
|
|
46
|
-
this.countDefined -= 1;
|
|
47
|
-
this.updateItem(key, undefined);
|
|
48
|
-
this.emitter.emit("localItemRemoved", { key });
|
|
49
|
-
}
|
|
50
|
-
return hasKey;
|
|
51
|
-
}
|
|
52
|
-
forEach(callbackfn, thisArg) {
|
|
53
|
-
for (const [key, item] of (0, utils_1.objectEntries)(this.value.items)) {
|
|
54
|
-
if (item.value !== undefined) {
|
|
55
|
-
// TODO: AB#55932: try fixing typing of objectEntries to avoid this cast
|
|
56
|
-
callbackfn((0, utils_1.asDeeplyReadonlyDeserializedJson)(item.value), key, this);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
get(key) {
|
|
61
|
-
return (0, utils_1.asDeeplyReadonlyDeserializedJson)(this.value.items[key]?.value);
|
|
62
|
-
}
|
|
63
|
-
has(key) {
|
|
64
|
-
return this.value.items[key]?.value !== undefined;
|
|
65
|
-
}
|
|
66
|
-
set(key, inValue) {
|
|
67
|
-
const value = (0, utils_1.toOpaqueJson)(inValue);
|
|
68
|
-
if (!(key in this.value.items)) {
|
|
69
|
-
this.countDefined += 1;
|
|
70
|
-
this.value.items[key] = { rev: 0, timestamp: 0, value };
|
|
71
|
-
}
|
|
72
|
-
this.updateItem(key, value);
|
|
73
|
-
this.emitter.emit("localItemUpdated", { key, value: (0, utils_1.asDeeplyReadonly)(inValue) });
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
get size() {
|
|
77
|
-
return this.countDefined;
|
|
78
|
-
}
|
|
79
|
-
keys() {
|
|
80
|
-
const keys = [];
|
|
81
|
-
for (const [key, item] of (0, utils_1.objectEntries)(this.value.items)) {
|
|
82
|
-
if (item.value !== undefined) {
|
|
83
|
-
// TODO: AB#55932: try fixing typing of objectEntries to avoid this cast
|
|
84
|
-
keys.push(key);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return keys[Symbol.iterator]();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Simply returns true for all given string keys.
|
|
92
|
-
*/
|
|
93
|
-
function anyKeyIsValid(unvalidatedKey) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
class LatestMapValueManagerImpl {
|
|
97
|
-
constructor(key, datastore, value, controlSettings, validator, isValidKey) {
|
|
98
|
-
this.key = key;
|
|
99
|
-
this.datastore = datastore;
|
|
100
|
-
this.value = value;
|
|
101
|
-
this.validator = validator;
|
|
102
|
-
this.isValidKey = isValidKey;
|
|
103
|
-
this.events = (0, client_utils_1.createEmitter)();
|
|
104
|
-
this.controls = new utils_1.OptionalBroadcastControl(controlSettings);
|
|
105
|
-
this.local = new ValueMapImpl(value, this.events, (updates) => {
|
|
106
|
-
datastore.localUpdate(key, updates, {
|
|
107
|
-
allowableUpdateLatencyMs: this.controls.allowableUpdateLatencyMs,
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
get presence() {
|
|
112
|
-
return this.datastore.presence;
|
|
113
|
-
}
|
|
114
|
-
*getRemotes() {
|
|
115
|
-
const allKnownStates = this.datastore.knownValues(this.key);
|
|
116
|
-
for (const attendeeId of (0, utils_1.objectKeys)(allKnownStates.states)) {
|
|
117
|
-
if (attendeeId !== allKnownStates.self) {
|
|
118
|
-
const attendee = this.datastore.presence.attendees.getAttendee(attendeeId);
|
|
119
|
-
const items = this.getRemote(attendee);
|
|
120
|
-
yield { attendee, items };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
getStateAttendees() {
|
|
125
|
-
const allKnownStates = this.datastore.knownValues(this.key);
|
|
126
|
-
return (0, utils_1.objectKeys)(allKnownStates.states)
|
|
127
|
-
.filter((attendeeId) => attendeeId !== allKnownStates.self)
|
|
128
|
-
.map((attendeeId) => this.datastore.presence.attendees.getAttendee(attendeeId));
|
|
129
|
-
}
|
|
130
|
-
getRemote(attendee) {
|
|
131
|
-
const validator = this.validator;
|
|
132
|
-
const allKnownStates = this.datastore.knownValues(this.key);
|
|
133
|
-
const attendeeId = attendee.attendeeId;
|
|
134
|
-
const clientStateMap = allKnownStates.states[attendeeId];
|
|
135
|
-
if (clientStateMap === undefined) {
|
|
136
|
-
throw new Error("No entry for attendee");
|
|
137
|
-
}
|
|
138
|
-
const items = new Map();
|
|
139
|
-
for (const [key, item] of (0, utils_1.objectEntries)(clientStateMap.items)) {
|
|
140
|
-
if (this.isValidKey(key) && (0, utils_1.isValueRequiredState)(item)) {
|
|
141
|
-
items.set(key, {
|
|
142
|
-
value: (0, validatedGetter_js_1.createValidatedGetter)(item, validator),
|
|
143
|
-
metadata: { revision: item.rev, timestamp: item.timestamp },
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return items;
|
|
148
|
-
}
|
|
149
|
-
update(attendee, _received, value) {
|
|
150
|
-
const allKnownStates = this.datastore.knownValues(this.key);
|
|
151
|
-
const attendeeId = attendee.attendeeId;
|
|
152
|
-
const currentState = (allKnownStates.states[attendeeId] ??=
|
|
153
|
-
// New attendee - prepare new attendee state directory
|
|
154
|
-
{
|
|
155
|
-
rev: value.rev,
|
|
156
|
-
items: {},
|
|
157
|
-
});
|
|
158
|
-
// Accumulate individual update keys
|
|
159
|
-
const updatedKeyItemPairs = [];
|
|
160
|
-
for (const [key, item] of (0, utils_1.objectEntries)(value.items)) {
|
|
161
|
-
const currentItem = currentState.items[key];
|
|
162
|
-
if (currentItem === undefined || currentItem.rev < item.rev) {
|
|
163
|
-
updatedKeyItemPairs.push([key, item]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (updatedKeyItemPairs.length === 0) {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
// Store updates
|
|
170
|
-
if (value.rev > currentState.rev) {
|
|
171
|
-
currentState.rev = value.rev;
|
|
172
|
-
}
|
|
173
|
-
const allUpdates = {
|
|
174
|
-
attendee,
|
|
175
|
-
items: new Map(),
|
|
176
|
-
};
|
|
177
|
-
const postUpdateActions = [];
|
|
178
|
-
for (const [key, item] of updatedKeyItemPairs) {
|
|
179
|
-
const hadPriorValue = currentState.items[key]?.value;
|
|
180
|
-
currentState.items[key] = item;
|
|
181
|
-
// Prepare update events, but only for valid keys.
|
|
182
|
-
if (!this.isValidKey(key)) {
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
const metadata = {
|
|
186
|
-
revision: item.rev,
|
|
187
|
-
timestamp: item.timestamp,
|
|
188
|
-
};
|
|
189
|
-
if ((0, utils_1.isValueRequiredState)(item)) {
|
|
190
|
-
const updatedItem = {
|
|
191
|
-
attendee,
|
|
192
|
-
key,
|
|
193
|
-
value: (0, validatedGetter_js_1.createValidatedGetter)(item, this.validator),
|
|
194
|
-
metadata,
|
|
195
|
-
};
|
|
196
|
-
postUpdateActions.push(() => this.events.emit("remoteItemUpdated", updatedItem));
|
|
197
|
-
allUpdates.items.set(key, {
|
|
198
|
-
value: updatedItem.value,
|
|
199
|
-
metadata,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
else if (hadPriorValue !== undefined) {
|
|
203
|
-
postUpdateActions.push(() => this.events.emit("remoteItemRemoved", {
|
|
204
|
-
attendee,
|
|
205
|
-
key,
|
|
206
|
-
metadata,
|
|
207
|
-
}));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
this.datastore.update(this.key, attendeeId, currentState);
|
|
211
|
-
// Only emit remoteUpdated if there are any individual updates, which
|
|
212
|
-
// accounts for the case where all updates were for invalid keys.
|
|
213
|
-
if (postUpdateActions.length > 0) {
|
|
214
|
-
postUpdateActions.push(() => this.events.emit("remoteUpdated", allUpdates));
|
|
215
|
-
}
|
|
216
|
-
return postUpdateActions;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Factory for creating a {@link LatestMap} or {@link LatestMapRaw} State object.
|
|
221
|
-
*/
|
|
222
|
-
const latestMap = (args) => {
|
|
223
|
-
const settings = args?.settings;
|
|
224
|
-
const initialValues = args?.local;
|
|
225
|
-
const validator = args?.validator;
|
|
226
|
-
const isKeyValid = args?.keyValidator ?? anyKeyIsValid;
|
|
227
|
-
const timestamp = Date.now();
|
|
228
|
-
const value = { rev: 0, items: {} };
|
|
229
|
-
// LatestMapRaw takes ownership of values within initialValues.
|
|
230
|
-
if (initialValues !== undefined) {
|
|
231
|
-
for (const [key, item] of (0, utils_1.objectEntries)(initialValues)) {
|
|
232
|
-
// TODO: AB#55932: try fixing typing of objectEntries to avoid this cast
|
|
233
|
-
const assumedValidKey = key;
|
|
234
|
-
value.items[assumedValidKey] = {
|
|
235
|
-
rev: 0,
|
|
236
|
-
timestamp,
|
|
237
|
-
value: (0, utils_1.toOpaqueJson)(item),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
const factory = (key, datastoreHandle) => ({
|
|
242
|
-
initialData: { value, allowableUpdateLatencyMs: settings?.allowableUpdateLatencyMs },
|
|
243
|
-
manager: (0, utils_1.brandIVM)(new LatestMapValueManagerImpl(key, (0, workspace_1.datastoreFromHandle)(datastoreHandle), value, settings, validator, isKeyValid)),
|
|
244
|
-
});
|
|
245
|
-
return Object.assign(factory, { instanceBase: LatestMapValueManagerImpl });
|
|
246
|
-
};
|
|
247
|
-
exports.latestMap = latestMap;
|
|
248
|
-
//# sourceMappingURL=latestMapValueManager.js.map
|