@fluid-experimental/data-objects 0.47.1 → 0.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export * from "./kvpair";
6
+ export * from "./signalManager";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export * from "./kvpair";
6
+ export * from "./signalManager";
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./kvpair\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./kvpair\";\nexport * from \"./signalManager\";\n"]}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export * from "./signalManager";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signalManager/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export * from "./signalManager";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signalManager/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./signalManager\";\n"]}
@@ -0,0 +1,131 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /// <reference types="node" />
6
+ import { EventEmitter } from "events";
7
+ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
8
+ import { IErrorEvent } from "@fluidframework/common-definitions";
9
+ import { TypedEventEmitter } from "@fluidframework/common-utils";
10
+ import { Jsonable } from "@fluidframework/datastore-definitions";
11
+ import { IInboundSignalMessage } from "@fluidframework/runtime-definitions";
12
+ export declare type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void;
13
+ /**
14
+ * ISignaler defines an interface for working with signals that is similar to the more common
15
+ * eventing patterns of EventEmitter. In addition to sending and responding to signals, it
16
+ * provides explicit methods around signal requests to other connected clients.
17
+ */
18
+ export interface ISignaler {
19
+ /**
20
+ * Adds a listener for the specified signal. It behaves in the same way as EventEmitter's `on`
21
+ * method regarding multiple registrations, callback order, etc.
22
+ * @param signalName - The name of the signal
23
+ * @param listener - The callback signal handler to add
24
+ * @returns This ISignaler
25
+ */
26
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
27
+ /**
28
+ * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's
29
+ * `off` method regarding multiple registrations, removal order, etc.
30
+ * @param signalName - The name of the signal
31
+ * @param listener - The callback signal handler to remove
32
+ * @returns This ISignaler
33
+ */
34
+ offSignal(signalName: string, listener: SignalListener | ((message: any) => void)): ISignaler;
35
+ /**
36
+ * Send a signal with payload to its connected listeners.
37
+ * @param signalName - The name of the signal
38
+ * @param payload - The data to send with the signal
39
+ */
40
+ submitSignal(signalName: string, payload?: Jsonable): any;
41
+ /**
42
+ * Adds a listener for a broadcast request. The listener is called when a client calls
43
+ * `requestBroadcast` for that signal. It behaves in the same way as EventEmitter's `on`
44
+ * method regarding multiple registrations, callback order, etc.
45
+ * @param signalName - The signal for which broadcast is requested
46
+ * @param listener - The callback for the broadcast request to add
47
+ * @returns This ISignaler
48
+ */
49
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
50
+ /**
51
+ * Remove a listener for a broadcast request. It behaves in the same way as EventEmitter's
52
+ * `off` method regarding multiple registrations, removal order, etc.
53
+ * @param signalName - The signal for which broadcast is requested
54
+ * @param listener - The callback for the broadcast request to remove
55
+ * @returns This ISignaler
56
+ */
57
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
58
+ /**
59
+ * Request broadcast of a signal from other connected clients. Other clients must have
60
+ * registered to respond to broadcast requests using the `onBroadcastRequested` method.
61
+ * @param signalName - The signal for which broadcast is requested
62
+ * @param payload - A payload to send with the broadcast request
63
+ */
64
+ requestBroadcast(signalName: string, payload?: Jsonable): any;
65
+ }
66
+ /**
67
+ * Duck type of something that provides the expected signalling functionality:
68
+ * A way to verify we can signal, a way to send a signal, and a way to listen for incoming signals
69
+ */
70
+ export interface IRuntimeSignaler {
71
+ connected: boolean;
72
+ on(event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void): any;
73
+ submitSignal(type: string, content: any): void;
74
+ }
75
+ /**
76
+ * Note: currently experimental and under development
77
+ *
78
+ * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime
79
+ * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can
80
+ * then be used in place of the original signaler. It uses a separate internal EventEmitter to
81
+ * manage callbacks, and thus will reflect that behavior with regards to callback registration and
82
+ * deregistration.
83
+ */
84
+ export declare class Signaler extends TypedEventEmitter<IErrorEvent> implements ISignaler {
85
+ /**
86
+ * Object to wrap that can submit and listen to signals
87
+ */
88
+ private readonly signaler;
89
+ private readonly emitter;
90
+ private readonly managerId;
91
+ constructor(
92
+ /**
93
+ * Object to wrap that can submit and listen to signals
94
+ */
95
+ signaler: IRuntimeSignaler,
96
+ /**
97
+ * Optional id to assign to this manager that will be attached to
98
+ * signal names. Useful to avoid collisions if there are multiple
99
+ * signal users at the Container level
100
+ */
101
+ managerId?: string);
102
+ private getManagerSignalName;
103
+ private getBroadcastSignalName;
104
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
105
+ offSignal(signalName: string, listener: SignalListener): ISignaler;
106
+ submitSignal(signalName: string, payload?: Jsonable): void;
107
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
108
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
109
+ requestBroadcast(signalName: string, payload?: Jsonable): void;
110
+ }
111
+ /**
112
+ * Note: currently experimental and under development
113
+ *
114
+ * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static
115
+ * users to get an ISignaler without a custom DO. Where possible, consumers should instead
116
+ * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.
117
+ */
118
+ export declare class SignalManager extends DataObject<{}, undefined, IErrorEvent> implements EventEmitter, ISignaler {
119
+ private _manager;
120
+ private get manager();
121
+ static get Name(): string;
122
+ static readonly factory: DataObjectFactory<SignalManager, undefined, undefined, IErrorEvent>;
123
+ protected hasInitialized(): Promise<void>;
124
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
125
+ offSignal(signalName: string, listener: SignalListener): ISignaler;
126
+ submitSignal(signalName: string, payload?: Jsonable): void;
127
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
128
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
129
+ requestBroadcast(signalName: string, payload?: Jsonable): void;
130
+ }
131
+ //# sourceMappingURL=signalManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalManager.d.ts","sourceRoot":"","sources":["../../src/signalManager/signalManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { EventEmitter } from "events";
6
+ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
7
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
8
+ /**
9
+ * Note: currently experimental and under development
10
+ *
11
+ * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime
12
+ * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can
13
+ * then be used in place of the original signaler. It uses a separate internal EventEmitter to
14
+ * manage callbacks, and thus will reflect that behavior with regards to callback registration and
15
+ * deregistration.
16
+ */
17
+ export class Signaler extends TypedEventEmitter {
18
+ constructor(
19
+ /**
20
+ * Object to wrap that can submit and listen to signals
21
+ */
22
+ signaler,
23
+ /**
24
+ * Optional id to assign to this manager that will be attached to
25
+ * signal names. Useful to avoid collisions if there are multiple
26
+ * signal users at the Container level
27
+ */
28
+ managerId) {
29
+ super();
30
+ this.signaler = signaler;
31
+ this.emitter = new EventEmitter();
32
+ this.emitter.on("error", (error) => {
33
+ this.emit("error", error);
34
+ });
35
+ this.managerId = managerId ? `#${managerId}` : undefined;
36
+ this.signaler.on("signal", (message, local) => {
37
+ const clientId = message.clientId;
38
+ // Only call listeners when the runtime is connected and if the signal has an
39
+ // identifiable sender clientId. The listener is responsible for deciding how
40
+ // it wants to handle local/remote signals
41
+ // eslint-disable-next-line no-null/no-null
42
+ if (this.signaler.connected && clientId !== null) {
43
+ this.emitter.emit(message.type, clientId, local, message.content);
44
+ }
45
+ });
46
+ }
47
+ getManagerSignalName(signalName) {
48
+ return this.managerId ? `${signalName}${this.managerId}` : signalName;
49
+ }
50
+ getBroadcastSignalName(signalName) {
51
+ return `${signalName}#req`;
52
+ }
53
+ // ISignaler methods
54
+ onSignal(signalName, listener) {
55
+ const managerSignalName = this.getManagerSignalName(signalName);
56
+ this.emitter.on(managerSignalName, listener);
57
+ return this;
58
+ }
59
+ offSignal(signalName, listener) {
60
+ const managerSignalName = this.getManagerSignalName(signalName);
61
+ this.emitter.off(managerSignalName, listener);
62
+ return this;
63
+ }
64
+ submitSignal(signalName, payload) {
65
+ const managerSignalName = this.getManagerSignalName(signalName);
66
+ if (this.signaler.connected) {
67
+ this.signaler.submitSignal(managerSignalName, payload);
68
+ }
69
+ }
70
+ onBroadcastRequested(signalName, listener) {
71
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
72
+ return this.onSignal(broadcastSignalName, listener);
73
+ }
74
+ offBroadcastRequested(signalName, listener) {
75
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
76
+ return this.offSignal(broadcastSignalName, listener);
77
+ }
78
+ requestBroadcast(signalName, payload) {
79
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
80
+ this.submitSignal(broadcastSignalName, payload);
81
+ }
82
+ }
83
+ /**
84
+ * Note: currently experimental and under development
85
+ *
86
+ * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static
87
+ * users to get an ISignaler without a custom DO. Where possible, consumers should instead
88
+ * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.
89
+ */
90
+ // eslint-disable-next-line @typescript-eslint/ban-types
91
+ export class SignalManager extends DataObject {
92
+ get manager() {
93
+ assert(this._manager !== undefined, 0x24b /* "internal signaler should be defined" */);
94
+ return this._manager;
95
+ }
96
+ static get Name() { return "@fluid-example/signal-manager"; }
97
+ async hasInitialized() {
98
+ this._manager = new Signaler(this.runtime);
99
+ this.manager.on("error", (error) => {
100
+ this.emit("error", error);
101
+ });
102
+ }
103
+ // ISignaler methods Note these are all passthroughs
104
+ onSignal(signalName, listener) {
105
+ this.manager.onSignal(signalName, listener);
106
+ return this;
107
+ }
108
+ offSignal(signalName, listener) {
109
+ this.manager.offSignal(signalName, listener);
110
+ return this;
111
+ }
112
+ submitSignal(signalName, payload) {
113
+ this.manager.submitSignal(signalName, payload);
114
+ }
115
+ onBroadcastRequested(signalName, listener) {
116
+ this.manager.onBroadcastRequested(signalName, listener);
117
+ return this;
118
+ }
119
+ offBroadcastRequested(signalName, listener) {
120
+ this.manager.offBroadcastRequested(signalName, listener);
121
+ return this;
122
+ }
123
+ requestBroadcast(signalName, payload) {
124
+ this.manager.requestBroadcast(signalName, payload);
125
+ }
126
+ }
127
+ SignalManager.factory = new DataObjectFactory(SignalManager.Name, SignalManager, [], {});
128
+ //# sourceMappingURL=signalManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalManager.js","sourceRoot":"","sources":["../../src/signalManager/signalManager.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 { EventEmitter } from \"events\";\nimport { DataObject, DataObjectFactory } from \"@fluidframework/aqueduct\";\nimport { IErrorEvent } from \"@fluidframework/common-definitions\";\nimport { assert, TypedEventEmitter } from \"@fluidframework/common-utils\";\nimport { Jsonable } from \"@fluidframework/datastore-definitions\";\nimport { IInboundSignalMessage } from \"@fluidframework/runtime-definitions\";\n\n// TODO:\n// add way to mark with current sequence number for ordering signals relative to ops\n// throttling and batching\n\nexport type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void;\n\n/**\n * ISignaler defines an interface for working with signals that is similar to the more common\n * eventing patterns of EventEmitter. In addition to sending and responding to signals, it\n * provides explicit methods around signal requests to other connected clients.\n */\nexport interface ISignaler {\n /**\n * Adds a listener for the specified signal. It behaves in the same way as EventEmitter's `on`\n * method regarding multiple registrations, callback order, etc.\n * @param signalName - The name of the signal\n * @param listener - The callback signal handler to add\n * @returns This ISignaler\n */\n onSignal(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's\n * `off` method regarding multiple registrations, removal order, etc.\n * @param signalName - The name of the signal\n * @param listener - The callback signal handler to remove\n * @returns This ISignaler\n */\n offSignal(signalName: string, listener: SignalListener | ((message: any) => void)): ISignaler;\n /**\n * Send a signal with payload to its connected listeners.\n * @param signalName - The name of the signal\n * @param payload - The data to send with the signal\n */\n submitSignal(signalName: string, payload?: Jsonable);\n\n /**\n * Adds a listener for a broadcast request. The listener is called when a client calls\n * `requestBroadcast` for that signal. It behaves in the same way as EventEmitter's `on`\n * method regarding multiple registrations, callback order, etc.\n * @param signalName - The signal for which broadcast is requested\n * @param listener - The callback for the broadcast request to add\n * @returns This ISignaler\n */\n onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Remove a listener for a broadcast request. It behaves in the same way as EventEmitter's\n * `off` method regarding multiple registrations, removal order, etc.\n * @param signalName - The signal for which broadcast is requested\n * @param listener - The callback for the broadcast request to remove\n * @returns This ISignaler\n */\n offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Request broadcast of a signal from other connected clients. Other clients must have\n * registered to respond to broadcast requests using the `onBroadcastRequested` method.\n * @param signalName - The signal for which broadcast is requested\n * @param payload - A payload to send with the broadcast request\n */\n requestBroadcast(signalName: string, payload?: Jsonable);\n}\n\n/**\n * Duck type of something that provides the expected signalling functionality:\n * A way to verify we can signal, a way to send a signal, and a way to listen for incoming signals\n */\nexport interface IRuntimeSignaler {\n connected: boolean;\n on(event: \"signal\", listener: (message: IInboundSignalMessage, local: boolean) => void);\n submitSignal(type: string, content: any): void;\n}\n\n/**\n * Note: currently experimental and under development\n *\n * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime\n * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can\n * then be used in place of the original signaler. It uses a separate internal EventEmitter to\n * manage callbacks, and thus will reflect that behavior with regards to callback registration and\n * deregistration.\n */\nexport class Signaler extends TypedEventEmitter<IErrorEvent> implements ISignaler {\n private readonly emitter = new EventEmitter();\n\n private readonly managerId: string | undefined;\n\n constructor(\n /**\n * Object to wrap that can submit and listen to signals\n */\n private readonly signaler: IRuntimeSignaler,\n /**\n * Optional id to assign to this manager that will be attached to\n * signal names. Useful to avoid collisions if there are multiple\n * signal users at the Container level\n */\n managerId?: string,\n ) {\n super();\n this.emitter.on(\"error\", (error) => {\n this.emit(\"error\", error);\n });\n this.managerId = managerId ? `#${managerId}` : undefined;\n this.signaler.on(\"signal\", (message: IInboundSignalMessage, local: boolean) => {\n const clientId = message.clientId;\n // Only call listeners when the runtime is connected and if the signal has an\n // identifiable sender clientId. The listener is responsible for deciding how\n // it wants to handle local/remote signals\n // eslint-disable-next-line no-null/no-null\n if (this.signaler.connected && clientId !== null) {\n this.emitter.emit(message.type, clientId, local, message.content);\n }\n });\n }\n\n private getManagerSignalName(signalName: string): string {\n return this.managerId ? `${signalName}${this.managerId}` : signalName;\n }\n\n private getBroadcastSignalName(signalName: string): string {\n return `${signalName}#req`;\n }\n\n // ISignaler methods\n\n public onSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const managerSignalName = this.getManagerSignalName(signalName);\n this.emitter.on(managerSignalName, listener);\n return this;\n }\n\n public offSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const managerSignalName = this.getManagerSignalName(signalName);\n this.emitter.off(managerSignalName, listener);\n return this;\n }\n\n public submitSignal(\n signalName: string,\n payload?: Jsonable,\n ) {\n const managerSignalName = this.getManagerSignalName(signalName);\n if (this.signaler.connected) {\n this.signaler.submitSignal(managerSignalName, payload);\n }\n }\n\n public onBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n return this.onSignal(broadcastSignalName, listener);\n }\n\n public offBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n return this.offSignal(broadcastSignalName, listener);\n }\n\n public requestBroadcast(\n signalName: string,\n payload?: Jsonable,\n ) {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n this.submitSignal(broadcastSignalName, payload);\n }\n}\n\n/**\n * Note: currently experimental and under development\n *\n * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static\n * users to get an ISignaler without a custom DO. Where possible, consumers should instead\n * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport class SignalManager extends DataObject<{}, undefined, IErrorEvent> implements EventEmitter, ISignaler {\n private _manager: Signaler | undefined;\n private get manager(): Signaler {\n assert(this._manager !== undefined, 0x24b /* \"internal signaler should be defined\" */);\n return this._manager;\n }\n\n public static get Name() { return \"@fluid-example/signal-manager\"; }\n\n public static readonly factory = new DataObjectFactory<SignalManager, undefined, undefined, IErrorEvent>\n (\n SignalManager.Name,\n SignalManager,\n [],\n {},\n );\n\n protected async hasInitialized() {\n this._manager = new Signaler(this.runtime);\n this.manager.on(\"error\", (error) => {\n this.emit(\"error\", error);\n });\n }\n\n // ISignaler methods Note these are all passthroughs\n\n public onSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.onSignal(signalName, listener);\n return this;\n }\n\n public offSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.offSignal(signalName, listener);\n return this;\n }\n\n public submitSignal(\n signalName: string,\n payload?: Jsonable,\n ) {\n this.manager.submitSignal(signalName, payload);\n }\n\n public onBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.onBroadcastRequested(signalName, listener);\n return this;\n }\n\n public offBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.offBroadcastRequested(signalName, listener);\n return this;\n }\n\n public requestBroadcast(\n signalName: string,\n payload?: Jsonable,\n ) {\n this.manager.requestBroadcast(signalName, payload);\n }\n}\n"]}
package/lib/index.d.ts CHANGED
@@ -3,4 +3,5 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export * from "./kvpair";
6
+ export * from "./signalManager";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC"}
package/lib/index.js CHANGED
@@ -3,4 +3,5 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export * from "./kvpair";
6
+ export * from "./signalManager";
6
7
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./kvpair\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./kvpair\";\nexport * from \"./signalManager\";\n"]}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export * from "./signalManager";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signalManager/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export * from "./signalManager";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signalManager/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./signalManager\";\n"]}
@@ -0,0 +1,131 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /// <reference types="node" />
6
+ import { EventEmitter } from "events";
7
+ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
8
+ import { IErrorEvent } from "@fluidframework/common-definitions";
9
+ import { TypedEventEmitter } from "@fluidframework/common-utils";
10
+ import { Jsonable } from "@fluidframework/datastore-definitions";
11
+ import { IInboundSignalMessage } from "@fluidframework/runtime-definitions";
12
+ export declare type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void;
13
+ /**
14
+ * ISignaler defines an interface for working with signals that is similar to the more common
15
+ * eventing patterns of EventEmitter. In addition to sending and responding to signals, it
16
+ * provides explicit methods around signal requests to other connected clients.
17
+ */
18
+ export interface ISignaler {
19
+ /**
20
+ * Adds a listener for the specified signal. It behaves in the same way as EventEmitter's `on`
21
+ * method regarding multiple registrations, callback order, etc.
22
+ * @param signalName - The name of the signal
23
+ * @param listener - The callback signal handler to add
24
+ * @returns This ISignaler
25
+ */
26
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
27
+ /**
28
+ * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's
29
+ * `off` method regarding multiple registrations, removal order, etc.
30
+ * @param signalName - The name of the signal
31
+ * @param listener - The callback signal handler to remove
32
+ * @returns This ISignaler
33
+ */
34
+ offSignal(signalName: string, listener: SignalListener | ((message: any) => void)): ISignaler;
35
+ /**
36
+ * Send a signal with payload to its connected listeners.
37
+ * @param signalName - The name of the signal
38
+ * @param payload - The data to send with the signal
39
+ */
40
+ submitSignal(signalName: string, payload?: Jsonable): any;
41
+ /**
42
+ * Adds a listener for a broadcast request. The listener is called when a client calls
43
+ * `requestBroadcast` for that signal. It behaves in the same way as EventEmitter's `on`
44
+ * method regarding multiple registrations, callback order, etc.
45
+ * @param signalName - The signal for which broadcast is requested
46
+ * @param listener - The callback for the broadcast request to add
47
+ * @returns This ISignaler
48
+ */
49
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
50
+ /**
51
+ * Remove a listener for a broadcast request. It behaves in the same way as EventEmitter's
52
+ * `off` method regarding multiple registrations, removal order, etc.
53
+ * @param signalName - The signal for which broadcast is requested
54
+ * @param listener - The callback for the broadcast request to remove
55
+ * @returns This ISignaler
56
+ */
57
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
58
+ /**
59
+ * Request broadcast of a signal from other connected clients. Other clients must have
60
+ * registered to respond to broadcast requests using the `onBroadcastRequested` method.
61
+ * @param signalName - The signal for which broadcast is requested
62
+ * @param payload - A payload to send with the broadcast request
63
+ */
64
+ requestBroadcast(signalName: string, payload?: Jsonable): any;
65
+ }
66
+ /**
67
+ * Duck type of something that provides the expected signalling functionality:
68
+ * A way to verify we can signal, a way to send a signal, and a way to listen for incoming signals
69
+ */
70
+ export interface IRuntimeSignaler {
71
+ connected: boolean;
72
+ on(event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void): any;
73
+ submitSignal(type: string, content: any): void;
74
+ }
75
+ /**
76
+ * Note: currently experimental and under development
77
+ *
78
+ * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime
79
+ * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can
80
+ * then be used in place of the original signaler. It uses a separate internal EventEmitter to
81
+ * manage callbacks, and thus will reflect that behavior with regards to callback registration and
82
+ * deregistration.
83
+ */
84
+ export declare class Signaler extends TypedEventEmitter<IErrorEvent> implements ISignaler {
85
+ /**
86
+ * Object to wrap that can submit and listen to signals
87
+ */
88
+ private readonly signaler;
89
+ private readonly emitter;
90
+ private readonly managerId;
91
+ constructor(
92
+ /**
93
+ * Object to wrap that can submit and listen to signals
94
+ */
95
+ signaler: IRuntimeSignaler,
96
+ /**
97
+ * Optional id to assign to this manager that will be attached to
98
+ * signal names. Useful to avoid collisions if there are multiple
99
+ * signal users at the Container level
100
+ */
101
+ managerId?: string);
102
+ private getManagerSignalName;
103
+ private getBroadcastSignalName;
104
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
105
+ offSignal(signalName: string, listener: SignalListener): ISignaler;
106
+ submitSignal(signalName: string, payload?: Jsonable): void;
107
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
108
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
109
+ requestBroadcast(signalName: string, payload?: Jsonable): void;
110
+ }
111
+ /**
112
+ * Note: currently experimental and under development
113
+ *
114
+ * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static
115
+ * users to get an ISignaler without a custom DO. Where possible, consumers should instead
116
+ * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.
117
+ */
118
+ export declare class SignalManager extends DataObject<{}, undefined, IErrorEvent> implements EventEmitter, ISignaler {
119
+ private _manager;
120
+ private get manager();
121
+ static get Name(): string;
122
+ static readonly factory: DataObjectFactory<SignalManager, undefined, undefined, IErrorEvent>;
123
+ protected hasInitialized(): Promise<void>;
124
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
125
+ offSignal(signalName: string, listener: SignalListener): ISignaler;
126
+ submitSignal(signalName: string, payload?: Jsonable): void;
127
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
128
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
129
+ requestBroadcast(signalName: string, payload?: Jsonable): void;
130
+ }
131
+ //# sourceMappingURL=signalManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalManager.d.ts","sourceRoot":"","sources":["../../src/signalManager/signalManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { EventEmitter } from "events";
6
+ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
7
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
8
+ /**
9
+ * Note: currently experimental and under development
10
+ *
11
+ * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime
12
+ * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can
13
+ * then be used in place of the original signaler. It uses a separate internal EventEmitter to
14
+ * manage callbacks, and thus will reflect that behavior with regards to callback registration and
15
+ * deregistration.
16
+ */
17
+ export class Signaler extends TypedEventEmitter {
18
+ constructor(
19
+ /**
20
+ * Object to wrap that can submit and listen to signals
21
+ */
22
+ signaler,
23
+ /**
24
+ * Optional id to assign to this manager that will be attached to
25
+ * signal names. Useful to avoid collisions if there are multiple
26
+ * signal users at the Container level
27
+ */
28
+ managerId) {
29
+ super();
30
+ this.signaler = signaler;
31
+ this.emitter = new EventEmitter();
32
+ this.emitter.on("error", (error) => {
33
+ this.emit("error", error);
34
+ });
35
+ this.managerId = managerId ? `#${managerId}` : undefined;
36
+ this.signaler.on("signal", (message, local) => {
37
+ const clientId = message.clientId;
38
+ // Only call listeners when the runtime is connected and if the signal has an
39
+ // identifiable sender clientId. The listener is responsible for deciding how
40
+ // it wants to handle local/remote signals
41
+ // eslint-disable-next-line no-null/no-null
42
+ if (this.signaler.connected && clientId !== null) {
43
+ this.emitter.emit(message.type, clientId, local, message.content);
44
+ }
45
+ });
46
+ }
47
+ getManagerSignalName(signalName) {
48
+ return this.managerId ? `${signalName}${this.managerId}` : signalName;
49
+ }
50
+ getBroadcastSignalName(signalName) {
51
+ return `${signalName}#req`;
52
+ }
53
+ // ISignaler methods
54
+ onSignal(signalName, listener) {
55
+ const managerSignalName = this.getManagerSignalName(signalName);
56
+ this.emitter.on(managerSignalName, listener);
57
+ return this;
58
+ }
59
+ offSignal(signalName, listener) {
60
+ const managerSignalName = this.getManagerSignalName(signalName);
61
+ this.emitter.off(managerSignalName, listener);
62
+ return this;
63
+ }
64
+ submitSignal(signalName, payload) {
65
+ const managerSignalName = this.getManagerSignalName(signalName);
66
+ if (this.signaler.connected) {
67
+ this.signaler.submitSignal(managerSignalName, payload);
68
+ }
69
+ }
70
+ onBroadcastRequested(signalName, listener) {
71
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
72
+ return this.onSignal(broadcastSignalName, listener);
73
+ }
74
+ offBroadcastRequested(signalName, listener) {
75
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
76
+ return this.offSignal(broadcastSignalName, listener);
77
+ }
78
+ requestBroadcast(signalName, payload) {
79
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
80
+ this.submitSignal(broadcastSignalName, payload);
81
+ }
82
+ }
83
+ /**
84
+ * Note: currently experimental and under development
85
+ *
86
+ * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static
87
+ * users to get an ISignaler without a custom DO. Where possible, consumers should instead
88
+ * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.
89
+ */
90
+ // eslint-disable-next-line @typescript-eslint/ban-types
91
+ export class SignalManager extends DataObject {
92
+ get manager() {
93
+ assert(this._manager !== undefined, 0x24b /* "internal signaler should be defined" */);
94
+ return this._manager;
95
+ }
96
+ static get Name() { return "@fluid-example/signal-manager"; }
97
+ async hasInitialized() {
98
+ this._manager = new Signaler(this.runtime);
99
+ this.manager.on("error", (error) => {
100
+ this.emit("error", error);
101
+ });
102
+ }
103
+ // ISignaler methods Note these are all passthroughs
104
+ onSignal(signalName, listener) {
105
+ this.manager.onSignal(signalName, listener);
106
+ return this;
107
+ }
108
+ offSignal(signalName, listener) {
109
+ this.manager.offSignal(signalName, listener);
110
+ return this;
111
+ }
112
+ submitSignal(signalName, payload) {
113
+ this.manager.submitSignal(signalName, payload);
114
+ }
115
+ onBroadcastRequested(signalName, listener) {
116
+ this.manager.onBroadcastRequested(signalName, listener);
117
+ return this;
118
+ }
119
+ offBroadcastRequested(signalName, listener) {
120
+ this.manager.offBroadcastRequested(signalName, listener);
121
+ return this;
122
+ }
123
+ requestBroadcast(signalName, payload) {
124
+ this.manager.requestBroadcast(signalName, payload);
125
+ }
126
+ }
127
+ SignalManager.factory = new DataObjectFactory(SignalManager.Name, SignalManager, [], {});
128
+ //# sourceMappingURL=signalManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signalManager.js","sourceRoot":"","sources":["../../src/signalManager/signalManager.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 { EventEmitter } from \"events\";\nimport { DataObject, DataObjectFactory } from \"@fluidframework/aqueduct\";\nimport { IErrorEvent } from \"@fluidframework/common-definitions\";\nimport { assert, TypedEventEmitter } from \"@fluidframework/common-utils\";\nimport { Jsonable } from \"@fluidframework/datastore-definitions\";\nimport { IInboundSignalMessage } from \"@fluidframework/runtime-definitions\";\n\n// TODO:\n// add way to mark with current sequence number for ordering signals relative to ops\n// throttling and batching\n\nexport type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void;\n\n/**\n * ISignaler defines an interface for working with signals that is similar to the more common\n * eventing patterns of EventEmitter. In addition to sending and responding to signals, it\n * provides explicit methods around signal requests to other connected clients.\n */\nexport interface ISignaler {\n /**\n * Adds a listener for the specified signal. It behaves in the same way as EventEmitter's `on`\n * method regarding multiple registrations, callback order, etc.\n * @param signalName - The name of the signal\n * @param listener - The callback signal handler to add\n * @returns This ISignaler\n */\n onSignal(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's\n * `off` method regarding multiple registrations, removal order, etc.\n * @param signalName - The name of the signal\n * @param listener - The callback signal handler to remove\n * @returns This ISignaler\n */\n offSignal(signalName: string, listener: SignalListener | ((message: any) => void)): ISignaler;\n /**\n * Send a signal with payload to its connected listeners.\n * @param signalName - The name of the signal\n * @param payload - The data to send with the signal\n */\n submitSignal(signalName: string, payload?: Jsonable);\n\n /**\n * Adds a listener for a broadcast request. The listener is called when a client calls\n * `requestBroadcast` for that signal. It behaves in the same way as EventEmitter's `on`\n * method regarding multiple registrations, callback order, etc.\n * @param signalName - The signal for which broadcast is requested\n * @param listener - The callback for the broadcast request to add\n * @returns This ISignaler\n */\n onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Remove a listener for a broadcast request. It behaves in the same way as EventEmitter's\n * `off` method regarding multiple registrations, removal order, etc.\n * @param signalName - The signal for which broadcast is requested\n * @param listener - The callback for the broadcast request to remove\n * @returns This ISignaler\n */\n offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;\n /**\n * Request broadcast of a signal from other connected clients. Other clients must have\n * registered to respond to broadcast requests using the `onBroadcastRequested` method.\n * @param signalName - The signal for which broadcast is requested\n * @param payload - A payload to send with the broadcast request\n */\n requestBroadcast(signalName: string, payload?: Jsonable);\n}\n\n/**\n * Duck type of something that provides the expected signalling functionality:\n * A way to verify we can signal, a way to send a signal, and a way to listen for incoming signals\n */\nexport interface IRuntimeSignaler {\n connected: boolean;\n on(event: \"signal\", listener: (message: IInboundSignalMessage, local: boolean) => void);\n submitSignal(type: string, content: any): void;\n}\n\n/**\n * Note: currently experimental and under development\n *\n * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime\n * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can\n * then be used in place of the original signaler. It uses a separate internal EventEmitter to\n * manage callbacks, and thus will reflect that behavior with regards to callback registration and\n * deregistration.\n */\nexport class Signaler extends TypedEventEmitter<IErrorEvent> implements ISignaler {\n private readonly emitter = new EventEmitter();\n\n private readonly managerId: string | undefined;\n\n constructor(\n /**\n * Object to wrap that can submit and listen to signals\n */\n private readonly signaler: IRuntimeSignaler,\n /**\n * Optional id to assign to this manager that will be attached to\n * signal names. Useful to avoid collisions if there are multiple\n * signal users at the Container level\n */\n managerId?: string,\n ) {\n super();\n this.emitter.on(\"error\", (error) => {\n this.emit(\"error\", error);\n });\n this.managerId = managerId ? `#${managerId}` : undefined;\n this.signaler.on(\"signal\", (message: IInboundSignalMessage, local: boolean) => {\n const clientId = message.clientId;\n // Only call listeners when the runtime is connected and if the signal has an\n // identifiable sender clientId. The listener is responsible for deciding how\n // it wants to handle local/remote signals\n // eslint-disable-next-line no-null/no-null\n if (this.signaler.connected && clientId !== null) {\n this.emitter.emit(message.type, clientId, local, message.content);\n }\n });\n }\n\n private getManagerSignalName(signalName: string): string {\n return this.managerId ? `${signalName}${this.managerId}` : signalName;\n }\n\n private getBroadcastSignalName(signalName: string): string {\n return `${signalName}#req`;\n }\n\n // ISignaler methods\n\n public onSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const managerSignalName = this.getManagerSignalName(signalName);\n this.emitter.on(managerSignalName, listener);\n return this;\n }\n\n public offSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const managerSignalName = this.getManagerSignalName(signalName);\n this.emitter.off(managerSignalName, listener);\n return this;\n }\n\n public submitSignal(\n signalName: string,\n payload?: Jsonable,\n ) {\n const managerSignalName = this.getManagerSignalName(signalName);\n if (this.signaler.connected) {\n this.signaler.submitSignal(managerSignalName, payload);\n }\n }\n\n public onBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n return this.onSignal(broadcastSignalName, listener);\n }\n\n public offBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n return this.offSignal(broadcastSignalName, listener);\n }\n\n public requestBroadcast(\n signalName: string,\n payload?: Jsonable,\n ) {\n const broadcastSignalName = this.getBroadcastSignalName(signalName);\n this.submitSignal(broadcastSignalName, payload);\n }\n}\n\n/**\n * Note: currently experimental and under development\n *\n * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static\n * users to get an ISignaler without a custom DO. Where possible, consumers should instead\n * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport class SignalManager extends DataObject<{}, undefined, IErrorEvent> implements EventEmitter, ISignaler {\n private _manager: Signaler | undefined;\n private get manager(): Signaler {\n assert(this._manager !== undefined, 0x24b /* \"internal signaler should be defined\" */);\n return this._manager;\n }\n\n public static get Name() { return \"@fluid-example/signal-manager\"; }\n\n public static readonly factory = new DataObjectFactory<SignalManager, undefined, undefined, IErrorEvent>\n (\n SignalManager.Name,\n SignalManager,\n [],\n {},\n );\n\n protected async hasInitialized() {\n this._manager = new Signaler(this.runtime);\n this.manager.on(\"error\", (error) => {\n this.emit(\"error\", error);\n });\n }\n\n // ISignaler methods Note these are all passthroughs\n\n public onSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.onSignal(signalName, listener);\n return this;\n }\n\n public offSignal(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.offSignal(signalName, listener);\n return this;\n }\n\n public submitSignal(\n signalName: string,\n payload?: Jsonable,\n ) {\n this.manager.submitSignal(signalName, payload);\n }\n\n public onBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.onBroadcastRequested(signalName, listener);\n return this;\n }\n\n public offBroadcastRequested(\n signalName: string,\n listener: SignalListener,\n ): ISignaler {\n this.manager.offBroadcastRequested(signalName, listener);\n return this;\n }\n\n public requestBroadcast(\n signalName: string,\n payload?: Jsonable,\n ) {\n this.manager.requestBroadcast(signalName, payload);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-experimental/data-objects",
3
- "version": "0.47.1",
3
+ "version": "0.48.1",
4
4
  "description": "A collection of ready to use Fluid Data Objects",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": "https://github.com/microsoft/FluidFramework",
@@ -27,9 +27,12 @@
27
27
  "tsfmt:fix": "tsfmt --replace"
28
28
  },
29
29
  "dependencies": {
30
- "@fluidframework/aqueduct": "^0.47.1",
30
+ "@fluidframework/aqueduct": "^0.48.1",
31
31
  "@fluidframework/common-definitions": "^0.20.1",
32
- "@fluidframework/map": "^0.47.1"
32
+ "@fluidframework/common-utils": "^0.32.1",
33
+ "@fluidframework/datastore-definitions": "^0.48.1",
34
+ "@fluidframework/map": "^0.48.1",
35
+ "@fluidframework/runtime-definitions": "^0.48.1"
33
36
  },
34
37
  "devDependencies": {
35
38
  "@fluidframework/build-common": "^0.23.0",
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@
4
4
  */
5
5
 
6
6
  export * from "./kvpair";
7
+ export * from "./signalManager";
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export * from "./signalManager";
@@ -0,0 +1,269 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { EventEmitter } from "events";
7
+ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
8
+ import { IErrorEvent } from "@fluidframework/common-definitions";
9
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
10
+ import { Jsonable } from "@fluidframework/datastore-definitions";
11
+ import { IInboundSignalMessage } from "@fluidframework/runtime-definitions";
12
+
13
+ // TODO:
14
+ // add way to mark with current sequence number for ordering signals relative to ops
15
+ // throttling and batching
16
+
17
+ export type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void;
18
+
19
+ /**
20
+ * ISignaler defines an interface for working with signals that is similar to the more common
21
+ * eventing patterns of EventEmitter. In addition to sending and responding to signals, it
22
+ * provides explicit methods around signal requests to other connected clients.
23
+ */
24
+ export interface ISignaler {
25
+ /**
26
+ * Adds a listener for the specified signal. It behaves in the same way as EventEmitter's `on`
27
+ * method regarding multiple registrations, callback order, etc.
28
+ * @param signalName - The name of the signal
29
+ * @param listener - The callback signal handler to add
30
+ * @returns This ISignaler
31
+ */
32
+ onSignal(signalName: string, listener: SignalListener): ISignaler;
33
+ /**
34
+ * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's
35
+ * `off` method regarding multiple registrations, removal order, etc.
36
+ * @param signalName - The name of the signal
37
+ * @param listener - The callback signal handler to remove
38
+ * @returns This ISignaler
39
+ */
40
+ offSignal(signalName: string, listener: SignalListener | ((message: any) => void)): ISignaler;
41
+ /**
42
+ * Send a signal with payload to its connected listeners.
43
+ * @param signalName - The name of the signal
44
+ * @param payload - The data to send with the signal
45
+ */
46
+ submitSignal(signalName: string, payload?: Jsonable);
47
+
48
+ /**
49
+ * Adds a listener for a broadcast request. The listener is called when a client calls
50
+ * `requestBroadcast` for that signal. It behaves in the same way as EventEmitter's `on`
51
+ * method regarding multiple registrations, callback order, etc.
52
+ * @param signalName - The signal for which broadcast is requested
53
+ * @param listener - The callback for the broadcast request to add
54
+ * @returns This ISignaler
55
+ */
56
+ onBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
57
+ /**
58
+ * Remove a listener for a broadcast request. It behaves in the same way as EventEmitter's
59
+ * `off` method regarding multiple registrations, removal order, etc.
60
+ * @param signalName - The signal for which broadcast is requested
61
+ * @param listener - The callback for the broadcast request to remove
62
+ * @returns This ISignaler
63
+ */
64
+ offBroadcastRequested(signalName: string, listener: SignalListener): ISignaler;
65
+ /**
66
+ * Request broadcast of a signal from other connected clients. Other clients must have
67
+ * registered to respond to broadcast requests using the `onBroadcastRequested` method.
68
+ * @param signalName - The signal for which broadcast is requested
69
+ * @param payload - A payload to send with the broadcast request
70
+ */
71
+ requestBroadcast(signalName: string, payload?: Jsonable);
72
+ }
73
+
74
+ /**
75
+ * Duck type of something that provides the expected signalling functionality:
76
+ * A way to verify we can signal, a way to send a signal, and a way to listen for incoming signals
77
+ */
78
+ export interface IRuntimeSignaler {
79
+ connected: boolean;
80
+ on(event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void);
81
+ submitSignal(type: string, content: any): void;
82
+ }
83
+
84
+ /**
85
+ * Note: currently experimental and under development
86
+ *
87
+ * Helper class to assist common scenarios around working with signals. Signaler wraps a runtime
88
+ * object with signaling functionality (e.g. ContainerRuntime or FluidDataStoreRuntime) and can
89
+ * then be used in place of the original signaler. It uses a separate internal EventEmitter to
90
+ * manage callbacks, and thus will reflect that behavior with regards to callback registration and
91
+ * deregistration.
92
+ */
93
+ export class Signaler extends TypedEventEmitter<IErrorEvent> implements ISignaler {
94
+ private readonly emitter = new EventEmitter();
95
+
96
+ private readonly managerId: string | undefined;
97
+
98
+ constructor(
99
+ /**
100
+ * Object to wrap that can submit and listen to signals
101
+ */
102
+ private readonly signaler: IRuntimeSignaler,
103
+ /**
104
+ * Optional id to assign to this manager that will be attached to
105
+ * signal names. Useful to avoid collisions if there are multiple
106
+ * signal users at the Container level
107
+ */
108
+ managerId?: string,
109
+ ) {
110
+ super();
111
+ this.emitter.on("error", (error) => {
112
+ this.emit("error", error);
113
+ });
114
+ this.managerId = managerId ? `#${managerId}` : undefined;
115
+ this.signaler.on("signal", (message: IInboundSignalMessage, local: boolean) => {
116
+ const clientId = message.clientId;
117
+ // Only call listeners when the runtime is connected and if the signal has an
118
+ // identifiable sender clientId. The listener is responsible for deciding how
119
+ // it wants to handle local/remote signals
120
+ // eslint-disable-next-line no-null/no-null
121
+ if (this.signaler.connected && clientId !== null) {
122
+ this.emitter.emit(message.type, clientId, local, message.content);
123
+ }
124
+ });
125
+ }
126
+
127
+ private getManagerSignalName(signalName: string): string {
128
+ return this.managerId ? `${signalName}${this.managerId}` : signalName;
129
+ }
130
+
131
+ private getBroadcastSignalName(signalName: string): string {
132
+ return `${signalName}#req`;
133
+ }
134
+
135
+ // ISignaler methods
136
+
137
+ public onSignal(
138
+ signalName: string,
139
+ listener: SignalListener,
140
+ ): ISignaler {
141
+ const managerSignalName = this.getManagerSignalName(signalName);
142
+ this.emitter.on(managerSignalName, listener);
143
+ return this;
144
+ }
145
+
146
+ public offSignal(
147
+ signalName: string,
148
+ listener: SignalListener,
149
+ ): ISignaler {
150
+ const managerSignalName = this.getManagerSignalName(signalName);
151
+ this.emitter.off(managerSignalName, listener);
152
+ return this;
153
+ }
154
+
155
+ public submitSignal(
156
+ signalName: string,
157
+ payload?: Jsonable,
158
+ ) {
159
+ const managerSignalName = this.getManagerSignalName(signalName);
160
+ if (this.signaler.connected) {
161
+ this.signaler.submitSignal(managerSignalName, payload);
162
+ }
163
+ }
164
+
165
+ public onBroadcastRequested(
166
+ signalName: string,
167
+ listener: SignalListener,
168
+ ): ISignaler {
169
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
170
+ return this.onSignal(broadcastSignalName, listener);
171
+ }
172
+
173
+ public offBroadcastRequested(
174
+ signalName: string,
175
+ listener: SignalListener,
176
+ ): ISignaler {
177
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
178
+ return this.offSignal(broadcastSignalName, listener);
179
+ }
180
+
181
+ public requestBroadcast(
182
+ signalName: string,
183
+ payload?: Jsonable,
184
+ ) {
185
+ const broadcastSignalName = this.getBroadcastSignalName(signalName);
186
+ this.submitSignal(broadcastSignalName, payload);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Note: currently experimental and under development
192
+ *
193
+ * DataObject implementation of ISignaler for fluid-static plug-and-play. Allows fluid-static
194
+ * users to get an ISignaler without a custom DO. Where possible, consumers should instead
195
+ * create a Signaler themselves instead of using the DO wrapper to avoid the DO overhead.
196
+ */
197
+ // eslint-disable-next-line @typescript-eslint/ban-types
198
+ export class SignalManager extends DataObject<{}, undefined, IErrorEvent> implements EventEmitter, ISignaler {
199
+ private _manager: Signaler | undefined;
200
+ private get manager(): Signaler {
201
+ assert(this._manager !== undefined, 0x24b /* "internal signaler should be defined" */);
202
+ return this._manager;
203
+ }
204
+
205
+ public static get Name() { return "@fluid-example/signal-manager"; }
206
+
207
+ public static readonly factory = new DataObjectFactory<SignalManager, undefined, undefined, IErrorEvent>
208
+ (
209
+ SignalManager.Name,
210
+ SignalManager,
211
+ [],
212
+ {},
213
+ );
214
+
215
+ protected async hasInitialized() {
216
+ this._manager = new Signaler(this.runtime);
217
+ this.manager.on("error", (error) => {
218
+ this.emit("error", error);
219
+ });
220
+ }
221
+
222
+ // ISignaler methods Note these are all passthroughs
223
+
224
+ public onSignal(
225
+ signalName: string,
226
+ listener: SignalListener,
227
+ ): ISignaler {
228
+ this.manager.onSignal(signalName, listener);
229
+ return this;
230
+ }
231
+
232
+ public offSignal(
233
+ signalName: string,
234
+ listener: SignalListener,
235
+ ): ISignaler {
236
+ this.manager.offSignal(signalName, listener);
237
+ return this;
238
+ }
239
+
240
+ public submitSignal(
241
+ signalName: string,
242
+ payload?: Jsonable,
243
+ ) {
244
+ this.manager.submitSignal(signalName, payload);
245
+ }
246
+
247
+ public onBroadcastRequested(
248
+ signalName: string,
249
+ listener: SignalListener,
250
+ ): ISignaler {
251
+ this.manager.onBroadcastRequested(signalName, listener);
252
+ return this;
253
+ }
254
+
255
+ public offBroadcastRequested(
256
+ signalName: string,
257
+ listener: SignalListener,
258
+ ): ISignaler {
259
+ this.manager.offBroadcastRequested(signalName, listener);
260
+ return this;
261
+ }
262
+
263
+ public requestBroadcast(
264
+ signalName: string,
265
+ payload?: Jsonable,
266
+ ) {
267
+ this.manager.requestBroadcast(signalName, payload);
268
+ }
269
+ }