@rbxts/tether 1.2.7 → 1.2.9
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 +54 -23
- package/out/builtin-middlewares.luau +27 -34
- package/out/message-emitter.d.ts +15 -2
- package/out/message-emitter.luau +225 -136
- package/out/middleware.d.ts +9 -3
- package/out/middleware.luau +27 -12
- package/package.json +2 -1
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) =>
|
|
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(
|
|
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(
|
|
100
|
-
|
|
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
|
|
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
|
|
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
|
|
138
|
-
()
|
|
139
|
-
if (os.clock() - lastRequest < interval)
|
|
140
|
-
return DropRequest;
|
|
168
|
+
return () => {
|
|
169
|
+
if (os.clock() - lastRequest < interval) return DropRequest;
|
|
141
170
|
|
|
142
|
-
|
|
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 (
|
|
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,
|
|
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
|
+
```
|
|
@@ -18,10 +18,8 @@ do
|
|
|
18
18
|
]]
|
|
19
19
|
local function simulatePing(pingInMs)
|
|
20
20
|
return function()
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if
|
|
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
|
-
|
|
64
|
-
|
|
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(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
package/out/message-emitter.d.ts
CHANGED
|
@@ -2,7 +2,12 @@ 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;
|
|
@@ -10,10 +15,13 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
|
10
15
|
private readonly serverFunctions;
|
|
11
16
|
private readonly guards;
|
|
12
17
|
private readonly serializers;
|
|
18
|
+
private serverQueue;
|
|
19
|
+
private clientBroadcastQueue;
|
|
20
|
+
private clientQueue;
|
|
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 {};
|
package/out/message-emitter.luau
CHANGED
|
@@ -11,12 +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
|
-
|
|
14
|
+
local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
|
|
15
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"
|
|
16
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"
|
|
17
19
|
local guardFailed = function(message, data)
|
|
18
20
|
return `[@rbxts/tether]: Type validation guard failed for message '{message}' - check your sent data\nSent data: {repr(data)}`
|
|
19
21
|
end
|
|
22
|
+
local defaultMesssageEmitterOptions = {
|
|
23
|
+
batchRemotes = true,
|
|
24
|
+
batchRate = 1 / 24,
|
|
25
|
+
}
|
|
20
26
|
local MessageEmitter
|
|
21
27
|
do
|
|
22
28
|
local super = Destroyable
|
|
@@ -31,8 +37,12 @@ do
|
|
|
31
37
|
local self = setmetatable({}, MessageEmitter)
|
|
32
38
|
return self:constructor(...) or self
|
|
33
39
|
end
|
|
34
|
-
function MessageEmitter:constructor()
|
|
40
|
+
function MessageEmitter:constructor(options)
|
|
41
|
+
if options == nil then
|
|
42
|
+
options = defaultMesssageEmitterOptions
|
|
43
|
+
end
|
|
35
44
|
super.constructor(self)
|
|
45
|
+
self.options = options
|
|
36
46
|
self.middleware = MiddlewareProvider.new()
|
|
37
47
|
self.clientCallbacks = {}
|
|
38
48
|
self.clientFunctions = {}
|
|
@@ -40,62 +50,51 @@ do
|
|
|
40
50
|
self.serverFunctions = {}
|
|
41
51
|
self.guards = {}
|
|
42
52
|
self.serializers = {}
|
|
53
|
+
self.serverQueue = {}
|
|
54
|
+
self.clientBroadcastQueue = {}
|
|
55
|
+
self.clientQueue = {}
|
|
43
56
|
self.server = {
|
|
44
57
|
on = function(message, callback)
|
|
58
|
+
if RunService:IsClient() then
|
|
59
|
+
error(noServerListen)
|
|
60
|
+
end
|
|
45
61
|
return self:on(message, callback, self.serverCallbacks)
|
|
46
62
|
end,
|
|
47
63
|
once = function(message, callback)
|
|
64
|
+
if RunService:IsClient() then
|
|
65
|
+
error(noServerListen)
|
|
66
|
+
end
|
|
48
67
|
return self:once(message, callback, self.serverCallbacks)
|
|
49
68
|
end,
|
|
50
69
|
emit = function(message, data, unreliable)
|
|
51
70
|
if unreliable == nil then
|
|
52
71
|
unreliable = false
|
|
53
72
|
end
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return nil
|
|
57
|
-
end
|
|
58
|
-
local getPacket = function()
|
|
59
|
-
return self:getPacket(message, data)
|
|
60
|
-
end
|
|
61
|
-
if not self:validateData(message, data) then
|
|
62
|
-
return nil
|
|
73
|
+
if RunService:IsServer() then
|
|
74
|
+
error("[@rbxts/tether]: Cannot emit message to server from server")
|
|
63
75
|
end
|
|
64
76
|
task.spawn(function()
|
|
65
|
-
local
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
71
|
-
if not self:validateData(message, data) then
|
|
72
|
-
return nil
|
|
73
|
-
end
|
|
74
|
-
local result = globalMiddleware(message)(ctx)
|
|
75
|
-
if result == DropRequest then
|
|
76
|
-
return nil
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
for _, middleware in self.middleware:getServer(message) do
|
|
80
|
-
if not self:validateData(message, data) then
|
|
81
|
-
return nil
|
|
82
|
-
end
|
|
83
|
-
local result = middleware(message)(ctx)
|
|
84
|
-
if result == DropRequest then
|
|
85
|
-
return nil
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
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
|
|
89
81
|
return nil
|
|
90
82
|
end
|
|
91
|
-
local
|
|
92
|
-
|
|
83
|
+
local _serverQueue = self.serverQueue
|
|
84
|
+
local _arg0 = { message, newData, unreliable }
|
|
85
|
+
table.insert(_serverQueue, _arg0)
|
|
86
|
+
if not self.options.batchRemotes then
|
|
87
|
+
self:update()
|
|
88
|
+
end
|
|
93
89
|
end)
|
|
94
90
|
end,
|
|
95
91
|
invoke = TS.async(function(message, returnMessage, data, unreliable)
|
|
96
92
|
if unreliable == nil then
|
|
97
93
|
unreliable = false
|
|
98
94
|
end
|
|
95
|
+
if RunService:IsServer() then
|
|
96
|
+
error("[@rbxts/tether]: Cannot invoke server function from server")
|
|
97
|
+
end
|
|
99
98
|
local _clientFunctions = self.clientFunctions
|
|
100
99
|
local _returnMessage = returnMessage
|
|
101
100
|
if not (_clientFunctions[_returnMessage] ~= nil) then
|
|
@@ -118,6 +117,9 @@ do
|
|
|
118
117
|
return returnValue
|
|
119
118
|
end),
|
|
120
119
|
setCallback = function(message, returnMessage, callback)
|
|
120
|
+
if RunService:IsClient() then
|
|
121
|
+
error(noServerListen)
|
|
122
|
+
end
|
|
121
123
|
return self.server.on(message, function(player, data)
|
|
122
124
|
local returnValue = callback(player, data)
|
|
123
125
|
self.client.emit(player, returnMessage, returnValue)
|
|
@@ -126,54 +128,37 @@ do
|
|
|
126
128
|
}
|
|
127
129
|
self.client = {
|
|
128
130
|
on = function(message, callback)
|
|
131
|
+
if RunService:IsServer() then
|
|
132
|
+
error(noClientListen)
|
|
133
|
+
end
|
|
129
134
|
return self:on(message, callback, self.clientCallbacks)
|
|
130
135
|
end,
|
|
131
136
|
once = function(message, callback)
|
|
137
|
+
if RunService:IsServer() then
|
|
138
|
+
error(noClientListen)
|
|
139
|
+
end
|
|
132
140
|
return self:once(message, callback, self.clientCallbacks)
|
|
133
141
|
end,
|
|
134
142
|
emit = function(player, message, data, unreliable)
|
|
135
143
|
if unreliable == nil then
|
|
136
144
|
unreliable = false
|
|
137
145
|
end
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return nil
|
|
141
|
-
end
|
|
142
|
-
local getPacket = function()
|
|
143
|
-
return self:getPacket(message, data)
|
|
144
|
-
end
|
|
145
|
-
if not self:validateData(message, data) then
|
|
146
|
-
return nil
|
|
146
|
+
if RunService:IsClient() then
|
|
147
|
+
error("[@rbxts/tether]: Cannot emit message to client from client")
|
|
147
148
|
end
|
|
148
149
|
task.spawn(function()
|
|
149
|
-
local
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
155
|
-
if not self:validateData(message, data) then
|
|
156
|
-
return nil
|
|
157
|
-
end
|
|
158
|
-
local result = globalMiddleware(message)(player, ctx)
|
|
159
|
-
if result == DropRequest then
|
|
160
|
-
return nil
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
for _, middleware in self.middleware:getClient(message) do
|
|
164
|
-
if not self:validateData(message, data) then
|
|
165
|
-
return nil
|
|
166
|
-
end
|
|
167
|
-
local result = middleware(message)(player, ctx)
|
|
168
|
-
if result == DropRequest then
|
|
169
|
-
return nil
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
if not self:validateData(message, data) then
|
|
150
|
+
local _binding = self:runClientMiddlewares(message, data)
|
|
151
|
+
local dropRequest = _binding[1]
|
|
152
|
+
local newData = _binding[2]
|
|
153
|
+
if dropRequest then
|
|
173
154
|
return nil
|
|
174
155
|
end
|
|
175
|
-
local
|
|
176
|
-
|
|
156
|
+
local _clientQueue = self.clientQueue
|
|
157
|
+
local _arg0 = { player, message, newData, unreliable }
|
|
158
|
+
table.insert(_clientQueue, _arg0)
|
|
159
|
+
if not self.options.batchRemotes then
|
|
160
|
+
self:update()
|
|
161
|
+
end
|
|
177
162
|
end)
|
|
178
163
|
end,
|
|
179
164
|
emitExcept = function(player, message, data, unreliable)
|
|
@@ -210,56 +195,31 @@ do
|
|
|
210
195
|
if unreliable == nil then
|
|
211
196
|
unreliable = false
|
|
212
197
|
end
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return nil
|
|
216
|
-
end
|
|
217
|
-
local getPacket = function()
|
|
218
|
-
return self:getPacket(message, data)
|
|
219
|
-
end
|
|
220
|
-
if not self:validateData(message, data) then
|
|
221
|
-
return nil
|
|
198
|
+
if RunService:IsClient() then
|
|
199
|
+
error("[@rbxts/tether]: Cannot emit message to all clients from client")
|
|
222
200
|
end
|
|
223
201
|
task.spawn(function()
|
|
224
|
-
local
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
local players = Players:GetPlayers()
|
|
230
|
-
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
231
|
-
for _1, player in players do
|
|
232
|
-
if not self:validateData(message, data) then
|
|
233
|
-
return nil
|
|
234
|
-
end
|
|
235
|
-
local result = globalMiddleware(message)(player, ctx)
|
|
236
|
-
if result == DropRequest then
|
|
237
|
-
return nil
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
for _, middleware in self.middleware:getClient(message) do
|
|
242
|
-
for _1, player in players do
|
|
243
|
-
if not self:validateData(message, data) then
|
|
244
|
-
return nil
|
|
245
|
-
end
|
|
246
|
-
local result = middleware(message)(player, ctx)
|
|
247
|
-
if result == DropRequest then
|
|
248
|
-
return nil
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
if not self:validateData(message, data) then
|
|
202
|
+
local _binding = self:runClientMiddlewares(message, data)
|
|
203
|
+
local dropRequest = _binding[1]
|
|
204
|
+
local newData = _binding[2]
|
|
205
|
+
if dropRequest then
|
|
253
206
|
return nil
|
|
254
207
|
end
|
|
255
|
-
local
|
|
256
|
-
|
|
208
|
+
local _clientBroadcastQueue = self.clientBroadcastQueue
|
|
209
|
+
local _arg0 = { message, newData, unreliable }
|
|
210
|
+
table.insert(_clientBroadcastQueue, _arg0)
|
|
211
|
+
if not self.options.batchRemotes then
|
|
212
|
+
self:update()
|
|
213
|
+
end
|
|
257
214
|
end)
|
|
258
215
|
end,
|
|
259
216
|
invoke = TS.async(function(message, returnMessage, player, data, unreliable)
|
|
260
217
|
if unreliable == nil then
|
|
261
218
|
unreliable = false
|
|
262
219
|
end
|
|
220
|
+
if RunService:IsClient() then
|
|
221
|
+
error("[@rbxts/tether]: Cannot invoke client function from client")
|
|
222
|
+
end
|
|
263
223
|
local _serverFunctions = self.serverFunctions
|
|
264
224
|
local _returnMessage = returnMessage
|
|
265
225
|
if not (_serverFunctions[_returnMessage] ~= nil) then
|
|
@@ -282,6 +242,9 @@ do
|
|
|
282
242
|
return returnValue
|
|
283
243
|
end),
|
|
284
244
|
setCallback = function(message, returnMessage, callback)
|
|
245
|
+
if RunService:IsServer() then
|
|
246
|
+
error(noClientListen)
|
|
247
|
+
end
|
|
285
248
|
return self.client.on(message, function(data)
|
|
286
249
|
local returnValue = callback(data)
|
|
287
250
|
self.server.emit(returnMessage, returnValue)
|
|
@@ -347,8 +310,8 @@ do
|
|
|
347
310
|
})
|
|
348
311
|
end
|
|
349
312
|
end
|
|
350
|
-
function MessageEmitter:create(meta)
|
|
351
|
-
local emitter = MessageEmitter.new()
|
|
313
|
+
function MessageEmitter:create(options, meta)
|
|
314
|
+
local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options))
|
|
352
315
|
if meta == nil then
|
|
353
316
|
warn(metaGenerationFailed)
|
|
354
317
|
return emitter:initialize()
|
|
@@ -365,16 +328,6 @@ do
|
|
|
365
328
|
end
|
|
366
329
|
return emitter:initialize()
|
|
367
330
|
end
|
|
368
|
-
function MessageEmitter:validateData(message, data)
|
|
369
|
-
local _guards = self.guards
|
|
370
|
-
local _message = message
|
|
371
|
-
local guard = _guards[_message]
|
|
372
|
-
local guardPassed = guard(data)
|
|
373
|
-
if not guardPassed then
|
|
374
|
-
warn(guardFailed(message, data))
|
|
375
|
-
end
|
|
376
|
-
return guardPassed
|
|
377
|
-
end
|
|
378
331
|
function MessageEmitter:initialize()
|
|
379
332
|
if RunService:IsClient() then
|
|
380
333
|
self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
|
|
@@ -391,8 +344,144 @@ do
|
|
|
391
344
|
return self:onRemoteFire(serializedPacket, player)
|
|
392
345
|
end))
|
|
393
346
|
end
|
|
347
|
+
local elapsed = 0
|
|
348
|
+
local _binding = self.options
|
|
349
|
+
local batchRemotes = _binding.batchRemotes
|
|
350
|
+
local batchRate = _binding.batchRate
|
|
351
|
+
if not batchRemotes then
|
|
352
|
+
return self
|
|
353
|
+
end
|
|
354
|
+
self.janitor:Add(RunService.Heartbeat:Connect(function(dt)
|
|
355
|
+
elapsed += dt
|
|
356
|
+
if elapsed >= batchRate then
|
|
357
|
+
elapsed -= batchRate
|
|
358
|
+
self:update()
|
|
359
|
+
end
|
|
360
|
+
end))
|
|
394
361
|
return self
|
|
395
362
|
end
|
|
363
|
+
function MessageEmitter:update()
|
|
364
|
+
for _, _binding in self.clientQueue do
|
|
365
|
+
local player = _binding[1]
|
|
366
|
+
local message = _binding[2]
|
|
367
|
+
local data = _binding[3]
|
|
368
|
+
local unreliable = _binding[4]
|
|
369
|
+
local remote = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
370
|
+
remote(player, self:getPacket(message, data))
|
|
371
|
+
end
|
|
372
|
+
self.clientQueue = {}
|
|
373
|
+
for _, _binding in self.clientBroadcastQueue do
|
|
374
|
+
local message = _binding[1]
|
|
375
|
+
local data = _binding[2]
|
|
376
|
+
local unreliable = _binding[3]
|
|
377
|
+
local remote = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
378
|
+
remote:broadcast(self:getPacket(message, data))
|
|
379
|
+
end
|
|
380
|
+
self.clientBroadcastQueue = {}
|
|
381
|
+
for _, _binding in self.serverQueue do
|
|
382
|
+
local message = _binding[1]
|
|
383
|
+
local data = _binding[2]
|
|
384
|
+
local unreliable = _binding[3]
|
|
385
|
+
local remote = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
|
|
386
|
+
remote(self:getPacket(message, data))
|
|
387
|
+
end
|
|
388
|
+
self.serverQueue = {}
|
|
389
|
+
end
|
|
390
|
+
function MessageEmitter:runClientMiddlewares(message, data, player)
|
|
391
|
+
if not self:validateData(message, data) then
|
|
392
|
+
return { true, data }
|
|
393
|
+
end
|
|
394
|
+
local players = player or Players:GetPlayers()
|
|
395
|
+
local ctx = {
|
|
396
|
+
message = message,
|
|
397
|
+
data = data,
|
|
398
|
+
updateData = function(newData)
|
|
399
|
+
data = newData
|
|
400
|
+
return nil
|
|
401
|
+
end,
|
|
402
|
+
getRawData = function()
|
|
403
|
+
return self:getPacket(message, data)
|
|
404
|
+
end,
|
|
405
|
+
}
|
|
406
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
407
|
+
local result = globalMiddleware(players, ctx)
|
|
408
|
+
if not self:validateData(message, data, "Invalid data after global client middleware") then
|
|
409
|
+
return { false, data }
|
|
410
|
+
end
|
|
411
|
+
if result == DropRequest then
|
|
412
|
+
self.middleware:notifyRequestDropped(message, "Global client middleware")
|
|
413
|
+
return { true, data }
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
417
|
+
local result = middleware(players, ctx)
|
|
418
|
+
if not self:validateData(message, data, "Invalid data after client middleware") then
|
|
419
|
+
return { false, data }
|
|
420
|
+
end
|
|
421
|
+
if result == DropRequest then
|
|
422
|
+
self.middleware:notifyRequestDropped(message, "Client middleware")
|
|
423
|
+
return { true, data }
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
if not self:validateData(message, data) then
|
|
427
|
+
return { true, data }
|
|
428
|
+
end
|
|
429
|
+
return { false, data }
|
|
430
|
+
end
|
|
431
|
+
function MessageEmitter:runServerMiddlewares(message, data)
|
|
432
|
+
if not self:validateData(message, data) then
|
|
433
|
+
return { true, data }
|
|
434
|
+
end
|
|
435
|
+
local ctx = {
|
|
436
|
+
message = message,
|
|
437
|
+
data = data,
|
|
438
|
+
updateData = function(newData)
|
|
439
|
+
data = newData
|
|
440
|
+
return nil
|
|
441
|
+
end,
|
|
442
|
+
getRawData = function()
|
|
443
|
+
return self:getPacket(message, data)
|
|
444
|
+
end,
|
|
445
|
+
}
|
|
446
|
+
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
447
|
+
if not self:validateData(message, data, "Invalid data after global server middleware") then
|
|
448
|
+
return { false, data }
|
|
449
|
+
end
|
|
450
|
+
local result = globalMiddleware(ctx)
|
|
451
|
+
if result == DropRequest then
|
|
452
|
+
self.middleware:notifyRequestDropped(message, "Global server middleware")
|
|
453
|
+
return { true, data }
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
for _, middleware in self.middleware:getServer(message) do
|
|
457
|
+
if not self:validateData(message, data, "Invalid data after server middleware") then
|
|
458
|
+
return { false, data }
|
|
459
|
+
end
|
|
460
|
+
local result = middleware(ctx)
|
|
461
|
+
if result == DropRequest then
|
|
462
|
+
self.middleware:notifyRequestDropped(message, "Server middleware")
|
|
463
|
+
return { true, data }
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
if not self:validateData(message, data) then
|
|
467
|
+
return { true, data }
|
|
468
|
+
end
|
|
469
|
+
return { false, data }
|
|
470
|
+
end
|
|
471
|
+
function MessageEmitter:validateData(message, data, requestDropReason)
|
|
472
|
+
if requestDropReason == nil then
|
|
473
|
+
requestDropReason = "Invalid data"
|
|
474
|
+
end
|
|
475
|
+
local _guards = self.guards
|
|
476
|
+
local _message = message
|
|
477
|
+
local guard = _guards[_message]
|
|
478
|
+
local guardPassed = guard(data)
|
|
479
|
+
if not guardPassed then
|
|
480
|
+
warn(guardFailed(message, data))
|
|
481
|
+
self.middleware:notifyRequestDropped(message, requestDropReason)
|
|
482
|
+
end
|
|
483
|
+
return guardPassed
|
|
484
|
+
end
|
|
396
485
|
function MessageEmitter:onRemoteFire(serializedPacket, player)
|
|
397
486
|
if buffer.len(serializedPacket.messageBuffer) > 1 then
|
|
398
487
|
return warn("[@rbxts/tether]: Rejected message because message buffer was larger than one byte")
|
|
@@ -409,12 +498,7 @@ do
|
|
|
409
498
|
if functions == nil then
|
|
410
499
|
return nil
|
|
411
500
|
end
|
|
412
|
-
local
|
|
413
|
-
local _packet = serializer
|
|
414
|
-
if _packet ~= nil then
|
|
415
|
-
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
416
|
-
end
|
|
417
|
-
local packet = _packet
|
|
501
|
+
local packet = self:deserializeAndValidate(message, serializedPacket)
|
|
418
502
|
for callback in functions do
|
|
419
503
|
callback(packet)
|
|
420
504
|
end
|
|
@@ -427,12 +511,7 @@ do
|
|
|
427
511
|
if callbacks == nil then
|
|
428
512
|
return nil
|
|
429
513
|
end
|
|
430
|
-
local
|
|
431
|
-
local _packet = serializer
|
|
432
|
-
if _packet ~= nil then
|
|
433
|
-
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
434
|
-
end
|
|
435
|
-
local packet = _packet
|
|
514
|
+
local packet = self:deserializeAndValidate(message, serializedPacket)
|
|
436
515
|
for callback in callbacks do
|
|
437
516
|
if isServer then
|
|
438
517
|
callback(player, packet)
|
|
@@ -441,6 +520,16 @@ do
|
|
|
441
520
|
end
|
|
442
521
|
end
|
|
443
522
|
end
|
|
523
|
+
function MessageEmitter:deserializeAndValidate(message, serializedPacket)
|
|
524
|
+
local serializer = self:getSerializer(message)
|
|
525
|
+
local _packet = serializer
|
|
526
|
+
if _packet ~= nil then
|
|
527
|
+
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
528
|
+
end
|
|
529
|
+
local packet = _packet
|
|
530
|
+
self:validateData(message, packet)
|
|
531
|
+
return packet
|
|
532
|
+
end
|
|
444
533
|
function MessageEmitter:once(message, callback, callbacksMap)
|
|
445
534
|
local destructor
|
|
446
535
|
destructor = self:on(message, function(player, data)
|
package/out/middleware.d.ts
CHANGED
|
@@ -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
|
-
} & ((
|
|
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> = (
|
|
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 */
|
package/out/middleware.luau
CHANGED
|
@@ -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(
|
|
81
|
-
return
|
|
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(
|
|
136
|
-
return
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/tether",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
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"
|