@rbxts/tether 1.0.34 → 1.1.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/README.md CHANGED
@@ -1,19 +1,24 @@
1
1
  # Tether
2
- A message-based networking solution for Roblox with automatic binary serialization
2
+ A message-based networking solution for Roblox with automatic binary serialization.
3
+
4
+ > [!CAUTION]
5
+ > Depends on `rbxts-transformer-flamework`!
6
+
7
+ ## Usage
3
8
 
4
9
  ### In `shared/messaging.ts`
5
10
  ```ts
6
- import { DataType } from "@rbxts/flamework-binary-serializer";
11
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
12
+ import { MessageEmitter } from "@rbxts/tether";
7
13
 
8
- export const messageEmitter = MessageEmitter.create<MessageData>();
9
- messageEmitter.initialize();
14
+ export const messaging = MessageEmitter.create<MessageData>();
10
15
 
11
16
  export const enum Message {
12
- TEST
17
+ Test
13
18
  }
14
19
 
15
20
  export interface MessageData {
16
- [Message.TEST]: {
21
+ [Message.Test]: {
17
22
  readonly foo: string;
18
23
  readonly n: DataType.u8;
19
24
  };
@@ -21,23 +26,92 @@ export interface MessageData {
21
26
  ```
22
27
 
23
28
  > [!CAUTION]
24
- > Every single message kind must implement an interface for it's data (in the example that would be the object with the `foo` and `bar` fields) as well as call `MessageEmitter.addSerializer()`. Messages will not work if you don't do this.
29
+ > Every single message kind must implement an interface for it's data (in the example that would be the object with the `foo` and `bar` fields). Message serialization (as well as your message itself) will not work if you don't do this.
25
30
 
26
31
  ### Server
27
32
  ```ts
28
- import { Message, messageEmitter } from "shared/messaging";
33
+ import { Message, messaging } from "shared/messaging";
29
34
 
30
- messageEmitter.onServerMessage(Message.TEST, (player, data) => {
35
+ messaging.onServerMessage(Message.Test, (player, data) => {
31
36
  print(player, "sent data:", data);
32
37
  });
33
38
  ```
34
39
 
35
40
  ### Client
36
41
  ```ts
37
- import { Message, messageEmitter } from "shared/messaging";
42
+ import { Message, messaging } from "shared/messaging";
38
43
 
39
- messageEmitter.emitServer(Message.TEST, {
44
+ messaging.emitServer(Message.Test, {
40
45
  foo: "bar",
41
46
  n: 69
42
47
  });
48
+ ```
49
+
50
+ ## Middleware
51
+ Drop, delay, or modify requests
52
+
53
+ ### Creating middleware
54
+
55
+ #### Client
56
+ ```ts
57
+ import type { ClientMiddleware } from "@rbxts/tether";
58
+
59
+ export function logClient(): ClientMiddleware {
60
+ return message => (player, data) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, data);
61
+ }
62
+ ```
63
+
64
+ #### Server
65
+ ```ts
66
+ import type { ServerMiddleware } from "@rbxts/tether";
67
+
68
+ export function logServer(): ServerMiddleware {
69
+ return message => data => print(`[LOG]: Sent message '${message}' to server with data:`, data);
70
+ }
71
+ ```
72
+
73
+ #### Shared
74
+ ```ts
75
+ import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
76
+
77
+ export function rateLimit(interval: number): SharedMiddleware {
78
+ let lastRequest = 0;
79
+
80
+ return message => // message attempting to be sent
81
+ () => { // no data/player - it's a shared middleware
82
+ if (os.clock() - lastRequest < interval)
83
+ return DropRequest;
84
+
85
+ lastRequest = os.clock();
86
+ };
87
+ }
88
+ ```
89
+
90
+ ### Using middleware
91
+ ```ts
92
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
93
+ import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
94
+
95
+ export const messaging = MessageEmitter.create<MessageData>();
96
+ messaging.middleware
97
+ // only allows requests to the server every 5 seconds,
98
+ // drops any requests that occur within 5 seconds of each other
99
+ .useServer(Message.Test, [BuiltinMiddlewares.rateLimit(5)])
100
+ // automatically validates that the data sent through the remote matches
101
+ // the data associated with the message at runtime using type guards
102
+ .useServer(Message.Test, [BuiltinMiddlewares.validateServer()])
103
+ // logs every message fired
104
+ .useServerGlobal([logServer()]);
105
+ .useClientGlobal([logClient()]);
106
+
107
+ export const enum Message {
108
+ Test
109
+ }
110
+
111
+ export interface MessageData {
112
+ [Message.Test]: {
113
+ readonly foo: string;
114
+ readonly n: DataType.u8;
115
+ };
116
+ }
43
117
  ```
@@ -0,0 +1,33 @@
1
+ import { Modding } from "@flamework/core";
2
+ import { type SharedMiddleware, type ServerMiddleware, type ClientMiddleware } from "./middleware";
3
+ type Guard<T> = (value: unknown) => value is T;
4
+ export declare namespace BuiltinMiddlewares {
5
+ /**
6
+ * Creates a shared per-message middleware that will drop any message that occurs within the given interval of the previous message
7
+ *
8
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request
9
+ * @returns A middleware that will drop any message that occurs within the given interval
10
+ */
11
+ function rateLimit(interval: number): SharedMiddleware;
12
+ /**
13
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided)
14
+ *
15
+ * If the guard fails, the middleware will drop the message
16
+ *
17
+ * @param guard The guard to use to validate the data
18
+ * @returns A server middleware that validates the data with the given guard
19
+ * @metadata macro
20
+ */
21
+ function validateServer<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ServerMiddleware<T>;
22
+ /**
23
+ * Creates a client middleware that validates the data with the given guard (or a generated guard if none was provided)
24
+ *
25
+ * If the guard fails, the middleware will drop the message
26
+ *
27
+ * @param guard The guard to use to validate the data.
28
+ * @returns A client middleware that validates the data with the given guard.
29
+ * @metadata macro
30
+ */
31
+ function validateClient<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ClientMiddleware<T>;
32
+ }
33
+ export {};
@@ -0,0 +1,93 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
4
+ local noOp = function()
5
+ return function() end
6
+ end
7
+ local validationGuardGenerationFailed = function(context)
8
+ return `[@rbxts/tether]: Failed to generate guard for validate{context}<T> builtin middleware - skipping validation`
9
+ end
10
+ local guardFailed = function(message)
11
+ return `[@rbxts/tether]: Type validation failed for message '{tostring(message)}' - check your sent data`
12
+ end
13
+ local BuiltinMiddlewares = {}
14
+ do
15
+ local _container = BuiltinMiddlewares
16
+ --[[
17
+ *
18
+ * Creates a shared per-message middleware that will drop any message that occurs within the given interval of the previous message
19
+ *
20
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request
21
+ * @returns A middleware that will drop any message that occurs within the given interval
22
+
23
+ ]]
24
+ local function rateLimit(interval)
25
+ local lastRequest = 0
26
+ return function()
27
+ return function()
28
+ if os.clock() - lastRequest < interval then
29
+ return DropRequest
30
+ end
31
+ lastRequest = os.clock()
32
+ end
33
+ end
34
+ end
35
+ _container.rateLimit = rateLimit
36
+ --[[
37
+ *
38
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided)
39
+ *
40
+ * If the guard fails, the middleware will drop the message
41
+ *
42
+ * @param guard The guard to use to validate the data
43
+ * @returns A server middleware that validates the data with the given guard
44
+ * @metadata macro
45
+
46
+ ]]
47
+ local function validateServer(guard)
48
+ if guard == nil then
49
+ warn(validationGuardGenerationFailed("Server"))
50
+ return noOp
51
+ end
52
+ return function(message)
53
+ return function(data)
54
+ if guard(data) then
55
+ return nil
56
+ end
57
+ warn(guardFailed(message))
58
+ return DropRequest
59
+ end
60
+ end
61
+ end
62
+ _container.validateServer = validateServer
63
+ --[[
64
+ *
65
+ * Creates a client middleware that validates the data with the given guard (or a generated guard if none was provided)
66
+ *
67
+ * If the guard fails, the middleware will drop the message
68
+ *
69
+ * @param guard The guard to use to validate the data.
70
+ * @returns A client middleware that validates the data with the given guard.
71
+ * @metadata macro
72
+
73
+ ]]
74
+ local function validateClient(guard)
75
+ if guard == nil then
76
+ warn(validationGuardGenerationFailed("Client"))
77
+ return noOp
78
+ end
79
+ return function(message)
80
+ return function(player, data)
81
+ if guard(data) then
82
+ return nil
83
+ end
84
+ warn(guardFailed(message))
85
+ return DropRequest
86
+ end
87
+ end
88
+ end
89
+ _container.validateClient = validateClient
90
+ end
91
+ return {
92
+ BuiltinMiddlewares = BuiltinMiddlewares,
93
+ }
package/out/index.d.ts CHANGED
@@ -1,29 +1,3 @@
1
- import { Modding } from "@flamework/core";
2
- import { type SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
- type ClientMessageCallback<T = unknown> = (data: T) => void;
4
- type ServerMessageCallback<T = unknown> = (player: Player, data: T) => void;
5
- export declare class MessageEmitter<MessageData> {
6
- private readonly clientCallbacks;
7
- private readonly serverCallbacks;
8
- private readonly serializers;
9
- private readonly serverEvents;
10
- private readonly clientEvents;
11
- /** @metadata macro */
12
- static create<MessageData>(metaForEachMessage?: Modding.Many<{
13
- [Kind in keyof MessageData]: Modding.Many<SerializerMetadata<MessageData[Kind]>>;
14
- }>): MessageEmitter<MessageData>;
15
- private constructor();
16
- /** @metadata macro */
17
- addSerializer<Kind extends keyof MessageData>(message: Kind, meta?: Modding.Many<SerializerMetadata<MessageData[Kind]>>): void;
18
- initialize(): RBXScriptConnection;
19
- onServerMessage<Kind extends keyof MessageData>(message: Kind, callback: ServerMessageCallback<MessageData[Kind]>): () => void;
20
- onClientMessage<Kind extends keyof MessageData>(message: Kind, callback: ClientMessageCallback<MessageData[Kind]>): () => void;
21
- emitServer<Kind extends keyof MessageData>(message: Kind, data: MessageData[Kind], unreliable?: boolean): void;
22
- emitClient<Kind extends keyof MessageData>(player: Player, message: Kind, data: MessageData[Kind], unreliable?: boolean): void;
23
- emitAllClients<Kind extends keyof MessageData>(message: Kind, data: MessageData[Kind], unreliable?: boolean): void;
24
- private getPacket;
25
- /** @metadata macro */
26
- private createMessageSerializer;
27
- private getSerializer;
28
- }
29
- export {};
1
+ export { MessageEmitter } from "./message-emitter";
2
+ export { BuiltinMiddlewares } from "./builtin-middlewares";
3
+ export { DropRequest, type ClientMiddleware, type ServerMiddleware, type SharedMiddleware, type Middleware } from "./middleware";
package/out/init.luau CHANGED
@@ -1,212 +1,7 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
4
- local Networking = TS.import(script, TS.getModule(script, "@flamework", "networking").out).Networking
5
- local createBinarySerializer = TS.import(script, TS.getModule(script, "@rbxts", "flamework-binary-serializer").out).createBinarySerializer
6
- local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
7
- local GlobalEvents = Networking.createEvent("@rbxts/tether:init@GlobalEvents")
8
- local MessageEmitter
9
- do
10
- MessageEmitter = setmetatable({}, {
11
- __tostring = function()
12
- return "MessageEmitter"
13
- end,
14
- })
15
- MessageEmitter.__index = MessageEmitter
16
- function MessageEmitter.new(...)
17
- local self = setmetatable({}, MessageEmitter)
18
- return self:constructor(...) or self
19
- end
20
- function MessageEmitter:constructor()
21
- self.clientCallbacks = {}
22
- self.serverCallbacks = {}
23
- self.serializers = {}
24
- if RunService:IsServer() then
25
- self.serverEvents = GlobalEvents:createServer({}, {
26
- incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
27
- incoming = {
28
- sendServerMessage = { { t.union(t.string, t.number), t.interface({
29
- buffer = t.typeof("buffer"),
30
- blobs = t.array(t.any),
31
- }) }, nil },
32
- sendUnreliableServerMessage = { { t.union(t.string, t.number), t.interface({
33
- buffer = t.typeof("buffer"),
34
- blobs = t.array(t.any),
35
- }) }, nil },
36
- },
37
- incomingUnreliable = {
38
- sendUnreliableServerMessage = true,
39
- },
40
- outgoingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
41
- outgoingUnreliable = {
42
- sendUnreliableClientMessage = true,
43
- },
44
- namespaceIds = {},
45
- namespaces = {},
46
- })
47
- else
48
- self.clientEvents = GlobalEvents:createClient({}, {
49
- incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
50
- incoming = {
51
- sendClientMessage = { { t.union(t.string, t.number), t.interface({
52
- buffer = t.typeof("buffer"),
53
- blobs = t.array(t.any),
54
- }) }, nil },
55
- sendUnreliableClientMessage = { { t.union(t.string, t.number), t.interface({
56
- buffer = t.typeof("buffer"),
57
- blobs = t.array(t.any),
58
- }) }, nil },
59
- },
60
- incomingUnreliable = {
61
- sendUnreliableClientMessage = true,
62
- },
63
- outgoingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
64
- outgoingUnreliable = {
65
- sendUnreliableServerMessage = true,
66
- },
67
- namespaceIds = {},
68
- namespaces = {},
69
- })
70
- end
71
- end
72
- function MessageEmitter:create(metaForEachMessage)
73
- if metaForEachMessage == nil then
74
- warn("[Tether]: Failed to generate serializer metadata for MessageEmitter")
75
- end
76
- local emitter = MessageEmitter.new()
77
- if metaForEachMessage == nil then
78
- return emitter
79
- end
80
- for kind, meta in pairs(metaForEachMessage) do
81
- emitter:addSerializer(kind, meta)
82
- end
83
- return emitter
84
- end
85
- function MessageEmitter:addSerializer(message, meta)
86
- self.serializers[message] = self:createMessageSerializer(meta)
87
- end
88
- function MessageEmitter:initialize()
89
- if RunService:IsClient() then
90
- return self.clientEvents.sendClientMessage:connect(function(sentMessage, _param)
91
- local buffer = _param.buffer
92
- local blobs = _param.blobs
93
- local _clientCallbacks = self.clientCallbacks
94
- local _sentMessage = sentMessage
95
- local _condition = _clientCallbacks[_sentMessage]
96
- if _condition == nil then
97
- _condition = {}
98
- end
99
- local messageCallbacks = _condition
100
- if #messageCallbacks == 0 then
101
- return nil
102
- end
103
- local serializer = self:getSerializer(sentMessage)
104
- local data = serializer.deserialize(buffer, blobs)
105
- for _, callback in messageCallbacks do
106
- callback(data)
107
- end
108
- end)
109
- else
110
- return self.serverEvents.sendServerMessage:connect(function(player, sentMessage, _param)
111
- local buffer = _param.buffer
112
- local blobs = _param.blobs
113
- local _serverCallbacks = self.serverCallbacks
114
- local _sentMessage = sentMessage
115
- local _condition = _serverCallbacks[_sentMessage]
116
- if _condition == nil then
117
- _condition = {}
118
- end
119
- local messageCallbacks = _condition
120
- if #messageCallbacks == 0 then
121
- return nil
122
- end
123
- local serializer = self:getSerializer(sentMessage)
124
- local data = serializer.deserialize(buffer, blobs)
125
- for _, callback in messageCallbacks do
126
- callback(player, data)
127
- end
128
- end)
129
- end
130
- end
131
- function MessageEmitter:onServerMessage(message, callback)
132
- local _serverCallbacks = self.serverCallbacks
133
- local _message = message
134
- if not (_serverCallbacks[_message] ~= nil) then
135
- local _serverCallbacks_1 = self.serverCallbacks
136
- local _message_1 = message
137
- _serverCallbacks_1[_message_1] = {}
138
- end
139
- local _serverCallbacks_1 = self.serverCallbacks
140
- local _message_1 = message
141
- local callbacks = _serverCallbacks_1[_message_1]
142
- local _callback = callback
143
- table.insert(callbacks, _callback)
144
- local _serverCallbacks_2 = self.serverCallbacks
145
- local _message_2 = message
146
- _serverCallbacks_2[_message_2] = callbacks
147
- return function()
148
- local _callback_1 = callback
149
- local _arg0 = (table.find(callbacks, _callback_1) or 0) - 1
150
- return table.remove(callbacks, _arg0 + 1)
151
- end
152
- end
153
- function MessageEmitter:onClientMessage(message, callback)
154
- local _clientCallbacks = self.clientCallbacks
155
- local _message = message
156
- if not (_clientCallbacks[_message] ~= nil) then
157
- local _clientCallbacks_1 = self.clientCallbacks
158
- local _message_1 = message
159
- _clientCallbacks_1[_message_1] = {}
160
- end
161
- local _clientCallbacks_1 = self.clientCallbacks
162
- local _message_1 = message
163
- local callbacks = _clientCallbacks_1[_message_1]
164
- local _callback = callback
165
- table.insert(callbacks, _callback)
166
- local _clientCallbacks_2 = self.clientCallbacks
167
- local _message_2 = message
168
- _clientCallbacks_2[_message_2] = callbacks
169
- return function()
170
- local _callback_1 = callback
171
- local _arg0 = (table.find(callbacks, _callback_1) or 0) - 1
172
- return table.remove(callbacks, _arg0 + 1)
173
- end
174
- end
175
- function MessageEmitter:emitServer(message, data, unreliable)
176
- if unreliable == nil then
177
- unreliable = false
178
- end
179
- local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
180
- send(message, self:getPacket(message, data))
181
- end
182
- function MessageEmitter:emitClient(player, message, data, unreliable)
183
- if unreliable == nil then
184
- unreliable = false
185
- end
186
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
187
- send(player, message, self:getPacket(message, data))
188
- end
189
- function MessageEmitter:emitAllClients(message, data, unreliable)
190
- if unreliable == nil then
191
- unreliable = false
192
- end
193
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
194
- send:broadcast(message, self:getPacket(message, data))
195
- end
196
- function MessageEmitter:getPacket(message, data, unreliable)
197
- if unreliable == nil then
198
- unreliable = false
199
- end
200
- local serializer = self:getSerializer(message)
201
- return serializer.serialize(data)
202
- end
203
- function MessageEmitter:createMessageSerializer(meta)
204
- return createBinarySerializer(meta)
205
- end
206
- function MessageEmitter:getSerializer(message)
207
- return self.serializers[tostring(message)]
208
- end
209
- end
210
- return {
211
- MessageEmitter = MessageEmitter,
212
- }
3
+ local exports = {}
4
+ exports.MessageEmitter = TS.import(script, script, "message-emitter").MessageEmitter
5
+ exports.BuiltinMiddlewares = TS.import(script, script, "builtin-middlewares").BuiltinMiddlewares
6
+ exports.DropRequest = TS.import(script, script, "middleware").DropRequest
7
+ return exports
@@ -0,0 +1,59 @@
1
+ import { Modding } from "@flamework/core";
2
+ import { type SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
+ import Destroyable from "@rbxts/destroyable";
4
+ import { MiddlewareProvider } from "./middleware";
5
+ import type { ClientMessageCallback, ServerMessageCallback } from "./structs";
6
+ export declare class MessageEmitter<MessageData> extends Destroyable {
7
+ readonly middleware: MiddlewareProvider<MessageData>;
8
+ private readonly clientCallbacks;
9
+ private readonly serverCallbacks;
10
+ private serializers;
11
+ private serverEvents;
12
+ private clientEvents;
13
+ /** @metadata macro */
14
+ static create<MessageData>(metaForEachMessage?: Modding.Many<{
15
+ [Kind in keyof MessageData]: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<MessageData[Kind]>>;
16
+ }>): MessageEmitter<MessageData>;
17
+ private constructor();
18
+ /**.
19
+ * @returns A destructor function that disconnects the callback from the message
20
+ */
21
+ onServerMessage<Kind extends keyof MessageData>(message: Kind, callback: ServerMessageCallback<MessageData[Kind]>): () => void;
22
+ /**.
23
+ * @returns A destructor function that disconnects the callback from the message
24
+ */
25
+ onClientMessage<Kind extends keyof MessageData>(message: Kind, callback: ClientMessageCallback<MessageData[Kind]>): () => void;
26
+ private on;
27
+ /**
28
+ * Emits a message to all connected clients
29
+ *
30
+ * @param message The message kind to be sent
31
+ * @param data The data associated with the message
32
+ * @param unreliable Whether the message should be sent unreliably
33
+ */
34
+ emitServer<Kind extends keyof MessageData>(message: Kind, data?: MessageData[Kind], unreliable?: boolean): void;
35
+ /**
36
+ * Emits a message to a specific client
37
+ *
38
+ * @param player The player to whom the message is sent
39
+ * @param message The message kind to be sent
40
+ * @param data The data associated with the message
41
+ * @param unreliable Whether the message should be sent unreliably
42
+ */
43
+ emitClient<Kind extends keyof MessageData>(player: Player, message: Kind, data?: MessageData[Kind], unreliable?: boolean): void;
44
+ /**
45
+ * Emits a message to all connected clients
46
+ *
47
+ * @param message The message kind to be sent
48
+ * @param data The data associated with the message
49
+ * @param unreliable Whether the message should be sent unreliably
50
+ */
51
+ emitAllClients<Kind extends keyof MessageData>(message: Kind, data?: MessageData[Kind], unreliable?: boolean): void;
52
+ private initialize;
53
+ private getPacket;
54
+ /** @metadata macro */
55
+ private addSerializer;
56
+ /** @metadata macro */
57
+ private createMessageSerializer;
58
+ private getSerializer;
59
+ }
@@ -0,0 +1,272 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
4
+ local Networking = TS.import(script, TS.getModule(script, "@flamework", "networking").out).Networking
5
+ local createBinarySerializer = TS.import(script, TS.getModule(script, "@rbxts", "flamework-binary-serializer").out).createBinarySerializer
6
+ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
7
+ local Players = _services.Players
8
+ local RunService = _services.RunService
9
+ local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default
10
+ local _middleware = TS.import(script, script.Parent, "middleware")
11
+ local DropRequest = _middleware.DropRequest
12
+ local MiddlewareProvider = _middleware.MiddlewareProvider
13
+ local GlobalEvents = Networking.createEvent("@rbxts/tether:message-emitter@GlobalEvents")
14
+ local MessageEmitter
15
+ do
16
+ local super = Destroyable
17
+ MessageEmitter = setmetatable({}, {
18
+ __tostring = function()
19
+ return "MessageEmitter"
20
+ end,
21
+ __index = super,
22
+ })
23
+ MessageEmitter.__index = MessageEmitter
24
+ function MessageEmitter.new(...)
25
+ local self = setmetatable({}, MessageEmitter)
26
+ return self:constructor(...) or self
27
+ end
28
+ function MessageEmitter:constructor()
29
+ super.constructor(self)
30
+ self.middleware = MiddlewareProvider.new()
31
+ self.clientCallbacks = {}
32
+ self.serverCallbacks = {}
33
+ self.serializers = {}
34
+ self.janitor:Add(function()
35
+ table.clear(self.clientCallbacks)
36
+ table.clear(self.serverCallbacks)
37
+ self.serializers = nil
38
+ self.serverEvents = nil
39
+ self.clientEvents = nil
40
+ end)
41
+ if RunService:IsServer() then
42
+ self.serverEvents = GlobalEvents:createServer({}, {
43
+ incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
44
+ incoming = {
45
+ sendServerMessage = { { t.union(t.string, t.number), t.optional(t.interface({
46
+ buffer = t.typeof("buffer"),
47
+ blobs = t.array(t.any),
48
+ })) }, nil },
49
+ sendUnreliableServerMessage = { { t.union(t.string, t.number), t.optional(t.interface({
50
+ buffer = t.typeof("buffer"),
51
+ blobs = t.array(t.any),
52
+ })) }, nil },
53
+ },
54
+ incomingUnreliable = {
55
+ sendUnreliableServerMessage = true,
56
+ },
57
+ outgoingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
58
+ outgoingUnreliable = {
59
+ sendUnreliableClientMessage = true,
60
+ },
61
+ namespaceIds = {},
62
+ namespaces = {},
63
+ })
64
+ else
65
+ self.clientEvents = GlobalEvents:createClient({}, {
66
+ incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
67
+ incoming = {
68
+ sendClientMessage = { { t.union(t.string, t.number), t.optional(t.interface({
69
+ buffer = t.typeof("buffer"),
70
+ blobs = t.array(t.any),
71
+ })) }, nil },
72
+ sendUnreliableClientMessage = { { t.union(t.string, t.number), t.optional(t.interface({
73
+ buffer = t.typeof("buffer"),
74
+ blobs = t.array(t.any),
75
+ })) }, nil },
76
+ },
77
+ incomingUnreliable = {
78
+ sendUnreliableClientMessage = true,
79
+ },
80
+ outgoingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
81
+ outgoingUnreliable = {
82
+ sendUnreliableServerMessage = true,
83
+ },
84
+ namespaceIds = {},
85
+ namespaces = {},
86
+ })
87
+ end
88
+ end
89
+ function MessageEmitter:create(metaForEachMessage)
90
+ local emitter = MessageEmitter.new()
91
+ if metaForEachMessage == nil then
92
+ warn("[@rbxts/tether]: Failed to generate serializer metadata for MessageEmitter")
93
+ return emitter:initialize()
94
+ end
95
+ for kind, meta in pairs(metaForEachMessage) do
96
+ if meta == nil then
97
+ continue
98
+ end
99
+ emitter:addSerializer(kind, meta)
100
+ end
101
+ return emitter:initialize()
102
+ end
103
+ function MessageEmitter:onServerMessage(message, callback)
104
+ return self:on(message, callback)
105
+ end
106
+ function MessageEmitter:onClientMessage(message, callback)
107
+ return self:on(message, callback)
108
+ end
109
+ function MessageEmitter:on(message, callback)
110
+ local callbacksMap = if RunService:IsServer() then self.serverCallbacks else self.clientCallbacks
111
+ local _message = message
112
+ if not (callbacksMap[_message] ~= nil) then
113
+ local _message_1 = message
114
+ callbacksMap[_message_1] = {}
115
+ end
116
+ local _message_1 = message
117
+ local callbacks = callbacksMap[_message_1]
118
+ local _callback = callback
119
+ callbacks[_callback] = true
120
+ local _message_2 = message
121
+ callbacksMap[_message_2] = callbacks
122
+ return function()
123
+ local _callback_1 = callback
124
+ -- ▼ Set.delete ▼
125
+ local _valueExisted = callbacks[_callback_1] ~= nil
126
+ callbacks[_callback_1] = nil
127
+ -- ▲ Set.delete ▲
128
+ return _valueExisted
129
+ end
130
+ end
131
+ function MessageEmitter:emitServer(message, data, unreliable)
132
+ if unreliable == nil then
133
+ unreliable = false
134
+ end
135
+ local updateData = function(newData)
136
+ data = newData
137
+ return nil
138
+ end
139
+ for _, globalMiddleware in self.middleware:getServerGlobal() do
140
+ local result = globalMiddleware(message)(data, updateData)
141
+ if result == DropRequest then
142
+ return nil
143
+ end
144
+ end
145
+ for _, middleware in self.middleware:getServer(message) do
146
+ local result = middleware(message)(data, updateData)
147
+ if result == DropRequest then
148
+ return nil
149
+ end
150
+ end
151
+ local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
152
+ send(message, self:getPacket(message, data))
153
+ end
154
+ function MessageEmitter:emitClient(player, message, data, unreliable)
155
+ if unreliable == nil then
156
+ unreliable = false
157
+ end
158
+ local updateData = function(newData)
159
+ data = newData
160
+ return nil
161
+ end
162
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
163
+ local result = globalMiddleware(message)(player, data, updateData)
164
+ if result == DropRequest then
165
+ return nil
166
+ end
167
+ end
168
+ for _, middleware in self.middleware:getClient(message) do
169
+ local result = middleware(message)(player, data, updateData)
170
+ if result == DropRequest then
171
+ return nil
172
+ end
173
+ end
174
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
175
+ send(player, message, self:getPacket(message, data))
176
+ end
177
+ function MessageEmitter:emitAllClients(message, data, unreliable)
178
+ if unreliable == nil then
179
+ unreliable = false
180
+ end
181
+ local updateData = function(newData)
182
+ data = newData
183
+ return nil
184
+ end
185
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
186
+ for _1, player in Players:GetPlayers() do
187
+ local result = globalMiddleware(message)(player, data, updateData)
188
+ if result == DropRequest then
189
+ return nil
190
+ end
191
+ end
192
+ end
193
+ for _, middleware in self.middleware:getClient(message) do
194
+ for _1, player in Players:GetPlayers() do
195
+ local result = middleware(message)(player, data, updateData)
196
+ if result == DropRequest then
197
+ return nil
198
+ end
199
+ end
200
+ end
201
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
202
+ send:broadcast(message, self:getPacket(message, data))
203
+ end
204
+ function MessageEmitter:initialize()
205
+ if RunService:IsClient() then
206
+ self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(sentMessage, packet)
207
+ local _clientCallbacks = self.clientCallbacks
208
+ local _sentMessage = sentMessage
209
+ local _condition = _clientCallbacks[_sentMessage]
210
+ if _condition == nil then
211
+ _condition = {}
212
+ end
213
+ local messageCallbacks = _condition
214
+ -- ▼ ReadonlySet.size ▼
215
+ local _size = 0
216
+ for _ in messageCallbacks do
217
+ _size += 1
218
+ end
219
+ -- ▲ ReadonlySet.size ▲
220
+ if _size == 0 then
221
+ return nil
222
+ end
223
+ local serializer = self:getSerializer(sentMessage)
224
+ local data = if packet ~= nil then serializer.deserialize(packet.buffer, packet.blobs) else nil
225
+ for callback in messageCallbacks do
226
+ callback(data)
227
+ end
228
+ end))
229
+ else
230
+ self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, sentMessage, packet)
231
+ local _serverCallbacks = self.serverCallbacks
232
+ local _sentMessage = sentMessage
233
+ local _condition = _serverCallbacks[_sentMessage]
234
+ if _condition == nil then
235
+ _condition = {}
236
+ end
237
+ local messageCallbacks = _condition
238
+ -- ▼ ReadonlySet.size ▼
239
+ local _size = 0
240
+ for _ in messageCallbacks do
241
+ _size += 1
242
+ end
243
+ -- ▲ ReadonlySet.size ▲
244
+ if _size == 0 then
245
+ return nil
246
+ end
247
+ local serializer = self:getSerializer(sentMessage)
248
+ local data = if packet ~= nil then serializer.deserialize(packet.buffer, packet.blobs) else nil
249
+ for callback in messageCallbacks do
250
+ callback(player, data)
251
+ end
252
+ end))
253
+ end
254
+ return self
255
+ end
256
+ function MessageEmitter:getPacket(message, data)
257
+ local serializer = self:getSerializer(message)
258
+ return if data == nil then nil else serializer.serialize(data)
259
+ end
260
+ function MessageEmitter:addSerializer(message, meta)
261
+ self.serializers[message] = self:createMessageSerializer(meta)
262
+ end
263
+ function MessageEmitter:createMessageSerializer(meta)
264
+ return createBinarySerializer(meta)
265
+ end
266
+ function MessageEmitter:getSerializer(message)
267
+ return self.serializers[tostring(message)]
268
+ end
269
+ end
270
+ return {
271
+ MessageEmitter = MessageEmitter,
272
+ }
@@ -0,0 +1,31 @@
1
+ import type { BaseMessage } from "./structs";
2
+ type DropRequestSymbol = symbol & {
3
+ _drop_req?: undefined;
4
+ };
5
+ export declare const DropRequest: DropRequestSymbol;
6
+ type UpdateDataFn<T> = (newData: T) => void;
7
+ export type ClientMiddleware<Data = unknown> = (message: BaseMessage) => (player: Player, data: Readonly<Data>, updateData: UpdateDataFn<Data>) => DropRequestSymbol | void;
8
+ export type ServerMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>) => DropRequestSymbol | void;
9
+ export type SharedMiddleware = (message: BaseMessage) => () => DropRequestSymbol | void;
10
+ export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data>;
11
+ export declare class MiddlewareProvider<MessageData> {
12
+ private readonly clientGlobalMiddlewares;
13
+ private readonly serverGlobalMiddlewares;
14
+ private readonly clientMiddlewares;
15
+ private readonly serverMiddlewares;
16
+ /** @hidden */
17
+ getClient<Kind extends keyof MessageData>(message: Kind): ClientMiddleware<MessageData[Kind]>[];
18
+ /** @hidden */
19
+ getServer<Kind extends keyof MessageData>(message: Kind): ServerMiddleware<MessageData[Kind]>[];
20
+ /** @hidden */
21
+ getClientGlobal<Data>(): ClientMiddleware<Data>[];
22
+ /** @hidden */
23
+ getServerGlobal<Data>(): ServerMiddleware<Data>[];
24
+ useClient<Kind extends keyof MessageData>(message: Kind, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[], order?: number): this;
25
+ useServer<Kind extends keyof MessageData>(message: Kind, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[], order?: number): this;
26
+ useShared<Kind extends keyof MessageData>(message: Kind, middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
27
+ useClientGlobal<Data>(middlewares: ClientMiddleware<Data> | readonly ClientMiddleware<Data>[], order?: number): this;
28
+ useServerGlobal<Data>(middlewares: ServerMiddleware<Data> | readonly ServerMiddleware<Data>[], order?: number): this;
29
+ useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
30
+ }
31
+ export {};
@@ -0,0 +1,121 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local DropRequest = newproxy()
3
+ local MiddlewareProvider
4
+ do
5
+ MiddlewareProvider = setmetatable({}, {
6
+ __tostring = function()
7
+ return "MiddlewareProvider"
8
+ end,
9
+ })
10
+ MiddlewareProvider.__index = MiddlewareProvider
11
+ function MiddlewareProvider.new(...)
12
+ local self = setmetatable({}, MiddlewareProvider)
13
+ return self:constructor(...) or self
14
+ end
15
+ function MiddlewareProvider:constructor()
16
+ self.clientGlobalMiddlewares = {}
17
+ self.serverGlobalMiddlewares = {}
18
+ self.clientMiddlewares = {}
19
+ self.serverMiddlewares = {}
20
+ end
21
+ function MiddlewareProvider:getClient(message)
22
+ if self.clientMiddlewares[message] == nil then
23
+ self.clientMiddlewares[message] = {}
24
+ end
25
+ return self.clientMiddlewares[message]
26
+ end
27
+ function MiddlewareProvider:getServer(message)
28
+ if self.serverMiddlewares[message] == nil then
29
+ self.serverMiddlewares[message] = {}
30
+ end
31
+ return self.serverMiddlewares[message]
32
+ end
33
+ function MiddlewareProvider:getClientGlobal()
34
+ return self.clientGlobalMiddlewares
35
+ end
36
+ function MiddlewareProvider:getServerGlobal()
37
+ return self.serverGlobalMiddlewares
38
+ end
39
+ function MiddlewareProvider:useClient(message, middlewares, order)
40
+ local messageMiddleware = self:getClient(message)
41
+ local _middlewares = middlewares
42
+ if type(_middlewares) == "function" then
43
+ local _condition = order
44
+ if _condition == nil then
45
+ _condition = #messageMiddleware - 1
46
+ end
47
+ local _middlewares_1 = middlewares
48
+ table.insert(messageMiddleware, _condition + 1, _middlewares_1)
49
+ else
50
+ for _, middleware in middlewares do
51
+ self:useClient(message, middleware, order)
52
+ end
53
+ end
54
+ return self
55
+ end
56
+ function MiddlewareProvider:useServer(message, middlewares, order)
57
+ local messageMiddleware = self:getServer(message)
58
+ local _middlewares = middlewares
59
+ if type(_middlewares) == "function" then
60
+ local _condition = order
61
+ if _condition == nil then
62
+ _condition = #messageMiddleware - 1
63
+ end
64
+ local _middlewares_1 = middlewares
65
+ table.insert(messageMiddleware, _condition + 1, _middlewares_1)
66
+ else
67
+ for _, middleware in middlewares do
68
+ self:useServer(message, middleware, order)
69
+ end
70
+ end
71
+ return self
72
+ end
73
+ function MiddlewareProvider:useShared(message, middlewares, order)
74
+ self:useClient(message, middlewares, order)
75
+ self:useServer(message, middlewares, order)
76
+ return self
77
+ end
78
+ function MiddlewareProvider:useClientGlobal(middlewares, order)
79
+ local globalMiddleware = self:getClientGlobal()
80
+ local _middlewares = middlewares
81
+ if type(_middlewares) == "function" then
82
+ local _condition = order
83
+ if _condition == nil then
84
+ _condition = #globalMiddleware - 1
85
+ end
86
+ local _middlewares_1 = middlewares
87
+ table.insert(globalMiddleware, _condition + 1, _middlewares_1)
88
+ else
89
+ for _, middleware in middlewares do
90
+ self:useClientGlobal(middleware, order)
91
+ end
92
+ end
93
+ return self
94
+ end
95
+ function MiddlewareProvider:useServerGlobal(middlewares, order)
96
+ local globalMiddleware = self:getServerGlobal()
97
+ local _middlewares = middlewares
98
+ if type(_middlewares) == "function" then
99
+ local _condition = order
100
+ if _condition == nil then
101
+ _condition = #globalMiddleware - 1
102
+ end
103
+ local _middlewares_1 = middlewares
104
+ table.insert(globalMiddleware, _condition + 1, _middlewares_1)
105
+ else
106
+ for _, middleware in middlewares do
107
+ self:useServerGlobal(middleware, order)
108
+ end
109
+ end
110
+ return self
111
+ end
112
+ function MiddlewareProvider:useSharedGlobal(middlewares, order)
113
+ self:useClientGlobal(middlewares, order)
114
+ self:useServerGlobal(middlewares, order)
115
+ return self
116
+ end
117
+ end
118
+ return {
119
+ DropRequest = DropRequest,
120
+ MiddlewareProvider = MiddlewareProvider,
121
+ }
@@ -0,0 +1,19 @@
1
+ import type { Networking } from "@flamework/networking";
2
+ export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
3
+ export type ClientMessageCallback<T = unknown> = (data: T) => void;
4
+ export type ServerMessageCallback<T = unknown> = (player: Player, data: T) => void;
5
+ export type BaseMessage = number | string | symbol;
6
+ export interface SerializedPacket {
7
+ readonly buffer: buffer;
8
+ readonly blobs: defined[];
9
+ }
10
+ export type MessageEvent = (kind: BaseMessage, packet?: SerializedPacket) => void;
11
+ export type UnreliableMessageEvent = Networking.Unreliable<MessageEvent>;
12
+ export interface ServerEvents {
13
+ sendServerMessage: MessageEvent;
14
+ sendUnreliableServerMessage: UnreliableMessageEvent;
15
+ }
16
+ export interface ClientEvents {
17
+ sendClientMessage: MessageEvent;
18
+ sendUnreliableClientMessage: UnreliableMessageEvent;
19
+ }
@@ -0,0 +1,2 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ return nil
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.0.34",
3
+ "version": "1.1.0",
4
4
  "main": "out/init.lua",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",
@@ -11,7 +11,9 @@
11
11
  "roblox",
12
12
  "tether",
13
13
  "networking",
14
- "message"
14
+ "message",
15
+ "serialization",
16
+ "middleware"
15
17
  ],
16
18
  "repository": {
17
19
  "url": "git+https://github.com/R-unic/tether.git"
@@ -30,13 +32,14 @@
30
32
  "devDependencies": {
31
33
  "@rbxts/compiler-types": "^3.0.0-types.0",
32
34
  "@rbxts/types": "^1.0.835",
33
- "rbxts-transformer-flamework": "latest",
35
+ "rbxts-transformer-flamework": "^1.2.4",
34
36
  "roblox-ts": "^3.0.0",
35
37
  "typescript": "^5.5.3"
36
38
  },
37
39
  "dependencies": {
38
- "@flamework/core": "^1.2.3",
39
- "@flamework/networking": "^1.2.3",
40
+ "@flamework/core": "^1.2.4",
41
+ "@flamework/networking": "^1.2.4",
42
+ "@rbxts/destroyable": "^1.0.1",
40
43
  "@rbxts/flamework-binary-serializer": "^0.6.0",
41
44
  "@rbxts/services": "^1.5.5"
42
45
  }