@rbxts/tether 1.2.0 → 1.2.2
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 +196 -197
- package/out/builtin-middlewares.d.ts +9 -1
- package/out/builtin-middlewares.luau +27 -1
- package/out/message-emitter.d.ts +1 -1
- package/out/message-emitter.luau +22 -7
- package/out/middleware.d.ts +9 -6
- package/out/middleware.luau +4 -4
- package/package.json +49 -49
package/README.md
CHANGED
|
@@ -1,198 +1,197 @@
|
|
|
1
|
-
# Tether
|
|
2
|
-
A message-based networking solution for Roblox with automatic binary serialization and type validation.
|
|
3
|
-
|
|
4
|
-
> [!CAUTION]
|
|
5
|
-
> Depends on `rbxts-transformer-flamework`!
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
### In `shared/messaging.ts`
|
|
10
|
-
```ts
|
|
11
|
-
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
12
|
-
import { MessageEmitter } from "@rbxts/tether";
|
|
13
|
-
|
|
14
|
-
export const messaging = MessageEmitter.create<MessageData>();
|
|
15
|
-
|
|
16
|
-
export const enum Message {
|
|
17
|
-
Test,
|
|
18
|
-
Packed
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface MessageData {
|
|
22
|
-
[Message.Test]: {
|
|
23
|
-
readonly foo: string;
|
|
24
|
-
readonly n: DataType.u8;
|
|
25
|
-
};
|
|
26
|
-
[Message.Packed]: DataType.Packed<{
|
|
27
|
-
boolean1: boolean;
|
|
28
|
-
boolean2: boolean;
|
|
29
|
-
boolean3: boolean;
|
|
30
|
-
boolean4: boolean;
|
|
31
|
-
boolean5: boolean;
|
|
32
|
-
boolean6: boolean;
|
|
33
|
-
boolean7: boolean;
|
|
34
|
-
boolean8: boolean;
|
|
35
|
-
}>;
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> [!CAUTION]
|
|
40
|
-
> Every single message kind must implement an interface for it's data (in the example that would be the object
|
|
41
|
-
|
|
42
|
-
### Server
|
|
43
|
-
```ts
|
|
44
|
-
import { Message, messaging } from "shared/messaging";
|
|
45
|
-
|
|
46
|
-
messaging.server.on(Message.Test, (player, data) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
export const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
.
|
|
173
|
-
.
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
readonly
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
1
|
+
# Tether
|
|
2
|
+
A message-based networking solution for Roblox with automatic binary serialization and type validation.
|
|
3
|
+
|
|
4
|
+
> [!CAUTION]
|
|
5
|
+
> Depends on `rbxts-transformer-flamework`!
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### In `shared/messaging.ts`
|
|
10
|
+
```ts
|
|
11
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
12
|
+
import { MessageEmitter } from "@rbxts/tether";
|
|
13
|
+
|
|
14
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
15
|
+
|
|
16
|
+
export const enum Message {
|
|
17
|
+
Test,
|
|
18
|
+
Packed
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MessageData {
|
|
22
|
+
[Message.Test]: {
|
|
23
|
+
readonly foo: string;
|
|
24
|
+
readonly n: DataType.u8;
|
|
25
|
+
};
|
|
26
|
+
[Message.Packed]: DataType.Packed<{
|
|
27
|
+
readonly boolean1: boolean;
|
|
28
|
+
readonly boolean2: boolean;
|
|
29
|
+
readonly boolean3: boolean;
|
|
30
|
+
readonly boolean4: boolean;
|
|
31
|
+
readonly boolean5: boolean;
|
|
32
|
+
readonly boolean6: boolean;
|
|
33
|
+
readonly boolean7: boolean;
|
|
34
|
+
readonly boolean8: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> [!CAUTION]
|
|
40
|
+
> 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
|
+
|
|
42
|
+
### Server
|
|
43
|
+
```ts
|
|
44
|
+
import { Message, messaging } from "shared/messaging";
|
|
45
|
+
|
|
46
|
+
messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Client
|
|
50
|
+
```ts
|
|
51
|
+
import { Message, messaging } from "shared/messaging";
|
|
52
|
+
|
|
53
|
+
messaging.server.emit(Message.Test, {
|
|
54
|
+
foo: "bar",
|
|
55
|
+
n: 69
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Simulated Remote Functions
|
|
60
|
+
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
|
+
|
|
62
|
+
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
|
+
|
|
64
|
+
### In `shared/messaging.ts`
|
|
65
|
+
```ts
|
|
66
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
67
|
+
import { MessageEmitter } from "@rbxts/tether";
|
|
68
|
+
|
|
69
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
70
|
+
|
|
71
|
+
export const enum Message {
|
|
72
|
+
Increment,
|
|
73
|
+
IncrementReturn
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface MessageData {
|
|
77
|
+
[Message.Increment]: DataType.u8;
|
|
78
|
+
[Message.IncrementReturn]: DataType.u8;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Server
|
|
83
|
+
```ts
|
|
84
|
+
import { Message, messaging } from "shared/messaging";
|
|
85
|
+
|
|
86
|
+
messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Client
|
|
90
|
+
```ts
|
|
91
|
+
import { Message, messaging } from "shared/messaging";
|
|
92
|
+
|
|
93
|
+
messaging.server
|
|
94
|
+
.invoke(Message.Increment, Message.IncrementReturn, 69)
|
|
95
|
+
.then(print); // 70 - incremented by the server
|
|
96
|
+
|
|
97
|
+
// or use await style
|
|
98
|
+
async function main(): Promise<void> {
|
|
99
|
+
const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
|
|
100
|
+
print(value) // 70
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Middleware
|
|
107
|
+
Drop, delay, or modify requests
|
|
108
|
+
|
|
109
|
+
### Creating middleware
|
|
110
|
+
|
|
111
|
+
**Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
|
|
112
|
+
#### Client
|
|
113
|
+
```ts
|
|
114
|
+
import type { ClientMiddleware } from "@rbxts/tether";
|
|
115
|
+
|
|
116
|
+
export function logClient(): ClientMiddleware {
|
|
117
|
+
return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Server
|
|
122
|
+
```ts
|
|
123
|
+
import type { ServerMiddleware } from "@rbxts/tether";
|
|
124
|
+
|
|
125
|
+
export function logServer(): ServerMiddleware {
|
|
126
|
+
return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Shared
|
|
131
|
+
```ts
|
|
132
|
+
import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
|
|
133
|
+
|
|
134
|
+
export function rateLimit(interval: number): SharedMiddleware {
|
|
135
|
+
let lastRequest = 0;
|
|
136
|
+
|
|
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;
|
|
141
|
+
|
|
142
|
+
lastRequest = os.clock();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Transforming data
|
|
148
|
+
```ts
|
|
149
|
+
import type { ServerMiddleware } from "@rbxts/tether";
|
|
150
|
+
|
|
151
|
+
export function incrementNumberData(): ServerMiddleware<number> {
|
|
152
|
+
// 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);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Using middleware
|
|
158
|
+
```ts
|
|
159
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
160
|
+
import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
|
|
161
|
+
|
|
162
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
163
|
+
messaging.middleware
|
|
164
|
+
// only allows requests to the server every 5 seconds,
|
|
165
|
+
// drops any requests that occur within 5 seconds of each other
|
|
166
|
+
.useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
|
|
167
|
+
// will be just one byte!
|
|
168
|
+
.useShared(Message.Packed, () => ctx => print("Packed object size:", buffer.len(ctx.getRawData().buffer)));
|
|
169
|
+
// logs every message fired
|
|
170
|
+
.useServerGlobal(logServer())
|
|
171
|
+
.useClientGlobal(logClient())
|
|
172
|
+
.useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
|
|
173
|
+
.useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
|
|
174
|
+
.useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
|
|
175
|
+
|
|
176
|
+
export const enum Message {
|
|
177
|
+
Test,
|
|
178
|
+
Packed
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface MessageData {
|
|
182
|
+
[Message.Test]: {
|
|
183
|
+
readonly foo: string;
|
|
184
|
+
readonly n: DataType.u8;
|
|
185
|
+
};
|
|
186
|
+
[Message.Packed]: DataType.Packed<{
|
|
187
|
+
readonly boolean1: boolean;
|
|
188
|
+
readonly boolean2: boolean;
|
|
189
|
+
readonly boolean3: boolean;
|
|
190
|
+
readonly boolean4: boolean;
|
|
191
|
+
readonly boolean5: boolean;
|
|
192
|
+
readonly boolean6: boolean;
|
|
193
|
+
readonly boolean7: boolean;
|
|
194
|
+
readonly boolean8: boolean;
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
198
197
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Modding } from "@flamework/core";
|
|
2
2
|
import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
|
|
3
|
+
import type { Any } from "ts-toolbelt";
|
|
3
4
|
import { type SharedMiddleware } from "./middleware";
|
|
4
5
|
import type { TetherPacket } from "./structs";
|
|
5
|
-
import { Any } from "ts-toolbelt";
|
|
6
6
|
export declare namespace BuiltinMiddlewares {
|
|
7
7
|
/**
|
|
8
8
|
* Creates a shared middleware that will simulate a ping of the given amount when a message is sent
|
|
@@ -11,6 +11,14 @@ export declare namespace BuiltinMiddlewares {
|
|
|
11
11
|
* @returns A shared middleware that will simulate a ping
|
|
12
12
|
*/
|
|
13
13
|
function simulatePing(pingInMs: number): SharedMiddleware;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
|
|
16
|
+
*
|
|
17
|
+
* @param maxBytes The maximum size of the packet in bytes
|
|
18
|
+
* @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
|
|
19
|
+
* @returns A shared middleware that will check if a message packet exceeds the given maximum size
|
|
20
|
+
*/
|
|
21
|
+
function maxPacketSize(maxBytes: number, throwError?: boolean): SharedMiddleware;
|
|
14
22
|
/**
|
|
15
23
|
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
16
24
|
*
|
|
@@ -24,6 +24,30 @@ do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
_container.simulatePing = simulatePing
|
|
27
|
+
--[[
|
|
28
|
+
*
|
|
29
|
+
* Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
|
|
30
|
+
*
|
|
31
|
+
* @param maxBytes The maximum size of the packet in bytes
|
|
32
|
+
* @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
|
|
33
|
+
* @returns A shared middleware that will check if a message packet exceeds the given maximum size
|
|
34
|
+
|
|
35
|
+
]]
|
|
36
|
+
local function maxPacketSize(maxBytes, throwError)
|
|
37
|
+
if throwError == nil then
|
|
38
|
+
throwError = true
|
|
39
|
+
end
|
|
40
|
+
return function(message)
|
|
41
|
+
return function(ctx)
|
|
42
|
+
local rawData = ctx.getRawData()
|
|
43
|
+
local totalSize = buffer.len(rawData.buffer) + #rawData.blobs * BLOB_SIZE
|
|
44
|
+
if totalSize > maxBytes then
|
|
45
|
+
return if throwError then error(`[@rbxts/tether]: Message '{message}' exceeded maximum packet size of {maxBytes} bytes`) else DropRequest
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
_container.maxPacketSize = maxPacketSize
|
|
27
51
|
--[[
|
|
28
52
|
*
|
|
29
53
|
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
@@ -87,7 +111,9 @@ do
|
|
|
87
111
|
]]
|
|
88
112
|
local function debug(schema)
|
|
89
113
|
return function(message)
|
|
90
|
-
return function(
|
|
114
|
+
return function(_param)
|
|
115
|
+
local data = _param.data
|
|
116
|
+
local getRawData = _param.getRawData
|
|
91
117
|
local rawData = getRawData()
|
|
92
118
|
local bufferSize = buffer.len(rawData.buffer)
|
|
93
119
|
local blobsSize = #rawData.blobs * BLOB_SIZE
|
package/out/message-emitter.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
|
9
9
|
private readonly serverCallbacks;
|
|
10
10
|
private readonly serverFunctions;
|
|
11
11
|
private readonly guards;
|
|
12
|
-
private serializers;
|
|
12
|
+
private readonly serializers;
|
|
13
13
|
private serverEvents;
|
|
14
14
|
private clientEvents;
|
|
15
15
|
/** @metadata macro */
|
package/out/message-emitter.luau
CHANGED
|
@@ -61,11 +61,16 @@ do
|
|
|
61
61
|
return nil
|
|
62
62
|
end
|
|
63
63
|
task.spawn(function()
|
|
64
|
+
local ctx = {
|
|
65
|
+
data = data,
|
|
66
|
+
updateData = updateData,
|
|
67
|
+
getRawData = getPacket,
|
|
68
|
+
}
|
|
64
69
|
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
65
70
|
if not self:validateData(message, data) then
|
|
66
71
|
return nil
|
|
67
72
|
end
|
|
68
|
-
local result = globalMiddleware(message)(
|
|
73
|
+
local result = globalMiddleware(message)(ctx)
|
|
69
74
|
if result == DropRequest then
|
|
70
75
|
return nil
|
|
71
76
|
end
|
|
@@ -74,7 +79,7 @@ do
|
|
|
74
79
|
if not self:validateData(message, data) then
|
|
75
80
|
return nil
|
|
76
81
|
end
|
|
77
|
-
local result = middleware(message)(
|
|
82
|
+
local result = middleware(message)(ctx)
|
|
78
83
|
if result == DropRequest then
|
|
79
84
|
return nil
|
|
80
85
|
end
|
|
@@ -140,11 +145,16 @@ do
|
|
|
140
145
|
return nil
|
|
141
146
|
end
|
|
142
147
|
task.spawn(function()
|
|
148
|
+
local ctx = {
|
|
149
|
+
data = data,
|
|
150
|
+
updateData = updateData,
|
|
151
|
+
getRawData = getPacket,
|
|
152
|
+
}
|
|
143
153
|
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
144
154
|
if not self:validateData(message, data) then
|
|
145
155
|
return nil
|
|
146
156
|
end
|
|
147
|
-
local result = globalMiddleware(message)(player,
|
|
157
|
+
local result = globalMiddleware(message)(player, ctx)
|
|
148
158
|
if result == DropRequest then
|
|
149
159
|
return nil
|
|
150
160
|
end
|
|
@@ -153,7 +163,7 @@ do
|
|
|
153
163
|
if not self:validateData(message, data) then
|
|
154
164
|
return nil
|
|
155
165
|
end
|
|
156
|
-
local result = middleware(message)(player,
|
|
166
|
+
local result = middleware(message)(player, ctx)
|
|
157
167
|
if result == DropRequest then
|
|
158
168
|
return nil
|
|
159
169
|
end
|
|
@@ -180,12 +190,17 @@ do
|
|
|
180
190
|
return nil
|
|
181
191
|
end
|
|
182
192
|
task.spawn(function()
|
|
193
|
+
local ctx = {
|
|
194
|
+
data = data,
|
|
195
|
+
updateData = updateData,
|
|
196
|
+
getRawData = getPacket,
|
|
197
|
+
}
|
|
183
198
|
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
184
199
|
for _1, player in Players:GetPlayers() do
|
|
185
200
|
if not self:validateData(message, data) then
|
|
186
201
|
return nil
|
|
187
202
|
end
|
|
188
|
-
local result = globalMiddleware(message)(player,
|
|
203
|
+
local result = globalMiddleware(message)(player, ctx)
|
|
189
204
|
if result == DropRequest then
|
|
190
205
|
return nil
|
|
191
206
|
end
|
|
@@ -196,7 +211,7 @@ do
|
|
|
196
211
|
if not self:validateData(message, data) then
|
|
197
212
|
return nil
|
|
198
213
|
end
|
|
199
|
-
local result = middleware(message)(player,
|
|
214
|
+
local result = middleware(message)(player, ctx)
|
|
200
215
|
if result == DropRequest then
|
|
201
216
|
return nil
|
|
202
217
|
end
|
|
@@ -244,7 +259,7 @@ do
|
|
|
244
259
|
self.janitor:Add(function()
|
|
245
260
|
table.clear(self.clientCallbacks)
|
|
246
261
|
table.clear(self.serverCallbacks)
|
|
247
|
-
self.serializers
|
|
262
|
+
table.clear(self.serializers)
|
|
248
263
|
self.serverEvents = nil
|
|
249
264
|
self.clientEvents = nil
|
|
250
265
|
end)
|
package/out/middleware.d.ts
CHANGED
|
@@ -3,16 +3,19 @@ type DropRequestSymbol = symbol & {
|
|
|
3
3
|
_drop_req?: undefined;
|
|
4
4
|
};
|
|
5
5
|
export declare const DropRequest: DropRequestSymbol;
|
|
6
|
-
type UpdateDataFn<T> = (newData: T) => void;
|
|
7
|
-
type GetRawDataFn = () => SerializedPacket;
|
|
8
6
|
export type ClientMiddleware<Data = unknown> = {
|
|
9
7
|
_client?: void;
|
|
10
|
-
} & ((message: BaseMessage) => (player: Player | Player[],
|
|
11
|
-
export type ServerMiddleware<Data = unknown> =
|
|
8
|
+
} & ((message: BaseMessage) => (player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
|
|
9
|
+
export type ServerMiddleware<Data = unknown> = {
|
|
12
10
|
_server?: void;
|
|
13
|
-
}
|
|
14
|
-
export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (
|
|
11
|
+
} & SharedMiddleware<Data>;
|
|
12
|
+
export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
|
|
15
13
|
export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
|
|
14
|
+
export interface MiddlewareContext<Data = unknown> {
|
|
15
|
+
readonly data: Readonly<Data>;
|
|
16
|
+
updateData: (newData: Data) => void;
|
|
17
|
+
getRawData: () => SerializedPacket;
|
|
18
|
+
}
|
|
16
19
|
export declare class MiddlewareProvider<MessageData> {
|
|
17
20
|
private readonly clientGlobalMiddlewares;
|
|
18
21
|
private readonly serverGlobalMiddlewares;
|
package/out/middleware.luau
CHANGED
|
@@ -78,8 +78,8 @@ do
|
|
|
78
78
|
local _newValue = table.create(#_exp)
|
|
79
79
|
local _callback = function(middleware)
|
|
80
80
|
return function(message)
|
|
81
|
-
return function(_,
|
|
82
|
-
return middleware(message)(
|
|
81
|
+
return function(_, ctx)
|
|
82
|
+
return middleware(message)(ctx)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|
|
@@ -133,8 +133,8 @@ do
|
|
|
133
133
|
local _newValue = table.create(#_exp)
|
|
134
134
|
local _callback = function(middleware)
|
|
135
135
|
return function(message)
|
|
136
|
-
return function(_,
|
|
137
|
-
return middleware(message)(
|
|
136
|
+
return function(_, ctx)
|
|
137
|
+
return middleware(message)(ctx)
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
end
|
package/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@rbxts/tether",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"main": "out/init.lua",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"build": "rbxtsc",
|
|
7
|
-
"watch": "rbxtsc -w",
|
|
8
|
-
"prepublishOnly": "npm run build"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"roblox",
|
|
12
|
-
"tether",
|
|
13
|
-
"networking",
|
|
14
|
-
"message",
|
|
15
|
-
"serialization",
|
|
16
|
-
"middleware"
|
|
17
|
-
],
|
|
18
|
-
"repository": {
|
|
19
|
-
"url": "git+https://github.com/R-unic/tether.git"
|
|
20
|
-
},
|
|
21
|
-
"author": "runicly",
|
|
22
|
-
"license": "ISC",
|
|
23
|
-
"description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
|
|
24
|
-
"types": "out/index.d.ts",
|
|
25
|
-
"files": [
|
|
26
|
-
"out",
|
|
27
|
-
"!**/*.tsbuildinfo"
|
|
28
|
-
],
|
|
29
|
-
"publishConfig": {
|
|
30
|
-
"access": "public"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
34
|
-
"@rbxts/types": "^1.0.835",
|
|
35
|
-
"rbxts-transformer-flamework": "^1.2.4",
|
|
36
|
-
"roblox-ts": "^3.0.0",
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"@flamework/
|
|
42
|
-
"@
|
|
43
|
-
"@rbxts/
|
|
44
|
-
"@rbxts/flamework-
|
|
45
|
-
"@rbxts/
|
|
46
|
-
"@rbxts/
|
|
47
|
-
"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@rbxts/tether",
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"main": "out/init.lua",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "rbxtsc",
|
|
7
|
+
"watch": "rbxtsc -w",
|
|
8
|
+
"prepublishOnly": "npm run build"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"roblox",
|
|
12
|
+
"tether",
|
|
13
|
+
"networking",
|
|
14
|
+
"message",
|
|
15
|
+
"serialization",
|
|
16
|
+
"middleware"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/R-unic/tether.git"
|
|
20
|
+
},
|
|
21
|
+
"author": "runicly",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
|
|
24
|
+
"types": "out/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"out",
|
|
27
|
+
"!**/*.tsbuildinfo"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
34
|
+
"@rbxts/types": "^1.0.835",
|
|
35
|
+
"rbxts-transformer-flamework": "^1.2.4",
|
|
36
|
+
"roblox-ts": "^3.0.0",
|
|
37
|
+
"typescript": "^5.5.3"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@flamework/core": "^1.2.4",
|
|
41
|
+
"@flamework/networking": "^1.2.4",
|
|
42
|
+
"@rbxts/destroyable": "^1.0.1",
|
|
43
|
+
"@rbxts/flamework-binary-serializer": "^0.6.0",
|
|
44
|
+
"@rbxts/flamework-meta-utils": "^1.0.4",
|
|
45
|
+
"@rbxts/repr": "^1.0.1",
|
|
46
|
+
"@rbxts/services": "^1.5.5",
|
|
47
|
+
"ts-toolbelt": "^9.6.0"
|
|
48
|
+
}
|
|
49
|
+
}
|