@rbxts/tether 1.0.5 → 1.0.7
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 +49 -1
- package/out/builtin-middlewares.d.ts +28 -2
- package/out/builtin-middlewares.luau +57 -0
- package/out/index.d.ts +1 -1
- package/out/init.luau +1 -3
- package/out/message-emitter.d.ts +4 -4
- package/out/message-emitter.luau +8 -6
- package/out/middleware.d.ts +15 -4
- package/out/middleware.luau +55 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Tether
|
|
2
|
-
A message-based networking solution for Roblox with automatic binary serialization
|
|
2
|
+
A message-based networking solution for Roblox with automatic binary serialization.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
3
5
|
|
|
4
6
|
### In `shared/messaging.ts`
|
|
5
7
|
```ts
|
|
@@ -40,4 +42,50 @@ messaging.emitServer(Message.TEST, {
|
|
|
40
42
|
foo: "bar",
|
|
41
43
|
n: 69
|
|
42
44
|
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Middleware
|
|
48
|
+
|
|
49
|
+
### Creating middleware
|
|
50
|
+
```ts
|
|
51
|
+
import { type Middleware, DropRequest } from "@rbxts/tether";
|
|
52
|
+
|
|
53
|
+
export function rateLimit(interval: number): Middleware {
|
|
54
|
+
let lastRequest = 0;
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
if (os.clock() - lastRequest < interval)
|
|
58
|
+
return DropRequest;
|
|
59
|
+
|
|
60
|
+
lastRequest = os.clock();
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Using middleware
|
|
66
|
+
```ts
|
|
67
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
68
|
+
import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
|
|
69
|
+
|
|
70
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
71
|
+
messaging.middleware
|
|
72
|
+
// only allows requests to the server every 5 seconds,
|
|
73
|
+
// drops any requests that occur within 5 seconds of each other
|
|
74
|
+
.useServer(Message.Test, [BuiltinMiddlewares.rateLimit(5)])
|
|
75
|
+
// automatically validates that data sent through the remote
|
|
76
|
+
// matches the data associated with the message at runtime
|
|
77
|
+
.useShared(Message.Test, [BuiltinMiddlewares.validateClient()])
|
|
78
|
+
// rate limit every server remote (global)
|
|
79
|
+
.useServerGlobal([BuiltinMiddlewares.rateLimit(1)]);
|
|
80
|
+
|
|
81
|
+
export const enum Message {
|
|
82
|
+
Test
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MessageData {
|
|
86
|
+
[Message.Test]: {
|
|
87
|
+
readonly foo: string;
|
|
88
|
+
readonly n: DataType.u8;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
43
91
|
```
|
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Modding } from "@flamework/core";
|
|
2
|
+
import { type SharedMiddleware, type ServerMiddleware, type ClientMiddleware } from "./middleware";
|
|
3
|
+
type Guard<T> = (value: unknown) => value is T;
|
|
2
4
|
export declare namespace BuiltinMiddlewares {
|
|
3
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Creates a universal middleware that will drop any message that occurs within the given interval of the previous message.
|
|
7
|
+
* @param interval The interval in seconds that the middleware should wait before allowing a new request.
|
|
8
|
+
* @returns A middleware that will drop any message that occurs within the given interval.
|
|
9
|
+
*/
|
|
10
|
+
function rateLimit(interval: number): SharedMiddleware;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
|
|
13
|
+
* If the guard fails, the middleware will drop the message.
|
|
14
|
+
*
|
|
15
|
+
* @param guard The guard to use to validate the data.
|
|
16
|
+
* @returns A server middleware that validates the data with the given guard.
|
|
17
|
+
* @macro
|
|
18
|
+
*/
|
|
19
|
+
function validateServer<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ServerMiddleware<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
|
|
22
|
+
* If the guard fails, the middleware will drop the message.
|
|
23
|
+
*
|
|
24
|
+
* @param guard The guard to use to validate the data.
|
|
25
|
+
* @returns A server middleware that validates the data with the given guard.
|
|
26
|
+
* @macro
|
|
27
|
+
*/
|
|
28
|
+
function validateClient<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ClientMiddleware<T>;
|
|
4
29
|
}
|
|
30
|
+
export {};
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
|
|
4
|
+
local noOp = function() end
|
|
5
|
+
local validationGuardGenerationFailed = function(context)
|
|
6
|
+
return `[@rbxts/tether]: Failed to generate guard for validate{context}<T> builtin middleware - skipping validation`
|
|
7
|
+
end
|
|
4
8
|
local BuiltinMiddlewares = {}
|
|
5
9
|
do
|
|
6
10
|
local _container = BuiltinMiddlewares
|
|
11
|
+
--[[
|
|
12
|
+
*
|
|
13
|
+
* Creates a universal middleware that will drop any message that occurs within the given interval of the previous message.
|
|
14
|
+
* @param interval The interval in seconds that the middleware should wait before allowing a new request.
|
|
15
|
+
* @returns A middleware that will drop any message that occurs within the given interval.
|
|
16
|
+
|
|
17
|
+
]]
|
|
7
18
|
local function rateLimit(interval)
|
|
8
19
|
local lastRequest = 0
|
|
9
20
|
return function()
|
|
@@ -14,6 +25,52 @@ do
|
|
|
14
25
|
end
|
|
15
26
|
end
|
|
16
27
|
_container.rateLimit = rateLimit
|
|
28
|
+
--[[
|
|
29
|
+
*
|
|
30
|
+
* Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
|
|
31
|
+
* If the guard fails, the middleware will drop the message.
|
|
32
|
+
*
|
|
33
|
+
* @param guard The guard to use to validate the data.
|
|
34
|
+
* @returns A server middleware that validates the data with the given guard.
|
|
35
|
+
* @macro
|
|
36
|
+
|
|
37
|
+
]]
|
|
38
|
+
local function validateServer(guard)
|
|
39
|
+
if guard == nil then
|
|
40
|
+
warn(validationGuardGenerationFailed("Server"))
|
|
41
|
+
return noOp
|
|
42
|
+
end
|
|
43
|
+
return function(data)
|
|
44
|
+
if guard(data) then
|
|
45
|
+
return nil
|
|
46
|
+
end
|
|
47
|
+
return DropRequest
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
_container.validateServer = validateServer
|
|
51
|
+
--[[
|
|
52
|
+
*
|
|
53
|
+
* Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
|
|
54
|
+
* If the guard fails, the middleware will drop the message.
|
|
55
|
+
*
|
|
56
|
+
* @param guard The guard to use to validate the data.
|
|
57
|
+
* @returns A server middleware that validates the data with the given guard.
|
|
58
|
+
* @macro
|
|
59
|
+
|
|
60
|
+
]]
|
|
61
|
+
local function validateClient(guard)
|
|
62
|
+
if guard == nil then
|
|
63
|
+
warn(validationGuardGenerationFailed("Client"))
|
|
64
|
+
return noOp
|
|
65
|
+
end
|
|
66
|
+
return function(data)
|
|
67
|
+
if guard(data) then
|
|
68
|
+
return nil
|
|
69
|
+
end
|
|
70
|
+
return DropRequest
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
_container.validateClient = validateClient
|
|
17
74
|
end
|
|
18
75
|
return {
|
|
19
76
|
BuiltinMiddlewares = BuiltinMiddlewares,
|
package/out/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { MessageEmitter } from "./message-emitter";
|
|
2
2
|
export { BuiltinMiddlewares } from "./builtin-middlewares";
|
|
3
|
-
export {
|
|
3
|
+
export { DropRequest, type ClientMiddleware, type ServerMiddleware, type Middleware } from "./middleware";
|
package/out/init.luau
CHANGED
|
@@ -3,7 +3,5 @@ local TS = _G[script]
|
|
|
3
3
|
local exports = {}
|
|
4
4
|
exports.MessageEmitter = TS.import(script, script, "message-emitter").MessageEmitter
|
|
5
5
|
exports.BuiltinMiddlewares = TS.import(script, script, "builtin-middlewares").BuiltinMiddlewares
|
|
6
|
-
|
|
7
|
-
exports.MiddlewareProvider = _middleware.MiddlewareProvider
|
|
8
|
-
exports.DropRequest = _middleware.DropRequest
|
|
6
|
+
exports.DropRequest = TS.import(script, script, "middleware").DropRequest
|
|
9
7
|
return exports
|
package/out/message-emitter.d.ts
CHANGED
|
@@ -4,23 +4,23 @@ import Destroyable from "@rbxts/destroyable";
|
|
|
4
4
|
import { MiddlewareProvider } from "./middleware";
|
|
5
5
|
import type { ClientMessageCallback, ServerMessageCallback } from "./structs";
|
|
6
6
|
export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
7
|
-
|
|
7
|
+
readonly middleware: MiddlewareProvider<MessageData>;
|
|
8
8
|
private readonly clientCallbacks;
|
|
9
9
|
private readonly serverCallbacks;
|
|
10
10
|
private serializers;
|
|
11
11
|
private serverEvents;
|
|
12
12
|
private clientEvents;
|
|
13
13
|
/** @metadata macro */
|
|
14
|
-
static create<MessageData>(
|
|
14
|
+
static create<MessageData>(metaForEachMessage?: Modding.Many<{
|
|
15
15
|
[Kind in keyof MessageData]: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<MessageData[Kind]>>;
|
|
16
16
|
}>): MessageEmitter<MessageData>;
|
|
17
17
|
private constructor();
|
|
18
18
|
/**.
|
|
19
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
19
|
+
* @returns A destructor function that disconnects the callback from the message.
|
|
20
20
|
*/
|
|
21
21
|
onServerMessage<Kind extends keyof MessageData>(message: Kind, callback: ServerMessageCallback<MessageData[Kind]>): () => void;
|
|
22
22
|
/**.
|
|
23
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
23
|
+
* @returns A destructor function that disconnects the callback from the message.
|
|
24
24
|
*/
|
|
25
25
|
onClientMessage<Kind extends keyof MessageData>(message: Kind, callback: ClientMessageCallback<MessageData[Kind]>): () => void;
|
|
26
26
|
private on;
|
package/out/message-emitter.luau
CHANGED
|
@@ -7,7 +7,9 @@ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
|
7
7
|
local Players = _services.Players
|
|
8
8
|
local RunService = _services.RunService
|
|
9
9
|
local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default
|
|
10
|
-
local
|
|
10
|
+
local _middleware = TS.import(script, script.Parent, "middleware")
|
|
11
|
+
local DropRequest = _middleware.DropRequest
|
|
12
|
+
local MiddlewareProvider = _middleware.MiddlewareProvider
|
|
11
13
|
local GlobalEvents = Networking.createEvent("@rbxts/tether:message-emitter@GlobalEvents")
|
|
12
14
|
local MessageEmitter
|
|
13
15
|
do
|
|
@@ -23,9 +25,9 @@ do
|
|
|
23
25
|
local self = setmetatable({}, MessageEmitter)
|
|
24
26
|
return self:constructor(...) or self
|
|
25
27
|
end
|
|
26
|
-
function MessageEmitter:constructor(
|
|
28
|
+
function MessageEmitter:constructor()
|
|
27
29
|
super.constructor(self)
|
|
28
|
-
self.middleware =
|
|
30
|
+
self.middleware = MiddlewareProvider.new()
|
|
29
31
|
self.clientCallbacks = {}
|
|
30
32
|
self.serverCallbacks = {}
|
|
31
33
|
self.serializers = {}
|
|
@@ -84,10 +86,10 @@ do
|
|
|
84
86
|
})
|
|
85
87
|
end
|
|
86
88
|
end
|
|
87
|
-
function MessageEmitter:create(
|
|
88
|
-
local emitter = MessageEmitter.new(
|
|
89
|
+
function MessageEmitter:create(metaForEachMessage)
|
|
90
|
+
local emitter = MessageEmitter.new()
|
|
89
91
|
if metaForEachMessage == nil then
|
|
90
|
-
warn("[
|
|
92
|
+
warn("[@rbxts/tether]: Failed to generate serializer metadata for MessageEmitter")
|
|
91
93
|
return emitter:initialize()
|
|
92
94
|
end
|
|
93
95
|
for kind, meta in pairs(metaForEachMessage) do
|
package/out/middleware.d.ts
CHANGED
|
@@ -2,17 +2,28 @@ type DropRequestSymbol = symbol & {
|
|
|
2
2
|
_skip_middleware?: undefined;
|
|
3
3
|
};
|
|
4
4
|
export declare const DropRequest: DropRequestSymbol;
|
|
5
|
-
export type ClientMiddleware<Data = unknown> = (player: Player, data: Data | undefined) => DropRequestSymbol | void;
|
|
6
|
-
export type ServerMiddleware<Data = unknown> = (data: Data | undefined) => DropRequestSymbol | void;
|
|
5
|
+
export type ClientMiddleware<Data = unknown> = (player: Player, data: Readonly<Data> | undefined) => DropRequestSymbol | void;
|
|
6
|
+
export type ServerMiddleware<Data = unknown> = (data: Readonly<Data> | undefined) => DropRequestSymbol | void;
|
|
7
|
+
export type SharedMiddleware = () => DropRequestSymbol | void;
|
|
7
8
|
export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data>;
|
|
8
9
|
export declare class MiddlewareProvider<MessageData> {
|
|
10
|
+
private readonly clientGlobalMiddlewares;
|
|
11
|
+
private readonly serverGlobalMiddlewares;
|
|
9
12
|
private readonly clientMiddlewares;
|
|
10
13
|
private readonly serverMiddlewares;
|
|
11
14
|
/** @hidden */
|
|
12
15
|
getClient<Kind extends keyof MessageData>(message: Kind): ClientMiddleware<MessageData[Kind]>[];
|
|
13
16
|
/** @hidden */
|
|
14
17
|
getServer<Kind extends keyof MessageData>(message: Kind): ServerMiddleware<MessageData[Kind]>[];
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
/** @hidden */
|
|
19
|
+
getClientGlobal<Data>(): ClientMiddleware<Data>[];
|
|
20
|
+
/** @hidden */
|
|
21
|
+
getServerGlobal<Data>(): ServerMiddleware<Data>[];
|
|
22
|
+
useClient<Kind extends keyof MessageData>(message: Kind, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[], order?: number): this;
|
|
23
|
+
useServer<Kind extends keyof MessageData>(message: Kind, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[], order?: number): this;
|
|
24
|
+
useShared<Kind extends keyof MessageData>(message: Kind, middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
|
25
|
+
useClientGlobal<Data>(middlewares: ClientMiddleware<Data> | readonly ClientMiddleware<Data>[], order?: number): this;
|
|
26
|
+
useServerGlobal<Data>(middlewares: ServerMiddleware<Data> | readonly ServerMiddleware<Data>[], order?: number): this;
|
|
27
|
+
useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
|
17
28
|
}
|
|
18
29
|
export {};
|
package/out/middleware.luau
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local
|
|
3
|
-
local symbol = newproxy(true)
|
|
4
|
-
local mt = getmetatable(symbol)
|
|
5
|
-
mt.__tostring = function()
|
|
6
|
-
return name
|
|
7
|
-
end
|
|
8
|
-
return symbol
|
|
9
|
-
end
|
|
10
|
-
local DropRequest = createSymbol("DropRequest")
|
|
2
|
+
local DropRequest = newproxy()
|
|
11
3
|
local MiddlewareProvider
|
|
12
4
|
do
|
|
13
5
|
MiddlewareProvider = setmetatable({}, {
|
|
@@ -21,6 +13,8 @@ do
|
|
|
21
13
|
return self:constructor(...) or self
|
|
22
14
|
end
|
|
23
15
|
function MiddlewareProvider:constructor()
|
|
16
|
+
self.clientGlobalMiddlewares = {}
|
|
17
|
+
self.serverGlobalMiddlewares = {}
|
|
24
18
|
self.clientMiddlewares = {}
|
|
25
19
|
self.serverMiddlewares = {}
|
|
26
20
|
end
|
|
@@ -36,6 +30,12 @@ do
|
|
|
36
30
|
end
|
|
37
31
|
return self.serverMiddlewares[message]
|
|
38
32
|
end
|
|
33
|
+
function MiddlewareProvider:getClientGlobal()
|
|
34
|
+
return self.clientGlobalMiddlewares
|
|
35
|
+
end
|
|
36
|
+
function MiddlewareProvider:getServerGlobal()
|
|
37
|
+
return self.serverGlobalMiddlewares
|
|
38
|
+
end
|
|
39
39
|
function MiddlewareProvider:useClient(message, middlewares, order)
|
|
40
40
|
local messageMiddleware = self:getClient(message)
|
|
41
41
|
local _middlewares = middlewares
|
|
@@ -48,7 +48,7 @@ do
|
|
|
48
48
|
table.insert(messageMiddleware, _condition + 1, _middlewares_1)
|
|
49
49
|
else
|
|
50
50
|
for _, middleware in middlewares do
|
|
51
|
-
self:useClient(message, middleware)
|
|
51
|
+
self:useClient(message, middleware, order)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
return self
|
|
@@ -65,11 +65,55 @@ do
|
|
|
65
65
|
table.insert(messageMiddleware, _condition + 1, _middlewares_1)
|
|
66
66
|
else
|
|
67
67
|
for _, middleware in middlewares do
|
|
68
|
-
self:useServer(message, middleware)
|
|
68
|
+
self:useServer(message, middleware, order)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
return self
|
|
72
|
+
end
|
|
73
|
+
function MiddlewareProvider:useShared(message, middlewares, order)
|
|
74
|
+
self:useClient(message, middlewares, order)
|
|
75
|
+
self:useServer(message, middlewares, order)
|
|
76
|
+
return self
|
|
77
|
+
end
|
|
78
|
+
function MiddlewareProvider:useClientGlobal(middlewares, order)
|
|
79
|
+
local globalMiddleware = self:getClientGlobal()
|
|
80
|
+
local _middlewares = middlewares
|
|
81
|
+
if typeof(_middlewares) == "function" then
|
|
82
|
+
local _condition = order
|
|
83
|
+
if _condition == nil then
|
|
84
|
+
_condition = #globalMiddleware - 1
|
|
85
|
+
end
|
|
86
|
+
local _middlewares_1 = middlewares
|
|
87
|
+
table.insert(globalMiddleware, _condition + 1, _middlewares_1)
|
|
88
|
+
else
|
|
89
|
+
for _, middleware in middlewares do
|
|
90
|
+
self:useClientGlobal(middleware, order)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
return self
|
|
94
|
+
end
|
|
95
|
+
function MiddlewareProvider:useServerGlobal(middlewares, order)
|
|
96
|
+
local globalMiddleware = self:getServerGlobal()
|
|
97
|
+
local _middlewares = middlewares
|
|
98
|
+
if typeof(_middlewares) == "function" then
|
|
99
|
+
local _condition = order
|
|
100
|
+
if _condition == nil then
|
|
101
|
+
_condition = #globalMiddleware - 1
|
|
102
|
+
end
|
|
103
|
+
local _middlewares_1 = middlewares
|
|
104
|
+
table.insert(globalMiddleware, _condition + 1, _middlewares_1)
|
|
105
|
+
else
|
|
106
|
+
for _, middleware in middlewares do
|
|
107
|
+
self:useServerGlobal(middleware, order)
|
|
69
108
|
end
|
|
70
109
|
end
|
|
71
110
|
return self
|
|
72
111
|
end
|
|
112
|
+
function MiddlewareProvider:useSharedGlobal(middlewares, order)
|
|
113
|
+
self:useClientGlobal(middlewares, order)
|
|
114
|
+
self:useServerGlobal(middlewares, order)
|
|
115
|
+
return self
|
|
116
|
+
end
|
|
73
117
|
end
|
|
74
118
|
return {
|
|
75
119
|
DropRequest = DropRequest,
|