@rbxts/tether 1.2.6 → 1.2.8

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,4 +1,5 @@
1
1
  # Tether
2
+
2
3
  A message-based networking solution for Roblox with automatic binary serialization and type validation.
3
4
 
4
5
  > [!CAUTION]
@@ -7,15 +8,16 @@ A message-based networking solution for Roblox with automatic binary serializati
7
8
  ## Usage
8
9
 
9
10
  ### In `shared/messaging.ts`
11
+
10
12
  ```ts
11
- import type { DataType } from "@rbxts/flamework-binary-serializer";
12
13
  import { MessageEmitter } from "@rbxts/tether";
14
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
13
15
 
14
16
  export const messaging = MessageEmitter.create<MessageData>();
15
17
 
16
18
  export const enum Message {
17
19
  Test,
18
- Packed
20
+ Packed,
19
21
  }
20
22
 
21
23
  export interface MessageData {
@@ -40,37 +42,43 @@ export interface MessageData {
40
42
  > Every single message kind must implement an interface for it's data (in the example that would be the object types in `MessageData`). Message serialization (as well as your message itself) will not work if you don't do this.
41
43
 
42
44
  ### Server
45
+
43
46
  ```ts
44
47
  import { Message, messaging } from "shared/messaging";
45
48
 
46
- messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
49
+ messaging.server.on(Message.Test, (player, data) =>
50
+ print(player, "sent data:", data)
51
+ );
47
52
  ```
48
53
 
49
54
  ### Client
55
+
50
56
  ```ts
51
57
  import { Message, messaging } from "shared/messaging";
52
58
 
53
59
  messaging.server.emit(Message.Test, {
54
60
  foo: "bar",
55
- n: 69
61
+ n: 69,
56
62
  });
57
63
  ```
58
64
 
59
65
  ## Simulated Remote Functions
66
+
60
67
  Tether does not directly use RemoteFunctions since it's based on the MessageEmitter structure. However I have created a small framework to simulate remote functions, as shown below.
61
68
 
62
69
  For each function you will need two messages. One to invoke the function, and one to send the return value back (which is done automatically).
63
70
 
64
71
  ### In `shared/messaging.ts`
72
+
65
73
  ```ts
66
- import type { DataType } from "@rbxts/flamework-binary-serializer";
67
74
  import { MessageEmitter } from "@rbxts/tether";
75
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
68
76
 
69
77
  export const messaging = MessageEmitter.create<MessageData>();
70
78
 
71
79
  export const enum Message {
72
80
  Increment,
73
- IncrementReturn
81
+ IncrementReturn,
74
82
  }
75
83
 
76
84
  export interface MessageData {
@@ -80,13 +88,19 @@ export interface MessageData {
80
88
  ```
81
89
 
82
90
  ### Server
91
+
83
92
  ```ts
84
93
  import { Message, messaging } from "shared/messaging";
85
94
 
86
- messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
95
+ messaging.server.setCallback(
96
+ Message.Increment,
97
+ Message.IncrementReturn,
98
+ (_, n) => n + 1
99
+ );
87
100
  ```
88
101
 
89
102
  ### Client
103
+
90
104
  ```ts
91
105
  import { Message, messaging } from "shared/messaging";
92
106
 
@@ -96,81 +110,98 @@ messaging.server
96
110
 
97
111
  // or use await style
98
112
  async function main(): Promise<void> {
99
- const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
100
- print(value) // 70
113
+ const value = await messaging.server.invoke(
114
+ Message.Increment,
115
+ Message.IncrementReturn,
116
+ 69
117
+ );
118
+ print(value); // 70
101
119
  }
102
120
 
103
121
  main();
104
122
  ```
105
123
 
106
124
  ## Middleware
125
+
107
126
  Drop, delay, or modify requests
108
127
 
109
128
  ### Creating middleware
110
129
 
111
130
  **Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
131
+
112
132
  #### Client
133
+
113
134
  ```ts
114
135
  import type { ClientMiddleware } from "@rbxts/tether";
115
136
 
116
137
  export function logClient(): ClientMiddleware {
117
- return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
138
+ return (player, ctx) =>
139
+ print(
140
+ `[LOG]: Sent message '${ctx.message}' to player ${player} with data:`,
141
+ ctx.data
142
+ );
118
143
  }
119
144
  ```
120
145
 
121
146
  #### Server
147
+
122
148
  ```ts
123
149
  import type { ServerMiddleware } from "@rbxts/tether";
124
150
 
125
151
  export function logServer(): ServerMiddleware {
126
- return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
152
+ return (ctx) =>
153
+ print(
154
+ `[LOG]: Sent message '${ctx.message}' to server with data:`,
155
+ ctx.data
156
+ );
127
157
  }
128
158
  ```
129
159
 
130
160
  #### Shared
161
+
131
162
  ```ts
132
163
  import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
133
164
 
134
165
  export function rateLimit(interval: number): SharedMiddleware {
135
166
  let lastRequest = 0;
136
167
 
137
- return message => // message attempting to be sent
138
- () => { // no data/player - it's a shared middleware
139
- if (os.clock() - lastRequest < interval)
140
- return DropRequest;
168
+ return () => {
169
+ if (os.clock() - lastRequest < interval) return DropRequest;
141
170
 
142
- lastRequest = os.clock();
143
- };
171
+ lastRequest = os.clock();
172
+ };
144
173
  }
145
174
  ```
146
175
 
147
176
  #### Transforming data
177
+
148
178
  ```ts
149
179
  import type { ServerMiddleware } from "@rbxts/tether";
150
180
 
151
181
  export function incrementNumberData(): ServerMiddleware<number> {
152
182
  // sets the data to be used by the any subsequent middlewares as well as sent through the remote
153
- return () => ({ data, updateData }) => updateData(data + 1);
183
+ return ({ data, updateData }) => updateData(data + 1);
154
184
  }
155
185
  ```
156
186
 
157
187
  ### Using middleware
188
+
158
189
  ```ts
159
- import type { DataType } from "@rbxts/flamework-binary-serializer";
160
190
  import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
191
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
161
192
 
162
193
  export const messaging = MessageEmitter.create<MessageData>();
163
194
  messaging.middleware
164
195
  // only allows requests to the server every 5 seconds,
165
196
  // drops any requests that occur within 5 seconds of each other
166
- .useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
197
+ .useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
167
198
  // will be just one byte!
168
- .useShared(Message.Packed, () => ctx => print("Packed object size:", buffer.len(ctx.getRawData().buffer)));
199
+ .useShared(Message.Packed, ctx => print("Packed object size:", buffer.len(ctx.getRawData().buffer)));
169
200
  // logs every message fired
170
201
  .useServerGlobal(logServer())
171
202
  .useClientGlobal(logClient())
172
203
  .useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
173
- .useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
204
+ .useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
174
205
  .useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
175
206
 
176
207
  export const enum Message {
@@ -194,4 +225,4 @@ export interface MessageData {
194
225
  readonly boolean8: boolean;
195
226
  }>;
196
227
  }
197
- ```
228
+ ```
@@ -2,7 +2,6 @@ import { Modding } from "@flamework/core";
2
2
  import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
3
  import type { Any } from "ts-toolbelt";
4
4
  import { type SharedMiddleware } from "./middleware";
5
- import type { TetherPacket } from "./structs";
6
5
  export declare namespace BuiltinMiddlewares {
7
6
  /**
8
7
  * Creates a shared middleware that will simulate a ping of the given amount when a message is sent
@@ -39,5 +38,5 @@ export declare namespace BuiltinMiddlewares {
39
38
  * @returns A shared middleware that will log a message whenever a message is sent.
40
39
  * @metadata macro
41
40
  */
42
- function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<TetherPacket<T>>>): SharedMiddleware<T>;
41
+ function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<T>>): SharedMiddleware<T>;
43
42
  }
@@ -18,10 +18,8 @@ do
18
18
  ]]
19
19
  local function simulatePing(pingInMs)
20
20
  return function()
21
- return function()
22
- task.wait(pingInMs / 1000)
23
- return nil
24
- end
21
+ task.wait(pingInMs / 1000)
22
+ return nil
25
23
  end
26
24
  end
27
25
  _container.simulatePing = simulatePing
@@ -38,13 +36,11 @@ do
38
36
  if throwError == nil then
39
37
  throwError = true
40
38
  end
41
- return function(message)
42
- return function(ctx)
43
- local rawData = ctx.getRawData()
44
- local totalSize = buffer.len(rawData.buffer) + #rawData.blobs * BLOB_SIZE
45
- if totalSize > maxBytes then
46
- return if throwError then error(`[@rbxts/tether]: Message '{message}' exceeded maximum packet size of {maxBytes} bytes`) else DropRequest
47
- end
39
+ return function(ctx)
40
+ local rawData = ctx.getRawData()
41
+ local totalSize = buffer.len(rawData.buffer) + #rawData.blobs * BLOB_SIZE
42
+ if totalSize > maxBytes then
43
+ return if throwError then error(`[@rbxts/tether]: Message '{ctx.message}' exceeded maximum packet size of {maxBytes} bytes`) else DropRequest
48
44
  end
49
45
  end
50
46
  end
@@ -60,12 +56,10 @@ do
60
56
  local function rateLimit(interval)
61
57
  local lastRequest = 0
62
58
  return function()
63
- return function()
64
- if os.clock() - lastRequest < interval then
65
- return DropRequest
66
- end
67
- lastRequest = os.clock()
59
+ if os.clock() - lastRequest < interval then
60
+ return DropRequest
68
61
  end
62
+ lastRequest = os.clock()
69
63
  end
70
64
  end
71
65
  _container.rateLimit = rateLimit
@@ -86,24 +80,23 @@ do
86
80
 
87
81
  ]]
88
82
  local function debug(schema)
89
- return function(message)
90
- return function(_param)
91
- local data = _param.data
92
- local getRawData = _param.getRawData
93
- local rawData = getRawData()
94
- local bufferSize = buffer.len(rawData.buffer)
95
- local blobsSize = #rawData.blobs * BLOB_SIZE
96
- local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[2], {
97
- pretty = true,
98
- }), "\n"), "\n ") else "unknown"
99
- local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", message, "\n", " - Data: ", repr(data, {
100
- pretty = true,
101
- }), "\n", " - Raw data:\n", " - Buffer: ", bufferToString(rawData.buffer), "\n", " - Blobs: ", repr(rawData.blobs, {
102
- pretty = false,
103
- robloxClassName = true,
104
- }), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
105
- print(table.concat(text, ""))
106
- end
83
+ return function(_param)
84
+ local message = _param.message
85
+ local data = _param.data
86
+ local getRawData = _param.getRawData
87
+ local rawData = getRawData()
88
+ local bufferSize = buffer.len(rawData.buffer)
89
+ local blobsSize = #rawData.blobs * BLOB_SIZE
90
+ local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[1], {
91
+ pretty = true,
92
+ }), "\n"), "\n ") else "unknown"
93
+ local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", message, "\n", " - Data: ", repr(data, {
94
+ pretty = true,
95
+ }), "\n", " - Raw data:\n", " - Buffer: ", bufferToString(rawData.buffer), "\n", " - Blobs: ", repr(rawData.blobs, {
96
+ pretty = false,
97
+ robloxClassName = true,
98
+ }), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
99
+ print(table.concat(text, ""))
107
100
  end
108
101
  end
109
102
  _container.debug = debug
@@ -2,18 +2,26 @@ import { Modding } from "@flamework/core";
2
2
  import Destroyable from "@rbxts/destroyable";
3
3
  import { MiddlewareProvider } from "./middleware";
4
4
  import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata, ClientMessageFunctionCallback, ServerMessageFunctionCallback } from "./structs";
5
+ interface MessageEmitterOptions {
6
+ readonly batchRemotes: boolean;
7
+ readonly batchRate: number;
8
+ }
5
9
  export declare class MessageEmitter<MessageData> extends Destroyable {
10
+ private readonly options;
6
11
  readonly middleware: MiddlewareProvider<MessageData>;
7
12
  private readonly clientCallbacks;
8
13
  private readonly clientFunctions;
9
14
  private readonly serverCallbacks;
10
15
  private readonly serverFunctions;
16
+ private readonly clientQueue;
17
+ private readonly clientBroadcastQueue;
18
+ private readonly serverQueue;
11
19
  private readonly guards;
12
20
  private readonly serializers;
13
21
  private serverEvents;
14
22
  private clientEvents;
15
23
  /** @metadata macro */
16
- static create<MessageData>(meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
24
+ static create<MessageData>(options?: Partial<MessageEmitterOptions>, meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
17
25
  private constructor();
18
26
  readonly server: {
19
27
  /**
@@ -101,11 +109,15 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
101
109
  */
102
110
  setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ClientMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
103
111
  };
104
- private validateData;
105
112
  private initialize;
113
+ private update;
114
+ private runClientMiddlewares;
115
+ private runServerMiddlewares;
116
+ private validateData;
106
117
  private onRemoteFire;
107
118
  private executeFunctions;
108
119
  private executeEventCallbacks;
120
+ private deserializeAndValidate;
109
121
  private once;
110
122
  private on;
111
123
  private getPacket;
@@ -115,3 +127,4 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
115
127
  private createMessageSerializer;
116
128
  private getSerializer;
117
129
  }
130
+ export {};
@@ -11,13 +11,18 @@ local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
11
11
  local _middleware = TS.import(script, script.Parent, "middleware")
12
12
  local DropRequest = _middleware.DropRequest
13
13
  local MiddlewareProvider = _middleware.MiddlewareProvider
14
- -- TODO: error when trying to do something like server.emit() from the server
15
- local messageSerializer = createBinarySerializer({ "object_raw", { { "message", { "u8" } }, { "data", { "literal", {}, -1 } } } })
14
+ local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
16
15
  local remotes = Networking.createEvent("@rbxts/tether:message-emitter@remotes")
16
+ local noServerListen = "[@rbxts/tether]: Cannot listen to server message from client"
17
+ local noClientListen = "[@rbxts/tether]: Cannot listen to client message from server"
17
18
  local metaGenerationFailed = "[@rbxts/tether]: Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas"
18
19
  local guardFailed = function(message, data)
19
20
  return `[@rbxts/tether]: Type validation guard failed for message '{message}' - check your sent data\nSent data: {repr(data)}`
20
21
  end
22
+ local defaultMesssageEmitterOptions = {
23
+ batchRemotes = true,
24
+ batchRate = 1 / 24,
25
+ }
21
26
  local MessageEmitter
22
27
  do
23
28
  local super = Destroyable
@@ -32,71 +37,61 @@ do
32
37
  local self = setmetatable({}, MessageEmitter)
33
38
  return self:constructor(...) or self
34
39
  end
35
- function MessageEmitter:constructor()
40
+ function MessageEmitter:constructor(options)
41
+ if options == nil then
42
+ options = defaultMesssageEmitterOptions
43
+ end
36
44
  super.constructor(self)
45
+ self.options = options
37
46
  self.middleware = MiddlewareProvider.new()
38
47
  self.clientCallbacks = {}
39
48
  self.clientFunctions = {}
40
49
  self.serverCallbacks = {}
41
50
  self.serverFunctions = {}
51
+ self.clientQueue = {}
52
+ self.clientBroadcastQueue = {}
53
+ self.serverQueue = {}
42
54
  self.guards = {}
43
55
  self.serializers = {}
44
56
  self.server = {
45
57
  on = function(message, callback)
58
+ if RunService:IsClient() then
59
+ error(noServerListen)
60
+ end
46
61
  return self:on(message, callback, self.serverCallbacks)
47
62
  end,
48
63
  once = function(message, callback)
64
+ if RunService:IsClient() then
65
+ error(noServerListen)
66
+ end
49
67
  return self:once(message, callback, self.serverCallbacks)
50
68
  end,
51
69
  emit = function(message, data, unreliable)
52
70
  if unreliable == nil then
53
71
  unreliable = false
54
72
  end
55
- local updateData = function(newData)
56
- data = newData
57
- return nil
58
- end
59
- local getPacket = function()
60
- return self:getPacket(message, data)
61
- end
62
- if not self:validateData(message, data) then
63
- return nil
73
+ if RunService:IsServer() then
74
+ error("[@rbxts/tether]: Cannot emit message to server from server")
64
75
  end
65
76
  task.spawn(function()
66
- local ctx = {
67
- data = data,
68
- updateData = updateData,
69
- getRawData = getPacket,
70
- }
71
- for _, globalMiddleware in self.middleware:getServerGlobal() do
72
- if not self:validateData(message, data) then
73
- return nil
74
- end
75
- local result = globalMiddleware(message)(ctx)
76
- if result == DropRequest then
77
- return nil
78
- end
79
- end
80
- for _, middleware in self.middleware:getServer(message) do
81
- if not self:validateData(message, data) then
82
- return nil
83
- end
84
- local result = middleware(message)(ctx)
85
- if result == DropRequest then
86
- return nil
87
- end
88
- end
89
- if not self:validateData(message, data) then
77
+ local _binding = self:runServerMiddlewares(message, data)
78
+ local dropRequest = _binding[1]
79
+ local newData = _binding[2]
80
+ if dropRequest then
90
81
  return nil
91
82
  end
92
- local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
93
- send(getPacket())
83
+ local _serverQueue = self.serverQueue
84
+ local _arg0 = { message, newData, unreliable }
85
+ table.insert(_serverQueue, _arg0)
94
86
  end)
95
87
  end,
96
88
  invoke = TS.async(function(message, returnMessage, data, unreliable)
97
89
  if unreliable == nil then
98
90
  unreliable = false
99
91
  end
92
+ if RunService:IsServer() then
93
+ error("[@rbxts/tether]: Cannot invoke server function from server")
94
+ end
100
95
  local _clientFunctions = self.clientFunctions
101
96
  local _returnMessage = returnMessage
102
97
  if not (_clientFunctions[_returnMessage] ~= nil) then
@@ -119,6 +114,9 @@ do
119
114
  return returnValue
120
115
  end),
121
116
  setCallback = function(message, returnMessage, callback)
117
+ if RunService:IsClient() then
118
+ error(noServerListen)
119
+ end
122
120
  return self.server.on(message, function(player, data)
123
121
  local returnValue = callback(player, data)
124
122
  self.client.emit(player, returnMessage, returnValue)
@@ -127,54 +125,34 @@ do
127
125
  }
128
126
  self.client = {
129
127
  on = function(message, callback)
128
+ if RunService:IsServer() then
129
+ error(noClientListen)
130
+ end
130
131
  return self:on(message, callback, self.clientCallbacks)
131
132
  end,
132
133
  once = function(message, callback)
134
+ if RunService:IsServer() then
135
+ error(noClientListen)
136
+ end
133
137
  return self:once(message, callback, self.clientCallbacks)
134
138
  end,
135
139
  emit = function(player, message, data, unreliable)
136
140
  if unreliable == nil then
137
141
  unreliable = false
138
142
  end
139
- local updateData = function(newData)
140
- data = newData
141
- return nil
142
- end
143
- local getPacket = function()
144
- return self:getPacket(message, data)
145
- end
146
- if not self:validateData(message, data) then
147
- return nil
143
+ if RunService:IsClient() then
144
+ error("[@rbxts/tether]: Cannot emit message to client from client")
148
145
  end
149
146
  task.spawn(function()
150
- local ctx = {
151
- data = data,
152
- updateData = updateData,
153
- getRawData = getPacket,
154
- }
155
- for _, globalMiddleware in self.middleware:getClientGlobal() do
156
- if not self:validateData(message, data) then
157
- return nil
158
- end
159
- local result = globalMiddleware(message)(player, ctx)
160
- if result == DropRequest then
161
- return nil
162
- end
163
- end
164
- for _, middleware in self.middleware:getClient(message) do
165
- if not self:validateData(message, data) then
166
- return nil
167
- end
168
- local result = middleware(message)(player, ctx)
169
- if result == DropRequest then
170
- return nil
171
- end
172
- end
173
- if not self:validateData(message, data) then
147
+ local _binding = self:runClientMiddlewares(message, data)
148
+ local dropRequest = _binding[1]
149
+ local newData = _binding[2]
150
+ if dropRequest then
174
151
  return nil
175
152
  end
176
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
177
- send(player, getPacket())
153
+ local _clientQueue = self.clientQueue
154
+ local _arg0 = { player, message, newData, unreliable }
155
+ table.insert(_clientQueue, _arg0)
178
156
  end)
179
157
  end,
180
158
  emitExcept = function(player, message, data, unreliable)
@@ -211,56 +189,28 @@ do
211
189
  if unreliable == nil then
212
190
  unreliable = false
213
191
  end
214
- local updateData = function(newData)
215
- data = newData
216
- return nil
217
- end
218
- local getPacket = function()
219
- return self:getPacket(message, data)
220
- end
221
- if not self:validateData(message, data) then
222
- return nil
192
+ if RunService:IsClient() then
193
+ error("[@rbxts/tether]: Cannot emit message to all clients from client")
223
194
  end
224
195
  task.spawn(function()
225
- local ctx = {
226
- data = data,
227
- updateData = updateData,
228
- getRawData = getPacket,
229
- }
230
- local players = Players:GetPlayers()
231
- for _, globalMiddleware in self.middleware:getClientGlobal() do
232
- for _1, player in players do
233
- if not self:validateData(message, data) then
234
- return nil
235
- end
236
- local result = globalMiddleware(message)(player, ctx)
237
- if result == DropRequest then
238
- return nil
239
- end
240
- end
241
- end
242
- for _, middleware in self.middleware:getClient(message) do
243
- for _1, player in players do
244
- if not self:validateData(message, data) then
245
- return nil
246
- end
247
- local result = middleware(message)(player, ctx)
248
- if result == DropRequest then
249
- return nil
250
- end
251
- end
252
- end
253
- if not self:validateData(message, data) then
196
+ local _binding = self:runClientMiddlewares(message, data)
197
+ local dropRequest = _binding[1]
198
+ local newData = _binding[2]
199
+ if dropRequest then
254
200
  return nil
255
201
  end
256
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
257
- send:broadcast(getPacket())
202
+ local _clientBroadcastQueue = self.clientBroadcastQueue
203
+ local _arg0 = { message, newData, unreliable }
204
+ table.insert(_clientBroadcastQueue, _arg0)
258
205
  end)
259
206
  end,
260
207
  invoke = TS.async(function(message, returnMessage, player, data, unreliable)
261
208
  if unreliable == nil then
262
209
  unreliable = false
263
210
  end
211
+ if RunService:IsClient() then
212
+ error("[@rbxts/tether]: Cannot invoke client function from client")
213
+ end
264
214
  local _serverFunctions = self.serverFunctions
265
215
  local _returnMessage = returnMessage
266
216
  if not (_serverFunctions[_returnMessage] ~= nil) then
@@ -283,6 +233,9 @@ do
283
233
  return returnValue
284
234
  end),
285
235
  setCallback = function(message, returnMessage, callback)
236
+ if RunService:IsServer() then
237
+ error(noClientListen)
238
+ end
286
239
  return self.client.on(message, function(data)
287
240
  local returnValue = callback(data)
288
241
  self.server.emit(returnMessage, returnValue)
@@ -301,10 +254,12 @@ do
301
254
  incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
302
255
  incoming = {
303
256
  sendServerMessage = { { t.interface({
257
+ messageBuffer = t.typeof("buffer"),
304
258
  buffer = t.typeof("buffer"),
305
259
  blobs = t.array(t.any),
306
260
  }) }, nil },
307
261
  sendUnreliableServerMessage = { { t.interface({
262
+ messageBuffer = t.typeof("buffer"),
308
263
  buffer = t.typeof("buffer"),
309
264
  blobs = t.array(t.any),
310
265
  }) }, nil },
@@ -324,10 +279,12 @@ do
324
279
  incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
325
280
  incoming = {
326
281
  sendClientMessage = { { t.interface({
282
+ messageBuffer = t.typeof("buffer"),
327
283
  buffer = t.typeof("buffer"),
328
284
  blobs = t.array(t.any),
329
285
  }) }, nil },
330
286
  sendUnreliableClientMessage = { { t.interface({
287
+ messageBuffer = t.typeof("buffer"),
331
288
  buffer = t.typeof("buffer"),
332
289
  blobs = t.array(t.any),
333
290
  }) }, nil },
@@ -344,8 +301,8 @@ do
344
301
  })
345
302
  end
346
303
  end
347
- function MessageEmitter:create(meta)
348
- local emitter = MessageEmitter.new()
304
+ function MessageEmitter:create(options, meta)
305
+ local emitter = MessageEmitter.new(Object.assign({}, options, defaultMesssageEmitterOptions))
349
306
  if meta == nil then
350
307
  warn(metaGenerationFailed)
351
308
  return emitter:initialize()
@@ -356,23 +313,12 @@ do
356
313
  local numberKind = tonumber(kind)
357
314
  emitter.guards[numberKind] = guard
358
315
  if serializerMetadata == nil then
359
- emitter.serializers[numberKind] = messageSerializer
360
316
  continue
361
317
  end
362
318
  emitter:addSerializer(numberKind, serializerMetadata)
363
319
  end
364
320
  return emitter:initialize()
365
321
  end
366
- function MessageEmitter:validateData(message, data)
367
- local _guards = self.guards
368
- local _message = message
369
- local guard = _guards[_message]
370
- local guardPassed = guard(data)
371
- if not guardPassed then
372
- warn(guardFailed(message, data))
373
- end
374
- return guardPassed
375
- end
376
322
  function MessageEmitter:initialize()
377
323
  if RunService:IsClient() then
378
324
  self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
@@ -389,10 +335,143 @@ do
389
335
  return self:onRemoteFire(serializedPacket, player)
390
336
  end))
391
337
  end
338
+ local elapsed = 0
339
+ RunService.Heartbeat:Connect(function(dt)
340
+ elapsed += dt
341
+ if elapsed >= self.options.batchRate then
342
+ elapsed -= self.options.batchRate
343
+ self:update()
344
+ end
345
+ end)
392
346
  return self
393
347
  end
348
+ function MessageEmitter:update()
349
+ for _, _binding in self.clientQueue do
350
+ local player = _binding[1]
351
+ local message = _binding[2]
352
+ local data = _binding[3]
353
+ local unreliable = _binding[4]
354
+ local remote = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
355
+ remote(player, self:getPacket(message, data))
356
+ end
357
+ table.clear(self.clientQueue)
358
+ for _, _binding in self.clientBroadcastQueue do
359
+ local message = _binding[1]
360
+ local data = _binding[2]
361
+ local unreliable = _binding[3]
362
+ local remote = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
363
+ remote:broadcast(self:getPacket(message, data))
364
+ end
365
+ table.clear(self.clientBroadcastQueue)
366
+ for _, _binding in self.serverQueue do
367
+ local message = _binding[1]
368
+ local data = _binding[2]
369
+ local unreliable = _binding[3]
370
+ local remote = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
371
+ remote(self:getPacket(message, data))
372
+ end
373
+ table.clear(self.serverQueue)
374
+ end
375
+ function MessageEmitter:runClientMiddlewares(message, data, player)
376
+ if not self:validateData(message, data) then
377
+ return { true, data }
378
+ end
379
+ local players = player or Players:GetPlayers()
380
+ local ctx = {
381
+ message = message,
382
+ data = data,
383
+ updateData = function(newData)
384
+ data = newData
385
+ return nil
386
+ end,
387
+ getRawData = function()
388
+ return self:getPacket(message, data)
389
+ end,
390
+ }
391
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
392
+ local result = globalMiddleware(players, ctx)
393
+ if not self:validateData(message, data, "Invalid data after global client middleware") then
394
+ return { false, data }
395
+ end
396
+ if result == DropRequest then
397
+ self.middleware:notifyRequestDropped(message, "Global client middleware")
398
+ return { true, data }
399
+ end
400
+ end
401
+ for _, middleware in self.middleware:getClient(message) do
402
+ local result = middleware(players, ctx)
403
+ if not self:validateData(message, data, "Invalid data after client middleware") then
404
+ return { false, data }
405
+ end
406
+ if result == DropRequest then
407
+ self.middleware:notifyRequestDropped(message, "Client middleware")
408
+ return { true, data }
409
+ end
410
+ end
411
+ if not self:validateData(message, data) then
412
+ return { true, data }
413
+ end
414
+ return { false, data }
415
+ end
416
+ function MessageEmitter:runServerMiddlewares(message, data)
417
+ if not self:validateData(message, data) then
418
+ return { true, data }
419
+ end
420
+ local ctx = {
421
+ message = message,
422
+ data = data,
423
+ updateData = function(newData)
424
+ data = newData
425
+ return nil
426
+ end,
427
+ getRawData = function()
428
+ return self:getPacket(message, data)
429
+ end,
430
+ }
431
+ for _, globalMiddleware in self.middleware:getServerGlobal() do
432
+ if not self:validateData(message, data, "Invalid data after global server middleware") then
433
+ return { false, data }
434
+ end
435
+ local result = globalMiddleware(ctx)
436
+ if result == DropRequest then
437
+ self.middleware:notifyRequestDropped(message, "Global server middleware")
438
+ return { true, data }
439
+ end
440
+ end
441
+ for _, middleware in self.middleware:getServer(message) do
442
+ if not self:validateData(message, data, "Invalid data after server middleware") then
443
+ return { false, data }
444
+ end
445
+ local result = middleware(ctx)
446
+ if result == DropRequest then
447
+ self.middleware:notifyRequestDropped(message, "Server middleware")
448
+ return { true, data }
449
+ end
450
+ end
451
+ if not self:validateData(message, data) then
452
+ return { true, data }
453
+ end
454
+ return { false, data }
455
+ end
456
+ function MessageEmitter:validateData(message, data, requestDropReason)
457
+ if requestDropReason == nil then
458
+ requestDropReason = "Invalid data"
459
+ end
460
+ local _guards = self.guards
461
+ local _message = message
462
+ local guard = _guards[_message]
463
+ local guardPassed = guard(data)
464
+ if not guardPassed then
465
+ warn(guardFailed(message, data))
466
+ self.middleware:notifyRequestDropped(message, requestDropReason)
467
+ end
468
+ return guardPassed
469
+ end
394
470
  function MessageEmitter:onRemoteFire(serializedPacket, player)
395
- local message = buffer.readu8(serializedPacket.buffer, 0)
471
+ if buffer.len(serializedPacket.messageBuffer) > 1 then
472
+ return warn("[@rbxts/tether]: Rejected message because message buffer was larger than one byte")
473
+ end
474
+ local message = buffer.readu8(serializedPacket.messageBuffer, 0)
396
475
  self:executeEventCallbacks(message, serializedPacket, player)
397
476
  self:executeFunctions(message, serializedPacket)
398
477
  end
@@ -404,18 +483,9 @@ do
404
483
  if functions == nil then
405
484
  return nil
406
485
  end
407
- local serializer = self:getSerializer(message)
408
- local _packet = serializer
409
- if _packet ~= nil then
410
- _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
411
- end
412
- local packet = _packet
486
+ local packet = self:deserializeAndValidate(message, serializedPacket)
413
487
  for callback in functions do
414
- local _result = packet
415
- if _result ~= nil then
416
- _result = _result.data
417
- end
418
- callback(_result)
488
+ callback(packet)
419
489
  end
420
490
  end
421
491
  function MessageEmitter:executeEventCallbacks(message, serializedPacket, player)
@@ -426,28 +496,24 @@ do
426
496
  if callbacks == nil then
427
497
  return nil
428
498
  end
499
+ local packet = self:deserializeAndValidate(message, serializedPacket)
500
+ for callback in callbacks do
501
+ if isServer then
502
+ callback(player, packet)
503
+ else
504
+ callback(packet)
505
+ end
506
+ end
507
+ end
508
+ function MessageEmitter:deserializeAndValidate(message, serializedPacket)
429
509
  local serializer = self:getSerializer(message)
430
510
  local _packet = serializer
431
511
  if _packet ~= nil then
432
512
  _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
433
513
  end
434
514
  local packet = _packet
435
- for callback in callbacks do
436
- if isServer then
437
- local _exp = player
438
- local _result = packet
439
- if _result ~= nil then
440
- _result = _result.data
441
- end
442
- callback(_exp, _result)
443
- else
444
- local _result = packet
445
- if _result ~= nil then
446
- _result = _result.data
447
- end
448
- callback(_result)
449
- end
450
- end
515
+ self:validateData(message, packet)
516
+ return packet
451
517
  end
452
518
  function MessageEmitter:once(message, callback, callbacksMap)
453
519
  local destructor
@@ -484,17 +550,22 @@ do
484
550
  end
485
551
  function MessageEmitter:getPacket(message, data)
486
552
  local serializer = self:getSerializer(message)
553
+ local messageBuffer = buffer.create(1)
554
+ buffer.writeu8(messageBuffer, 0, message)
487
555
  if serializer == nil then
488
- warn(`[@rbxts/tether]: Failed to get packet for message '{message}', no serializer was found`)
489
- return messageSerializer.serialize({
490
- message = message,
491
- data = nil,
492
- })
556
+ return {
557
+ messageBuffer = messageBuffer,
558
+ buffer = buffer.create(0),
559
+ blobs = {},
560
+ }
493
561
  end
494
- return serializer.serialize({
495
- message = message,
496
- data = data,
497
- })
562
+ local _object = {
563
+ messageBuffer = messageBuffer,
564
+ }
565
+ for _k, _v in serializer.serialize(data) do
566
+ _object[_k] = _v
567
+ end
568
+ return _object
498
569
  end
499
570
  function MessageEmitter:addSerializer(message, meta)
500
571
  self.serializers[message] = self:createMessageSerializer(meta)
@@ -5,22 +5,28 @@ type DropRequestSymbol = symbol & {
5
5
  export declare const DropRequest: DropRequestSymbol;
6
6
  export type ClientMiddleware<Data = unknown> = {
7
7
  _client?: void;
8
- } & ((message: BaseMessage) => (player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
8
+ } & ((player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
9
9
  export type ServerMiddleware<Data = unknown> = {
10
10
  _server?: void;
11
11
  } & SharedMiddleware<Data>;
12
- export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
12
+ export type SharedMiddleware<Data = unknown> = (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
13
13
  export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
14
- export interface MiddlewareContext<Data = unknown> {
14
+ export interface MiddlewareContext<Data = unknown, Message extends BaseMessage = BaseMessage> {
15
+ readonly message: Message;
15
16
  readonly data: Readonly<Data>;
16
17
  updateData: (newData: Data) => void;
17
18
  getRawData: () => SerializedPacket;
18
19
  }
20
+ type RequestDropCallback = (message: BaseMessage, reason?: string) => void;
19
21
  export declare class MiddlewareProvider<MessageData> {
20
22
  private readonly clientGlobalMiddlewares;
21
23
  private readonly serverGlobalMiddlewares;
22
24
  private readonly clientMiddlewares;
23
25
  private readonly serverMiddlewares;
26
+ private readonly requestDroppedCallbacks;
27
+ onRequestDropped(callback: RequestDropCallback): () => void;
28
+ /** @hidden */
29
+ notifyRequestDropped(message: BaseMessage, reason?: string): void;
24
30
  /** @hidden */
25
31
  getClient<Kind extends keyof MessageData>(message: Kind & BaseMessage): ClientMiddleware<MessageData[Kind]>[];
26
32
  /** @hidden */
@@ -1,5 +1,6 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local DropRequest = newproxy()
3
+ -- TODO: middlewares upon received message
3
4
  local MiddlewareProvider
4
5
  do
5
6
  MiddlewareProvider = setmetatable({}, {
@@ -17,6 +18,26 @@ do
17
18
  self.serverGlobalMiddlewares = {}
18
19
  self.clientMiddlewares = {}
19
20
  self.serverMiddlewares = {}
21
+ self.requestDroppedCallbacks = {}
22
+ end
23
+ function MiddlewareProvider:onRequestDropped(callback)
24
+ local _requestDroppedCallbacks = self.requestDroppedCallbacks
25
+ local _callback = callback
26
+ _requestDroppedCallbacks[_callback] = true
27
+ return function()
28
+ local _requestDroppedCallbacks_1 = self.requestDroppedCallbacks
29
+ local _callback_1 = callback
30
+ -- ▼ Set.delete ▼
31
+ local _valueExisted = _requestDroppedCallbacks_1[_callback_1] ~= nil
32
+ _requestDroppedCallbacks_1[_callback_1] = nil
33
+ -- ▲ Set.delete ▲
34
+ return _valueExisted
35
+ end
36
+ end
37
+ function MiddlewareProvider:notifyRequestDropped(message, reason)
38
+ for callback in self.requestDroppedCallbacks do
39
+ callback(message, reason)
40
+ end
20
41
  end
21
42
  function MiddlewareProvider:getClient(message)
22
43
  if self.clientMiddlewares[message] == nil then
@@ -77,10 +98,8 @@ do
77
98
  -- ▼ ReadonlyArray.map ▼
78
99
  local _newValue = table.create(#_exp)
79
100
  local _callback = function(middleware)
80
- return function(message)
81
- return function(_, ctx)
82
- return middleware(message)(ctx)
83
- end
101
+ return function(_, ctx)
102
+ return middleware(ctx)
84
103
  end
85
104
  end
86
105
  for _k, _v in _exp do
@@ -89,8 +108,7 @@ do
89
108
  -- ▲ ReadonlyArray.map ▲
90
109
  local client = _newValue
91
110
  self:useServer(message, server, order)
92
- self:useClient(message, client, order)
93
- return self
111
+ return self:useClient(message, client, order)
94
112
  end
95
113
  function MiddlewareProvider:useClientGlobal(middlewares, order)
96
114
  local globalMiddleware = self:getClientGlobal()
@@ -132,10 +150,8 @@ do
132
150
  -- ▼ ReadonlyArray.map ▼
133
151
  local _newValue = table.create(#_exp)
134
152
  local _callback = function(middleware)
135
- return function(message)
136
- return function(_, ctx)
137
- return middleware(message)(ctx)
138
- end
153
+ return function(_, ctx)
154
+ return middleware(ctx)
139
155
  end
140
156
  end
141
157
  for _k, _v in _exp do
@@ -144,8 +160,7 @@ do
144
160
  -- ▲ ReadonlyArray.map ▲
145
161
  local client = _newValue
146
162
  self:useClientGlobal(client, order)
147
- self:useServerGlobal(middlewares, order)
148
- return self
163
+ return self:useServerGlobal(middlewares, order)
149
164
  end
150
165
  end
151
166
  return {
package/out/structs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Modding } from "@flamework/core";
2
2
  import type { Networking } from "@flamework/networking";
3
- import type { DataType, SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
+ import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
4
4
  export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
5
5
  export type ClientMessageCallback<T = unknown> = (data: T) => void;
6
6
  export type ClientMessageFunctionCallback<T = unknown, R = unknown> = (data: T) => R;
@@ -8,13 +8,10 @@ export type ServerMessageCallback<T = unknown> = (player: Player, data: T) => vo
8
8
  export type ServerMessageFunctionCallback<T = unknown, R = unknown> = (player: Player, data: T) => R;
9
9
  export type BaseMessage = number;
10
10
  export interface SerializedPacket {
11
+ readonly messageBuffer: buffer;
11
12
  readonly buffer: buffer;
12
13
  readonly blobs: defined[];
13
14
  }
14
- export interface TetherPacket<Data> {
15
- readonly message: DataType.u8;
16
- readonly data: Data;
17
- }
18
15
  export type MessageEvent = (packet: SerializedPacket) => void;
19
16
  export type UnreliableMessageEvent = Networking.Unreliable<MessageEvent>;
20
17
  export interface ServerEvents {
@@ -27,7 +24,7 @@ export interface ClientEvents {
27
24
  }
28
25
  export interface MessageMetadata<MessageData, Kind extends keyof MessageData> {
29
26
  readonly guard: Modding.Generic<MessageData[Kind], "guard">;
30
- readonly serializerMetadata: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<TetherPacket<MessageData[Kind]>>>;
27
+ readonly serializerMetadata: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<MessageData[Kind]>>;
31
28
  }
32
29
  export type Guard<T = unknown> = (value: unknown) => value is T;
33
30
  export type MessageEmitterMetadata<MessageData> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "main": "out/init.lua",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",
@@ -42,6 +42,7 @@
42
42
  "@rbxts/destroyable": "^1.0.1",
43
43
  "@rbxts/flamework-binary-serializer": "^0.6.0",
44
44
  "@rbxts/flamework-meta-utils": "^1.0.4",
45
+ "@rbxts/object-utils": "^1.0.4",
45
46
  "@rbxts/repr": "^1.0.1",
46
47
  "@rbxts/services": "^1.5.5",
47
48
  "ts-toolbelt": "^9.6.0"