@soundworks/core 4.0.0-alpha.9 → 5.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/CHANGELOG.md +102 -0
- package/README.md +14 -29
- package/package.json +24 -42
- package/src/client/Client.js +306 -256
- package/src/client/{Context.js → ClientContext.js} +63 -54
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
- package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
- package/src/client/ClientSocket.js +270 -0
- package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
- package/src/client/index.js +36 -39
- package/src/common/BasePlugin.js +63 -58
- package/src/common/BasePluginManager.js +191 -142
- package/src/common/BaseStateManager.js +635 -145
- package/src/common/BatchedTransport.js +73 -0
- package/src/common/ParameterBag.js +90 -93
- package/src/common/PromiseStore.js +54 -0
- package/src/common/SharedState.js +821 -0
- package/src/common/SharedStateCollection.js +492 -0
- package/src/common/constants.js +14 -2
- package/src/common/logger.js +64 -68
- package/src/common/shared-state-types.js +82 -0
- package/src/common/sockets-utils.js +0 -64
- package/src/common/version.js +2 -0
- package/src/server/Server.js +552 -824
- package/src/server/ServerClient.js +74 -0
- package/src/server/{Context.js → ServerContext.js} +96 -77
- package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
- package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
- package/src/server/ServerPluginManager.js +177 -0
- package/src/server/ServerSocket.js +256 -0
- package/src/server/ServerSockets.js +150 -0
- package/src/server/ServerStateManager.js +706 -0
- package/src/server/SharedStatePrivate.js +277 -0
- package/src/server/audit-network-latency.worker.js +19 -4
- package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
- package/src/server/create-http-server.js +127 -0
- package/src/server/crypto.js +8 -7
- package/src/server/index.js +60 -17
- package/types/client/Client.d.ts +95 -70
- package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/ClientPlugin.d.ts +51 -0
- package/types/client/ClientPluginManager.d.ts +112 -0
- package/types/client/ClientSocket.d.ts +76 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
- package/types/client/index.d.ts +33 -6
- package/types/common/BasePlugin.d.ts +64 -53
- package/types/common/BasePluginManager.d.ts +84 -39
- package/types/common/BaseStateManager.d.ts +227 -49
- package/types/common/BatchedTransport.d.ts +17 -0
- package/types/common/ParameterBag.d.ts +73 -76
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/SharedState.d.ts +291 -0
- package/types/common/SharedStateCollection.d.ts +311 -0
- package/types/common/constants.d.ts +10 -3
- package/types/common/logger.d.ts +8 -6
- package/types/common/shared-state-types.d.ts +95 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +2 -0
- package/types/server/Server.d.ts +215 -136
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/ServerContext.d.ts +209 -0
- package/types/server/ServerContextManager.d.ts +75 -0
- package/types/server/ServerPlugin.d.ts +57 -0
- package/types/server/ServerPluginManager.d.ts +115 -0
- package/types/server/ServerSocket.d.ts +69 -0
- package/types/server/ServerSockets.d.ts +42 -0
- package/types/server/ServerStateManager.d.ts +284 -0
- package/types/server/SharedStatePrivate.d.ts +23 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -2
- package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
- package/types/server/create-http-server.d.ts +5 -0
- package/types/server/crypto.d.ts +4 -0
- package/types/server/index.d.ts +51 -3
- package/.eslintrc +0 -15
- package/.github/workflows/gh-pages.yml +0 -31
- package/.github/workflows/unit-tests.yml +0 -18
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -35
- package/src/client/Socket.js +0 -402
- package/src/common/BaseSharedState.js +0 -583
- package/src/common/BaseSharedStateCollection.js +0 -179
- package/src/common/SharedStatePrivate.js +0 -192
- package/src/common/promise-store.js +0 -25
- package/src/server/Client.js +0 -55
- package/src/server/PluginManager.js +0 -170
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -36
- package/src/server/Socket.js +0 -396
- package/src/server/Sockets.js +0 -224
- package/src/server/StateManager.js +0 -620
- package/tsconfig.json +0 -21
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts +0 -53
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts +0 -96
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -138
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -173
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -20
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -40
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts +0 -59
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts +0 -70
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts +0 -91
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -128
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -73
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -190
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
|
@@ -1,620 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'node:events';
|
|
2
|
-
|
|
3
|
-
import { idGenerator, isString, isPlainObject } from '@ircam/sc-utils';
|
|
4
|
-
import clonedeep from 'lodash.clonedeep';
|
|
5
|
-
|
|
6
|
-
import BaseStateManager from '../common/BaseStateManager.js';
|
|
7
|
-
import ParameterBag from '../common/ParameterBag.js';
|
|
8
|
-
import SharedStatePrivate from '../common/SharedStatePrivate.js';
|
|
9
|
-
import {
|
|
10
|
-
SERVER_ID,
|
|
11
|
-
CREATE_REQUEST,
|
|
12
|
-
CREATE_RESPONSE,
|
|
13
|
-
CREATE_ERROR,
|
|
14
|
-
DELETE_NOTIFICATION,
|
|
15
|
-
ATTACH_REQUEST,
|
|
16
|
-
ATTACH_RESPONSE,
|
|
17
|
-
ATTACH_ERROR,
|
|
18
|
-
OBSERVE_REQUEST,
|
|
19
|
-
OBSERVE_RESPONSE,
|
|
20
|
-
OBSERVE_NOTIFICATION,
|
|
21
|
-
UNOBSERVE_NOTIFICATION,
|
|
22
|
-
DELETE_SCHEMA,
|
|
23
|
-
PRIVATE_STATES,
|
|
24
|
-
} from '../common/constants.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const generateStateId = idGenerator();
|
|
28
|
-
const generateRemoteId = idGenerator();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @typedef {object} server.StateManager~schema
|
|
32
|
-
*
|
|
33
|
-
* Description of a schema to be registered by the {@link server.StateManager#registerSchema}
|
|
34
|
-
*
|
|
35
|
-
* A schema is the blueprint, or definition from which shared states can be created.
|
|
36
|
-
*
|
|
37
|
-
* It consists of a set of key / value pairs where the key is the name of
|
|
38
|
-
* the parameter, and the value is an object describing the parameter.
|
|
39
|
-
*
|
|
40
|
-
* The value can be of any of the foolowing types:
|
|
41
|
-
* - {@link server.StateManager~schemaBooleanDefinition}
|
|
42
|
-
* - {@link server.StateManager~schemaStringDefinition}
|
|
43
|
-
* - {@link server.StateManager~schemaIntegerDefinition}
|
|
44
|
-
* - {@link server.StateManager~schemaFloatDefinition}
|
|
45
|
-
* - {@link server.StateManager~schemaEnumDefinition}
|
|
46
|
-
* - {@link server.StateManager~schemaAnyDefinition}
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* const mySchema = {
|
|
50
|
-
* triggerSound: {
|
|
51
|
-
* type: 'boolean',
|
|
52
|
-
* event: true,
|
|
53
|
-
* },
|
|
54
|
-
* volume: {
|
|
55
|
-
* type: 'float'
|
|
56
|
-
* default: 0,
|
|
57
|
-
* min: -80,
|
|
58
|
-
* max: 6,
|
|
59
|
-
* }
|
|
60
|
-
* };
|
|
61
|
-
*
|
|
62
|
-
* server.stateManager.registerSchema('my-schema-name', mySchema);
|
|
63
|
-
*/
|
|
64
|
-
/**
|
|
65
|
-
* Describe a {@link server.StateManager~schema} entry of "boolean" type.
|
|
66
|
-
*
|
|
67
|
-
* @typedef {object} server.StateManager~schemaBooleanDefinition
|
|
68
|
-
* @property {string} type='boolean' - Define a boolean parameter.
|
|
69
|
-
* @property {boolean} default - Default value of the parameter.
|
|
70
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
71
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
72
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
73
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
74
|
-
* automatically set to `true` and `default` to `null`.
|
|
75
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
76
|
-
* the propagation of a parameter even when its value do not change. It
|
|
77
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
78
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
79
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
80
|
-
* `event=true` does not make sens.
|
|
81
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
82
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
83
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
84
|
-
* change on the network. This option can be usefull in cases the network
|
|
85
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
86
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
87
|
-
* will be called again on when the "real" / final value will be received.
|
|
88
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
89
|
-
*/
|
|
90
|
-
/**
|
|
91
|
-
* Describe a {@link server.StateManager~schema} entry of "string" type.
|
|
92
|
-
*
|
|
93
|
-
* @typedef {object} server.StateManager~schemaStringDefinition
|
|
94
|
-
* @property {string} type='string' - Define a boolean parameter.
|
|
95
|
-
* @property {string} default - Default value of the parameter.
|
|
96
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
97
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
98
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
99
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
100
|
-
* automatically set to `true` and `default` to `null`.
|
|
101
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
102
|
-
* the propagation of a parameter even when its value do not change. It
|
|
103
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
104
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
105
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
106
|
-
* `event=true` does not make sens.
|
|
107
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
108
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
109
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
110
|
-
* change on the network. This option can be usefull in cases the network
|
|
111
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
112
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
113
|
-
* will be called again on when the "real" / final value will be received.
|
|
114
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
115
|
-
*/
|
|
116
|
-
/**
|
|
117
|
-
* Describe a {@link server.StateManager~schema} entry of "integer" type.
|
|
118
|
-
*
|
|
119
|
-
* @typedef {object} server.StateManager~schemaIntegerDefinition
|
|
120
|
-
* @property {string} type='integer' - Define a boolean parameter.
|
|
121
|
-
* @property {number} default - Default value of the parameter.
|
|
122
|
-
* @property {number} [min=-Infinity] - Minimum value of the parameter.
|
|
123
|
-
* @property {number} [max=+Infinity] - Maximum value of the parameter.
|
|
124
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
125
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
126
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
127
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
128
|
-
* automatically set to `true` and `default` to `null`.
|
|
129
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
130
|
-
* the propagation of a parameter even when its value do not change. It
|
|
131
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
132
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
133
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
134
|
-
* `event=true` does not make sens.
|
|
135
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
136
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
137
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
138
|
-
* change on the network. This option can be usefull in cases the network
|
|
139
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
140
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
141
|
-
* will be called again on when the "real" / final value will be received.
|
|
142
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
143
|
-
*/
|
|
144
|
-
/**
|
|
145
|
-
* Describe a {@link server.StateManager~schema} entry of "float" type.
|
|
146
|
-
*
|
|
147
|
-
* @typedef {object} server.StateManager~schemaFloatDefinition
|
|
148
|
-
* @property {string} [type='float'] - Float parameter.
|
|
149
|
-
* @property {number} default - Default value.
|
|
150
|
-
* @property {number} [min=-Infinity] - Minimum value.
|
|
151
|
-
* @property {number} [max=+Infinity] - Maximum value.
|
|
152
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
153
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
154
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
155
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
156
|
-
* automatically set to `true` and `default` to `null`.
|
|
157
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
158
|
-
* the propagation of a parameter even when its value do not change. It
|
|
159
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
160
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
161
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
162
|
-
* `event=true` does not make sens.
|
|
163
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
164
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
165
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
166
|
-
* change on the network. This option can be usefull in cases the network
|
|
167
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
168
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
169
|
-
* will be called again on when the "real" / final value will be received.
|
|
170
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
171
|
-
*/
|
|
172
|
-
/**
|
|
173
|
-
* Describe a {@link server.StateManager~schema} entry of "enum" type.
|
|
174
|
-
*
|
|
175
|
-
* @typedef {object} server.StateManager~schemaEnumDefinition
|
|
176
|
-
* @property {string} [type='enum'] - Enum parameter.
|
|
177
|
-
* @property {string} default - Default value of the parameter.
|
|
178
|
-
* @property {Array} list - Possible values of the parameter.
|
|
179
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
180
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
181
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
182
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
183
|
-
* automatically set to `true` and `default` to `null`.
|
|
184
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
185
|
-
* the propagation of a parameter even when its value do not change. It
|
|
186
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
187
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
188
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
189
|
-
* `event=true` does not make sens.
|
|
190
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
191
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
192
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
193
|
-
* change on the network. This option can be usefull in cases the network
|
|
194
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
195
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
196
|
-
* will be called again on when the "real" / final value will be received.
|
|
197
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
198
|
-
*/
|
|
199
|
-
/**
|
|
200
|
-
* Describe a {@link server.StateManager~schema} entry of "any" type.
|
|
201
|
-
*
|
|
202
|
-
* Note that the `any` type always return a shallow copy of the state internal
|
|
203
|
-
* value. Mutating the returned value will therefore not modify the internal state.
|
|
204
|
-
*
|
|
205
|
-
* @typedef {object} server.StateManager~schemaAnyDefinition
|
|
206
|
-
* @property {string} [type='any'] - Parameter of any type.
|
|
207
|
-
* @property {*} default - Default value of the parameter.
|
|
208
|
-
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
209
|
-
* set to `true` the parameter `default` is set to `null`.
|
|
210
|
-
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
|
|
211
|
-
* set its value back to `null` after propagation. When `true`, `nullable` is
|
|
212
|
-
* automatically set to `true` and `default` to `null`.
|
|
213
|
-
* @property {boolean} [filterChange=true] - Setting this option to `false` forces
|
|
214
|
-
* the propagation of a parameter even when its value do not change. It
|
|
215
|
-
* offers a kind of middle ground between the default bahavior (e.g. where
|
|
216
|
-
* only changed values are propagated) and the behavior of the `event` option
|
|
217
|
-
* (which has no state per se). As such, setting this options to `false` if
|
|
218
|
-
* `event=true` does not make sens.
|
|
219
|
-
* @property {boolean} [immediate=false] - Setting this option to `true` will
|
|
220
|
-
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the
|
|
221
|
-
* state that generate the update (i.e. calling `set`), before propagating the
|
|
222
|
-
* change on the network. This option can be usefull in cases the network
|
|
223
|
-
* would introduce a noticeable latency on the client. If for some reason
|
|
224
|
-
* the value is overriden server-side (e.g. in an `updateHook`) the listeners
|
|
225
|
-
* will be called again on when the "real" / final value will be received.
|
|
226
|
-
* @property {object} [metas={}] - Optionnal metadata of the parameter.
|
|
227
|
-
*/
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* @callback server.StateManager~ObserveCallback
|
|
231
|
-
* @async
|
|
232
|
-
* @param {string} schemaName - name of the schema
|
|
233
|
-
* @param {number} stateId - id of the state
|
|
234
|
-
* @param {number} nodeId - id of the node that created the state
|
|
235
|
-
*/
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @callback server.StateManager~updateHook
|
|
239
|
-
* @async
|
|
240
|
-
*
|
|
241
|
-
* @param {object} updates - Update object as given on a set callback, or
|
|
242
|
-
* result of the previous hook
|
|
243
|
-
* @param {object} currentValues - Current values of the state.
|
|
244
|
-
* @param {object} [context=null] - Optionnal context passed by the creator
|
|
245
|
-
* of the update.
|
|
246
|
-
*
|
|
247
|
-
* @return {object} The "real" updates to be applied on the state.
|
|
248
|
-
*/
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* The `StateManager` allows to create new {@link server.SharedState}s, or attach
|
|
252
|
-
* to {@link server.SharedState}s created by other nodes (clients or server). It
|
|
253
|
-
* can also track all the {@link server.SharedState}s created by other nodes.
|
|
254
|
-
*
|
|
255
|
-
* An instance of `StateManager` is automatically created by the `soundworks.Server`
|
|
256
|
-
* at initialization (cf. {@link server.Server#stateManager}).
|
|
257
|
-
*
|
|
258
|
-
* Compared to the {@link client.StateManager}, the `server.StateManager` can also
|
|
259
|
-
* create and delete schemas, as well as register update hook that are executed when
|
|
260
|
-
* a state is updated.
|
|
261
|
-
*
|
|
262
|
-
* See {@link server.Server#stateManager}
|
|
263
|
-
*
|
|
264
|
-
* Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
|
|
265
|
-
*
|
|
266
|
-
* ```
|
|
267
|
-
* // server-side
|
|
268
|
-
* import { Server } from '@soundworks/server/index.js';
|
|
269
|
-
*
|
|
270
|
-
* const server = new Server(config);
|
|
271
|
-
* // declare and register the schema of a shared state.
|
|
272
|
-
* server.stateManager.registerSchema('some-global-state', {
|
|
273
|
-
* myRandom: {
|
|
274
|
-
* type: 'float',
|
|
275
|
-
* default: 0,
|
|
276
|
-
* }
|
|
277
|
-
* });
|
|
278
|
-
*
|
|
279
|
-
* await server.start();
|
|
280
|
-
*
|
|
281
|
-
* // create a global state server-side
|
|
282
|
-
* const globalState = await server.stateManager.create('some-global-state');
|
|
283
|
-
* // listen and react to the changes made by the clients
|
|
284
|
-
* globalState.onUpdate(updates => console.log(updates));
|
|
285
|
-
* ```
|
|
286
|
-
*
|
|
287
|
-
* ```
|
|
288
|
-
* // client-side
|
|
289
|
-
* import { Client } from '@soundworks/client.index.js';
|
|
290
|
-
*
|
|
291
|
-
* const client = new Client(config);
|
|
292
|
-
* await client.start();
|
|
293
|
-
*
|
|
294
|
-
* // attach to the global state created by the server
|
|
295
|
-
* const globalState = await client.stateManager.attach('some-global-state');
|
|
296
|
-
*
|
|
297
|
-
* // update the value of a `myRandom` parameter every seconds
|
|
298
|
-
* setInterval(() => {
|
|
299
|
-
* globalState.set({ myRandom: Math.random() });
|
|
300
|
-
* }, 1000);
|
|
301
|
-
* ```
|
|
302
|
-
*
|
|
303
|
-
* @memberof server
|
|
304
|
-
* @extends BaseStateManager
|
|
305
|
-
* @inheritdoc
|
|
306
|
-
* @hideconstructor
|
|
307
|
-
*/
|
|
308
|
-
class StateManager extends BaseStateManager {
|
|
309
|
-
constructor() {
|
|
310
|
-
// acts as a client of itself locally
|
|
311
|
-
const localClientId = SERVER_ID;
|
|
312
|
-
const localTransport = new EventEmitter();
|
|
313
|
-
|
|
314
|
-
super(localClientId, localTransport);
|
|
315
|
-
|
|
316
|
-
this._clientByNodeId = new Map();
|
|
317
|
-
this._serverStatesById = new Map();
|
|
318
|
-
this._schemas = new Map();
|
|
319
|
-
this._observers = new Set();
|
|
320
|
-
this._hooksBySchemaName = new Map(); // protected
|
|
321
|
-
|
|
322
|
-
this.addClient(localClientId, localTransport);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Add a client to the manager.
|
|
327
|
-
*
|
|
328
|
-
* This is automatically handled by the {@link server.Server} when a client connects.
|
|
329
|
-
*
|
|
330
|
-
* @param {number} nodeId - Id of the client node, as given in
|
|
331
|
-
* {@link client.StateManager}
|
|
332
|
-
* @param {object} transport - Transport mecanism to communicate with the
|
|
333
|
-
* client. Must implement a basic EventEmitter API.
|
|
334
|
-
*
|
|
335
|
-
* @private
|
|
336
|
-
*/
|
|
337
|
-
addClient(nodeId, transport) {
|
|
338
|
-
const client = { id: nodeId, transport };
|
|
339
|
-
this._clientByNodeId.set(nodeId, client);
|
|
340
|
-
|
|
341
|
-
// ---------------------------------------------
|
|
342
|
-
// CREATE
|
|
343
|
-
// ---------------------------------------------
|
|
344
|
-
client.transport.addListener(CREATE_REQUEST, (reqId, schemaName, requireSchema, initValues = {}) => {
|
|
345
|
-
if (this._schemas.has(schemaName)) {
|
|
346
|
-
try {
|
|
347
|
-
const schema = this._schemas.get(schemaName);
|
|
348
|
-
const stateId = generateStateId.next().value;
|
|
349
|
-
const remoteId = generateRemoteId.next().value;
|
|
350
|
-
// id, schemaName, schema, manager, initValues = {}
|
|
351
|
-
const state = new SharedStatePrivate(stateId, schemaName, schema, this, initValues);
|
|
352
|
-
|
|
353
|
-
state._attachClient(remoteId, client, true); // attach client to the state
|
|
354
|
-
this._serverStatesById.set(stateId, state);
|
|
355
|
-
|
|
356
|
-
const currentValues = state._parameters.getValues();
|
|
357
|
-
const sendedSchema = requireSchema ? schema : null;
|
|
358
|
-
|
|
359
|
-
client.transport.emit(CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
|
|
360
|
-
|
|
361
|
-
this._observers.forEach(observer => {
|
|
362
|
-
if (observer.id !== nodeId) {
|
|
363
|
-
observer.transport.emit(OBSERVE_NOTIFICATION, schemaName, stateId, nodeId);
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
} catch (err) {
|
|
367
|
-
client.transport.emit(CREATE_ERROR, reqId, err.message);
|
|
368
|
-
console.error(err.message);
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
const msg = `Cannot create state "${schemaName}", schema does not exists`;
|
|
372
|
-
console.error(msg);
|
|
373
|
-
client.transport.emit(CREATE_ERROR, reqId, msg);
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// ---------------------------------------------
|
|
378
|
-
// ATTACH (when creator, is attached by default)
|
|
379
|
-
// ---------------------------------------------
|
|
380
|
-
client.transport.addListener(ATTACH_REQUEST, (reqId, schemaName, stateId = null, requireSchema = true) => {
|
|
381
|
-
if (this._schemas.has(schemaName)) {
|
|
382
|
-
let state = null;
|
|
383
|
-
|
|
384
|
-
if (stateId !== null && this._serverStatesById.has(stateId)) {
|
|
385
|
-
state = this._serverStatesById.get(stateId);
|
|
386
|
-
} else if (stateId === null) {
|
|
387
|
-
// if no `stateId` given, we try to find the first state with the given
|
|
388
|
-
// `schemaName` in the list, this allow a client to attach to a global
|
|
389
|
-
// state created by the server (or some persistant client) without
|
|
390
|
-
// having to know the `stateId` (e.g. some global state...)
|
|
391
|
-
for (let existingState of this._serverStatesById.values()) {
|
|
392
|
-
if (existingState.schemaName === schemaName) {
|
|
393
|
-
state = existingState;
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (state !== null) {
|
|
400
|
-
// @note - we use a unique remote id to allow a client to attach
|
|
401
|
-
// several times to the same state.
|
|
402
|
-
// i.e. same state -> several remote attach on the same node
|
|
403
|
-
const remoteId = generateRemoteId.next().value;
|
|
404
|
-
const schema = this._schemas.get(schemaName);
|
|
405
|
-
const currentValues = state._parameters.getValues();
|
|
406
|
-
|
|
407
|
-
state._attachClient(remoteId, client, false);
|
|
408
|
-
// send schema for client-side instanciation
|
|
409
|
-
const sendedSchema = requireSchema ? schema : null;
|
|
410
|
-
client.transport.emit(ATTACH_RESPONSE, reqId, state.id, remoteId, schemaName, sendedSchema, currentValues);
|
|
411
|
-
} else {
|
|
412
|
-
const msg = `Cannot attach, no existing state for schema "${schemaName}" with stateId: "${stateId}"`;
|
|
413
|
-
client.transport.emit(ATTACH_ERROR, reqId, msg);
|
|
414
|
-
console.error(msg);
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
const msg = `Cannot attach, schema "${schemaName}" does not exists`;
|
|
418
|
-
client.transport.emit(ATTACH_ERROR, reqId, msg);
|
|
419
|
-
console.error(msg);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// ---------------------------------------------
|
|
424
|
-
// OBSERVE PEERS (be notified when a state is created, lazy)
|
|
425
|
-
// ---------------------------------------------
|
|
426
|
-
client.transport.addListener(OBSERVE_REQUEST, reqId => {
|
|
427
|
-
const statesInfos = [];
|
|
428
|
-
|
|
429
|
-
this._serverStatesById.forEach(state => {
|
|
430
|
-
const { schemaName, id, _creatorId } = state;
|
|
431
|
-
// only track application states
|
|
432
|
-
// (e.g. do not propagate infos about audit state)
|
|
433
|
-
if (!PRIVATE_STATES.includes(schemaName) && _creatorId !== client.id) {
|
|
434
|
-
statesInfos.push([schemaName, id, _creatorId]);
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// add client to observers first because if some (sync) server side
|
|
439
|
-
// callback throws, the client would never be added to the list
|
|
440
|
-
this._observers.add(client);
|
|
441
|
-
|
|
442
|
-
client.transport.emit(OBSERVE_RESPONSE, reqId, ...statesInfos);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
client.transport.addListener(UNOBSERVE_NOTIFICATION, () => {
|
|
446
|
-
this._observers.delete(client);
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Remove a client from the manager. Clean all created or attached states.
|
|
452
|
-
*
|
|
453
|
-
* This is automatically handled by the {@link server.Server} when a client disconnects.
|
|
454
|
-
*
|
|
455
|
-
* @param {number} nodeId - Id of the client node, as given in
|
|
456
|
-
* {@link client.StateManager}
|
|
457
|
-
*
|
|
458
|
-
* @private
|
|
459
|
-
*/
|
|
460
|
-
removeClient(nodeId) {
|
|
461
|
-
for (let [_id, state] of this._serverStatesById.entries()) {
|
|
462
|
-
let deleteState = false;
|
|
463
|
-
|
|
464
|
-
// define if the client is the creator of the state, in which case
|
|
465
|
-
// everybody must delete it
|
|
466
|
-
for (let [remoteId, attachedClient] of state._attachedClients.entries()) {
|
|
467
|
-
if (nodeId === attachedClient.id && remoteId === state._creatorRemoteId) {
|
|
468
|
-
deleteState = true;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
for (let [remoteId, attachedClient] of state._attachedClients.entries()) {
|
|
473
|
-
if (nodeId === attachedClient.id) {
|
|
474
|
-
state._detachClient(remoteId, attachedClient);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (deleteState) {
|
|
478
|
-
if (remoteId !== state._creatorRemoteId) {
|
|
479
|
-
// send notification to other attached nodes
|
|
480
|
-
attachedClient.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
this._serverStatesById.delete(state.id);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// if is an observer, delete it
|
|
489
|
-
const client = this._clientByNodeId.get(nodeId);
|
|
490
|
-
this._observers.delete(client);
|
|
491
|
-
this._clientByNodeId.delete(nodeId);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Register a schema from which shared states (cf. {@link common.SharedState})
|
|
496
|
-
* can be instanciated.
|
|
497
|
-
*
|
|
498
|
-
* @param {string} schemaName - Name of the schema.
|
|
499
|
-
* @param {server.StateManager~schema} schema - Data structure
|
|
500
|
-
* describing the states that will be created from this schema.
|
|
501
|
-
*
|
|
502
|
-
* @see {@link server.StateManager#create}
|
|
503
|
-
* @see {@link client.StateManager#create}
|
|
504
|
-
*
|
|
505
|
-
* @example
|
|
506
|
-
* server.stateManager.registerSchema('my-schema', {
|
|
507
|
-
* myBoolean: {
|
|
508
|
-
* type: 'boolean'
|
|
509
|
-
* default: false,
|
|
510
|
-
* },
|
|
511
|
-
* myFloat: {
|
|
512
|
-
* type: 'float'
|
|
513
|
-
* default: 0.1,
|
|
514
|
-
* min: -1,
|
|
515
|
-
* max: 1
|
|
516
|
-
* }
|
|
517
|
-
* })
|
|
518
|
-
*/
|
|
519
|
-
registerSchema(schemaName, schema) {
|
|
520
|
-
if (!isString(schemaName)) {
|
|
521
|
-
throw new Error(`[stateManager.registerSchema] Invalid schema name "${schemaName}", should be a string`);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (this._schemas.has(schemaName)) {
|
|
525
|
-
throw new Error(`[stateManager.registerSchema] cannot register schema with name: "${schemaName}", schema name already exists`);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (!isPlainObject(schema)) {
|
|
529
|
-
throw new Error(`[stateManager.registerSchema] Invalid schema, should be an object`);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
ParameterBag.validateSchema(schema);
|
|
533
|
-
|
|
534
|
-
this._schemas.set(schemaName, clonedeep(schema));
|
|
535
|
-
// create hooks list
|
|
536
|
-
this._hooksBySchemaName.set(schemaName, new Set());
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Delete a schema and all associated states.
|
|
541
|
-
*
|
|
542
|
-
* When a schema is deleted, all states created from this schema are deleted
|
|
543
|
-
* as well, therefore all attached clients are detached and the `onDetach`
|
|
544
|
-
* and `onDelete` callbacks are called on the related states.
|
|
545
|
-
*
|
|
546
|
-
* @param {string} schemaName - Name of the schema.
|
|
547
|
-
*/
|
|
548
|
-
deleteSchema(schemaName) {
|
|
549
|
-
// @note: deleting schema
|
|
550
|
-
for (let [_id, state] of this._serverStatesById.entries()) {
|
|
551
|
-
if (state.schemaName === schemaName) {
|
|
552
|
-
for (let [remoteId, attached] of state._attachedClients.entries()) {
|
|
553
|
-
state._detachClient(remoteId, attached);
|
|
554
|
-
attached.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
this._serverStatesById.delete(this.id);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// clear schema cache of all connected clients
|
|
562
|
-
for (let client of this._clientByNodeId.values()) {
|
|
563
|
-
client.transport.emit(`${DELETE_SCHEMA}`, schemaName);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
this._schemas.delete(schemaName);
|
|
567
|
-
// delete registered hooks
|
|
568
|
-
this._hooksBySchemaName.delete(schemaName);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Register a function for a given schema (e.g. will be applied on all states
|
|
573
|
-
* created from this schema) that will be executed before the update values
|
|
574
|
-
* are propagated. For example, this could be used to implement a preset system
|
|
575
|
-
* where all the values of the state are updated from e.g. some data stored in
|
|
576
|
-
* filesystem while the consumer of the state only want to update the preset name.
|
|
577
|
-
*
|
|
578
|
-
* The hook is associated to every state of its kind (i.e. schemaName) and
|
|
579
|
-
* executed on every update (call of `set`). Note that the hooks are executed
|
|
580
|
-
* server-side regarless the node on which `set` has been called and before
|
|
581
|
-
* the "actual" update of the state (e.g. before the call of `onUpdate`).
|
|
582
|
-
*
|
|
583
|
-
* @param {string} schemaName - Kind of states on which applying the hook.
|
|
584
|
-
* @param {server.StateManager~updateHook} updateHook - Function
|
|
585
|
-
* called between the `set` call and the actual update.
|
|
586
|
-
*
|
|
587
|
-
* @returns {Fuction} deleteHook - Handler that deletes the hook when executed.
|
|
588
|
-
*
|
|
589
|
-
* @example
|
|
590
|
-
* server.stateManager.registerSchema('hooked', {
|
|
591
|
-
* name: { type: 'string', default: null, nullable: true },
|
|
592
|
-
* name: { numUpdates: 'integer', default: 0 },
|
|
593
|
-
* });
|
|
594
|
-
* server.stateManager.registerUpdateHook('hooked', updates => {
|
|
595
|
-
* return {
|
|
596
|
-
* ...updates
|
|
597
|
-
* numUpdates: currentValues.numUpdates + 1,
|
|
598
|
-
* };
|
|
599
|
-
* });
|
|
600
|
-
*
|
|
601
|
-
* const state = await server.stateManager.create('hooked');
|
|
602
|
-
*
|
|
603
|
-
* await state.set({ name: 'test' });
|
|
604
|
-
* const values = state.getValues();
|
|
605
|
-
* assert.deepEqual(result, { name: 'test', numUpdates: 1 });
|
|
606
|
-
*/
|
|
607
|
-
registerUpdateHook(schemaName, updateHook) {
|
|
608
|
-
// throw error if schemaName has not been registered
|
|
609
|
-
if (!this._schemas.has(schemaName)) {
|
|
610
|
-
throw new Error(`[stateManager.registerUpdateHook] cannot register update hook for schema name "${schemaName}", schema name does not exists`);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const hooks = this._hooksBySchemaName.get(schemaName);
|
|
614
|
-
hooks.add(updateHook);
|
|
615
|
-
|
|
616
|
-
return () => hooks.delete(updateHook);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
export default StateManager;
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"include": ["src/**/*.js"],
|
|
3
|
-
|
|
4
|
-
"compilerOptions": {
|
|
5
|
-
// Tells TypeScript to read JS files, as
|
|
6
|
-
// normally they are ignored as source files
|
|
7
|
-
"allowJs": true,
|
|
8
|
-
// Generate d.ts files
|
|
9
|
-
"declaration": true,
|
|
10
|
-
// This compiler run should
|
|
11
|
-
// only output d.ts files
|
|
12
|
-
"emitDeclarationOnly": true,
|
|
13
|
-
// Types should go into this directory.
|
|
14
|
-
// Removing this would place the .d.ts files
|
|
15
|
-
// next to the .js files
|
|
16
|
-
"outDir": "types",
|
|
17
|
-
// go to js file when using IDE functions like
|
|
18
|
-
// "Go to Definition" in VSCode
|
|
19
|
-
"declarationMap": true
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../../src/client/Client.js"],"names":[],"mappings":";AAeA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IACE;;;;OAIG;IACH,oBAJW,oDAAkD,EA0I5D;IApGC;;;;OAIG;IACH,MAFU,MAAM,CAEO;IAEvB;;;;OAIG;IACH,QAFU,oDAAkD,CAExC;IAYpB;;;;;;OAMG;IACH,IAFU,MAAM,CAEF;IAEd;;;;;OAKG;IACH,MAFU,MAAM,CAEA;IAEhB;;;;;;OAMG;IACH,sBAA0B;IAE1B;;;;OAIG;IACH,QAFU,MAAM,CAE8B;IAE9C;;;;;;;OAOG;IACH,sCAA0C;IAE1C;;;;;;;OAOG;IACH,oCAA4C;IAE5C;;;;;;;OAOG;IACH,kCAAwB;IAExB;;;;OAIG;IACH,QAFU,MAAM,CAEI;IAEpB,eAAe;IACf,iCAAyC;IACzC,eAAe;IACf,oBAAuB;IAKzB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,sBA6DC;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAmBC;IAED;;;;;;;;;;;;;;;OAeG;IACH,sBAUC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,iBAPa,2BAA2B,CAiBvC;IAED;;;;;OAKG;IACH,6CAIC;IAED,eAAe;IACf,wBAgBC;CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Context.d.ts","sourceRoot":"","sources":["../../src/client/Context.js"],"names":[],"mappings":";AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH;IACE;;;OAGG;IACH,mCAgDC;IA3CC;;OAEG;IACH,eAAoB;IAEpB;;OAEG;IACH,eAAoB;IAqCtB;;;;;;;;;;OAUG;IACH,mBAEC;IAED;;;;;;;OAOG;IACH,uBAAgB;IAEhB;;;;;;OAMG;IACH,sBAAe;IAEf;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,sBAeC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,qBAUC;CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContextManager.d.ts","sourceRoot":"","sources":["../../src/client/ContextManager.js"],"names":[],"mappings":";AAuBA;;;;;;;;;GASG;AACH;IAEI,eAAe;IACf,kBAAwC;IACxC,eAAe;IACf,8BAAsC;IAGxC;;;;;OAKG;IACH,iBAMC;IAED;;;;;;OAMG;IACH,oCAyBC;IAED;;;;OAIG;IACH,cAGC;IAED;;;;OAIG;IACH,aAGC;CACF"}
|