@rbxts/tether 1.4.0 → 1.4.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 +1 -1
- package/out/emitters/client-emitter.d.ts +49 -0
- package/out/emitters/client-emitter.luau +156 -0
- package/out/emitters/contextual-emitter.d.ts +26 -0
- package/out/emitters/contextual-emitter.luau +49 -0
- package/out/emitters/message-emitter.d.ts +41 -0
- package/out/emitters/message-emitter.luau +222 -0
- package/out/emitters/server-emitter.d.ts +30 -0
- package/out/emitters/server-emitter.luau +107 -0
- package/out/index.d.ts +1 -1
- package/out/init.luau +1 -1
- package/out/logging.d.ts +15 -0
- package/out/logging.luau +4 -0
- package/out/relayer.d.ts +16 -0
- package/out/relayer.luau +214 -0
- package/out/serdes.d.ts +13 -0
- package/out/serdes.luau +58 -0
- package/out/structs.d.ts +3 -2
- package/out/utility.d.ts +12 -0
- package/out/utility.luau +74 -0
- package/package.json +1 -1
- package/out/message-emitter.d.ts +0 -128
- package/out/message-emitter.luau +0 -792
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ messaging.server.emit(Message.Test, {
|
|
|
64
64
|
});
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
##
|
|
67
|
+
## Function Messages
|
|
68
68
|
|
|
69
69
|
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.
|
|
70
70
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ContextualEmitter } from "./contextual-emitter";
|
|
2
|
+
import type { BaseMessage, ClientMessageCallback, ClientFunctionMessageCallback } from "../structs";
|
|
3
|
+
export declare class ClientEmitter<MessageData> extends ContextualEmitter<MessageData> {
|
|
4
|
+
readonly context = "client";
|
|
5
|
+
readonly on: <K extends keyof MessageData>(this: ClientEmitter<MessageData>, message: K & BaseMessage, callback: ClientMessageCallback<MessageData[K]>) => () => void;
|
|
6
|
+
readonly once: <K extends keyof MessageData>(this: ClientEmitter<MessageData>, message: K & BaseMessage, callback: ClientMessageCallback<MessageData[K]>) => () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Emits a message to a specific client or multiple clients
|
|
9
|
+
*
|
|
10
|
+
* @param player The player(s) to whom the message is sent
|
|
11
|
+
* @param message The message kind to be sent
|
|
12
|
+
* @param data The data associated with the message
|
|
13
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
14
|
+
*/
|
|
15
|
+
emit<K extends keyof MessageData>(player: Player | Player[], message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
16
|
+
/**
|
|
17
|
+
* Emits a message to all clients except the specified client(s)
|
|
18
|
+
*
|
|
19
|
+
* @param player The player(s) to whom the message is not sent
|
|
20
|
+
* @param message The message kind to be sent
|
|
21
|
+
* @param data The data associated with the message
|
|
22
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
23
|
+
*/
|
|
24
|
+
emitExcept<K extends keyof MessageData>(player: Player | Player[], message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
25
|
+
/**
|
|
26
|
+
* Emits a message to all connected clients
|
|
27
|
+
*
|
|
28
|
+
* @param message The message kind to be sent
|
|
29
|
+
* @param data The data associated with the message
|
|
30
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
31
|
+
*/
|
|
32
|
+
emitAll<K extends keyof MessageData>(message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
33
|
+
/**
|
|
34
|
+
* Sets a callback for a simulated remote function
|
|
35
|
+
*
|
|
36
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
37
|
+
*/
|
|
38
|
+
setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: ClientFunctionMessageCallback<MessageData[K], MessageData[R]>): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Simulates a remote function invocation
|
|
41
|
+
*
|
|
42
|
+
* @param message The message kind to be sent
|
|
43
|
+
* @param returnMessage The message kind to be returned
|
|
44
|
+
* @param player The player to whom the function is sent
|
|
45
|
+
* @param data The data associated with the message
|
|
46
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
47
|
+
*/
|
|
48
|
+
invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, player: Player, data?: MessageData[K], unreliable?: boolean): Promise<MessageData[R]>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
4
|
+
local Players = _services.Players
|
|
5
|
+
local RunService = _services.RunService
|
|
6
|
+
local ContextualEmitter = TS.import(script, script.Parent, "contextual-emitter").ContextualEmitter
|
|
7
|
+
if setLuneContext == nil then
|
|
8
|
+
setLuneContext = function() end
|
|
9
|
+
end
|
|
10
|
+
local ClientEmitter
|
|
11
|
+
do
|
|
12
|
+
local super = ContextualEmitter
|
|
13
|
+
ClientEmitter = setmetatable({}, {
|
|
14
|
+
__tostring = function()
|
|
15
|
+
return "ClientEmitter"
|
|
16
|
+
end,
|
|
17
|
+
__index = super,
|
|
18
|
+
})
|
|
19
|
+
ClientEmitter.__index = ClientEmitter
|
|
20
|
+
function ClientEmitter.new(...)
|
|
21
|
+
local self = setmetatable({}, ClientEmitter)
|
|
22
|
+
return self:constructor(...) or self
|
|
23
|
+
end
|
|
24
|
+
function ClientEmitter:constructor(...)
|
|
25
|
+
super.constructor(self, ...)
|
|
26
|
+
self.context = "client"
|
|
27
|
+
end
|
|
28
|
+
function ClientEmitter:emit(player, message, data, unreliable)
|
|
29
|
+
if unreliable == nil then
|
|
30
|
+
unreliable = false
|
|
31
|
+
end
|
|
32
|
+
if RunService:IsClient() then
|
|
33
|
+
error("[tether::error] Cannot emit message from client to client")
|
|
34
|
+
end
|
|
35
|
+
task.spawn(function()
|
|
36
|
+
local _binding = self.master:runClientMiddlewares(message, data, player)
|
|
37
|
+
local dropRequest = _binding[1]
|
|
38
|
+
local newData = _binding[2]
|
|
39
|
+
if dropRequest then
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
self.master.relayer:queueMessage(self.context, message, { player, message, newData, unreliable })
|
|
43
|
+
end)
|
|
44
|
+
end
|
|
45
|
+
function ClientEmitter:emitExcept(player, message, data, unreliable)
|
|
46
|
+
if unreliable == nil then
|
|
47
|
+
unreliable = false
|
|
48
|
+
end
|
|
49
|
+
local shouldSendTo = function(p)
|
|
50
|
+
local _player = player
|
|
51
|
+
local _result
|
|
52
|
+
if typeof(_player) == "Instance" then
|
|
53
|
+
_result = p ~= player
|
|
54
|
+
else
|
|
55
|
+
local _player_1 = player
|
|
56
|
+
local _p = p
|
|
57
|
+
_result = not (table.find(_player_1, _p) ~= nil)
|
|
58
|
+
end
|
|
59
|
+
return _result
|
|
60
|
+
end
|
|
61
|
+
local _self = self
|
|
62
|
+
local _exp = Players:GetPlayers()
|
|
63
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
64
|
+
local _newValue = {}
|
|
65
|
+
local _length = 0
|
|
66
|
+
for _k, _v in _exp do
|
|
67
|
+
if shouldSendTo(_v, _k - 1, _exp) == true then
|
|
68
|
+
_length += 1
|
|
69
|
+
_newValue[_length] = _v
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
73
|
+
_self:emit(_newValue, message, data, unreliable)
|
|
74
|
+
end
|
|
75
|
+
function ClientEmitter:emitAll(message, data, unreliable)
|
|
76
|
+
if unreliable == nil then
|
|
77
|
+
unreliable = false
|
|
78
|
+
end
|
|
79
|
+
if RunService:IsClient() then
|
|
80
|
+
error("[tether::error] Cannot emit message from client to all clients")
|
|
81
|
+
end
|
|
82
|
+
task.spawn(function()
|
|
83
|
+
local _binding = self.master:runClientMiddlewares(message, data)
|
|
84
|
+
local dropRequest = _binding[1]
|
|
85
|
+
local newData = _binding[2]
|
|
86
|
+
if dropRequest then
|
|
87
|
+
return nil
|
|
88
|
+
end
|
|
89
|
+
self.master.relayer:queueMessage(true, message, { message, newData, unreliable })
|
|
90
|
+
end)
|
|
91
|
+
end
|
|
92
|
+
function ClientEmitter:setCallback(message, returnMessage, callback)
|
|
93
|
+
if RunService:IsServer() then
|
|
94
|
+
error("[tether::error] Cannot listen to server message from client")
|
|
95
|
+
end
|
|
96
|
+
return self:on(message, function(data)
|
|
97
|
+
local returnValue = callback(data)
|
|
98
|
+
-- Defer the response emission to end of frame and swap context to avoid context check issues
|
|
99
|
+
-- task.defer guarantees response is sent by end of current frame, ensuring predictable timing in production
|
|
100
|
+
task.defer(function()
|
|
101
|
+
setLuneContext("client")
|
|
102
|
+
self.master.server:emit(returnMessage, returnValue)
|
|
103
|
+
setLuneContext("both")
|
|
104
|
+
end)
|
|
105
|
+
end)
|
|
106
|
+
end
|
|
107
|
+
function ClientEmitter:invoke(message, returnMessage, player, data, unreliable)
|
|
108
|
+
if unreliable == nil then
|
|
109
|
+
unreliable = false
|
|
110
|
+
end
|
|
111
|
+
if RunService:IsClient() then
|
|
112
|
+
error("[tether::error] Cannot invoke function from client to client")
|
|
113
|
+
end
|
|
114
|
+
local _binding = self.master
|
|
115
|
+
local serverFunctions = _binding.serverFunctions
|
|
116
|
+
local _returnMessage = returnMessage
|
|
117
|
+
if not (serverFunctions[_returnMessage] ~= nil) then
|
|
118
|
+
local _returnMessage_1 = returnMessage
|
|
119
|
+
serverFunctions[_returnMessage_1] = {}
|
|
120
|
+
end
|
|
121
|
+
local _returnMessage_1 = returnMessage
|
|
122
|
+
local functions = serverFunctions[_returnMessage_1]
|
|
123
|
+
local returnValue
|
|
124
|
+
local responseCallback = function(data)
|
|
125
|
+
returnValue = data
|
|
126
|
+
return returnValue
|
|
127
|
+
end
|
|
128
|
+
functions[responseCallback] = true
|
|
129
|
+
self:emit(player, message, data, unreliable)
|
|
130
|
+
return TS.Promise.new(function(resolve, reject)
|
|
131
|
+
-- awful
|
|
132
|
+
local frames = 0
|
|
133
|
+
while true do
|
|
134
|
+
local _condition = returnValue == nil
|
|
135
|
+
if _condition then
|
|
136
|
+
local _original = frames
|
|
137
|
+
frames += 1
|
|
138
|
+
_condition = _original < 400
|
|
139
|
+
end
|
|
140
|
+
if not _condition then
|
|
141
|
+
break
|
|
142
|
+
end
|
|
143
|
+
RunService.Heartbeat:Wait()
|
|
144
|
+
end
|
|
145
|
+
if frames == 400 then
|
|
146
|
+
return reject("[tether::error] Client function timed out (no response)")
|
|
147
|
+
end
|
|
148
|
+
-- clean up the callback after receiving the response
|
|
149
|
+
functions[responseCallback] = nil
|
|
150
|
+
resolve(returnValue)
|
|
151
|
+
end)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
return {
|
|
155
|
+
ClientEmitter = ClientEmitter,
|
|
156
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MessageEmitter } from "./message-emitter";
|
|
2
|
+
import type { BaseMessage, MessageCallback, FunctionMessageCallback } from "../structs";
|
|
3
|
+
type Cleanup = () => void;
|
|
4
|
+
export declare abstract class ContextualEmitter<MessageData> {
|
|
5
|
+
protected readonly master: MessageEmitter<MessageData>;
|
|
6
|
+
abstract readonly context: "client" | "server";
|
|
7
|
+
/**
|
|
8
|
+
* Constructs a new ContextualEmitter.
|
|
9
|
+
* @param master The master emitter to which messages are delegated.
|
|
10
|
+
*/
|
|
11
|
+
constructor(master: MessageEmitter<MessageData>);
|
|
12
|
+
abstract emit<K extends keyof MessageData>(player: (K & BaseMessage) | (Player | Player[]), message?: MessageData[K] | (K & BaseMessage), data?: MessageData[K] | boolean, unreliable?: boolean): void;
|
|
13
|
+
abstract setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: FunctionMessageCallback<MessageData[K], MessageData[R]>): Cleanup;
|
|
14
|
+
abstract invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, player?: Player | MessageData[K], data?: MessageData[K] | boolean, unreliable?: boolean): Promise<MessageData[R]>;
|
|
15
|
+
/**
|
|
16
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
17
|
+
*/
|
|
18
|
+
on<K extends keyof MessageData>(message: K & BaseMessage, callback: MessageCallback<MessageData[K]>): Cleanup;
|
|
19
|
+
/**
|
|
20
|
+
* Disconnects the callback as soon as it is called for the first time
|
|
21
|
+
*
|
|
22
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
23
|
+
*/
|
|
24
|
+
once<K extends keyof MessageData>(message: K & BaseMessage, callback: MessageCallback<MessageData[K]>): Cleanup;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
4
|
+
local ContextualEmitter
|
|
5
|
+
do
|
|
6
|
+
ContextualEmitter = {}
|
|
7
|
+
function ContextualEmitter:constructor(master)
|
|
8
|
+
self.master = master
|
|
9
|
+
end
|
|
10
|
+
function ContextualEmitter:on(message, callback)
|
|
11
|
+
local isClient = self.context == "client"
|
|
12
|
+
if RunService:IsClient() and not isClient then
|
|
13
|
+
return error("[tether::error] Cannot listen to server message from client")
|
|
14
|
+
elseif RunService:IsServer() and isClient then
|
|
15
|
+
return error("[tether::error] Cannot listen to client message from server")
|
|
16
|
+
end
|
|
17
|
+
local callbacksMap = if isClient then self.master.clientCallbacks else self.master.serverCallbacks
|
|
18
|
+
local _message = message
|
|
19
|
+
if not (callbacksMap[_message] ~= nil) then
|
|
20
|
+
local _message_1 = message
|
|
21
|
+
callbacksMap[_message_1] = {}
|
|
22
|
+
end
|
|
23
|
+
local _message_1 = message
|
|
24
|
+
local callbacks = callbacksMap[_message_1]
|
|
25
|
+
local _callback = callback
|
|
26
|
+
callbacks[_callback] = true
|
|
27
|
+
local _message_2 = message
|
|
28
|
+
callbacksMap[_message_2] = callbacks
|
|
29
|
+
return function()
|
|
30
|
+
local _callback_1 = callback
|
|
31
|
+
-- ▼ Set.delete ▼
|
|
32
|
+
local _valueExisted = callbacks[_callback_1] ~= nil
|
|
33
|
+
callbacks[_callback_1] = nil
|
|
34
|
+
-- ▲ Set.delete ▲
|
|
35
|
+
return _valueExisted
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
function ContextualEmitter:once(message, callback)
|
|
39
|
+
local destructor
|
|
40
|
+
destructor = self:on(message, function(player, data)
|
|
41
|
+
destructor()
|
|
42
|
+
callback(player, data)
|
|
43
|
+
end)
|
|
44
|
+
return destructor
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
return {
|
|
48
|
+
ContextualEmitter = ContextualEmitter,
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Modding } from "@flamework/core";
|
|
2
|
+
import { Trash } from "@rbxts/trash";
|
|
3
|
+
import Destroyable from "@rbxts/destroyable";
|
|
4
|
+
import { MiddlewareProvider } from "../middleware";
|
|
5
|
+
import type { SerializedPacket, ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata } from "../structs";
|
|
6
|
+
import { ServerEmitter } from "./server-emitter";
|
|
7
|
+
import { ClientEmitter } from "./client-emitter";
|
|
8
|
+
import { Relayer } from "../relayer";
|
|
9
|
+
import { Serdes } from "../serdes";
|
|
10
|
+
export interface MessageEmitterOptions<MessageData> {
|
|
11
|
+
readonly batchRemotes: boolean;
|
|
12
|
+
readonly batchRate: number;
|
|
13
|
+
readonly doNotBatch: Set<keyof MessageData>;
|
|
14
|
+
}
|
|
15
|
+
export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
16
|
+
readonly options: MessageEmitterOptions<MessageData>;
|
|
17
|
+
readonly server: ServerEmitter<MessageData>;
|
|
18
|
+
readonly client: ClientEmitter<MessageData>;
|
|
19
|
+
readonly middleware: MiddlewareProvider<MessageData>;
|
|
20
|
+
/** @hidden */ readonly trash: Trash;
|
|
21
|
+
/** @hidden */ readonly relayer: Relayer<MessageData>;
|
|
22
|
+
/** @hidden */ readonly serdes: Serdes<MessageData>;
|
|
23
|
+
/** @hidden */ clientCallbacks: Map<keyof MessageData, Set<ClientMessageCallback>>;
|
|
24
|
+
/** @hidden */ clientFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
|
|
25
|
+
/** @hidden */ serverCallbacks: Map<keyof MessageData, Set<ServerMessageCallback>>;
|
|
26
|
+
/** @hidden */ serverFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
|
|
27
|
+
private readonly guards;
|
|
28
|
+
/** @metadata macro */
|
|
29
|
+
static create<MessageData>(options?: Partial<MessageEmitterOptions<MessageData>>, meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
|
|
30
|
+
private constructor();
|
|
31
|
+
/** @hidden */
|
|
32
|
+
runClientMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], player?: Player | Player[]): [boolean, MessageData[Kind]];
|
|
33
|
+
/** @hidden */
|
|
34
|
+
runServerMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind]): [boolean, MessageData[Kind]];
|
|
35
|
+
/** @hidden */
|
|
36
|
+
onRemoteFire(isServer: boolean, serializedPackets: SerializedPacket[], player?: Player): void;
|
|
37
|
+
private validateData;
|
|
38
|
+
private executeFunctions;
|
|
39
|
+
private executeEventCallbacks;
|
|
40
|
+
private deserializeAndValidate;
|
|
41
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
|
|
4
|
+
local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default
|
|
5
|
+
local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
|
|
6
|
+
local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
|
|
7
|
+
local _middleware = TS.import(script, script.Parent.Parent, "middleware")
|
|
8
|
+
local DropRequest = _middleware.DropRequest
|
|
9
|
+
local MiddlewareProvider = _middleware.MiddlewareProvider
|
|
10
|
+
local ServerEmitter = TS.import(script, script.Parent, "server-emitter").ServerEmitter
|
|
11
|
+
local ClientEmitter = TS.import(script, script.Parent, "client-emitter").ClientEmitter
|
|
12
|
+
local Relayer = TS.import(script, script.Parent.Parent, "relayer").Relayer
|
|
13
|
+
local Serdes = TS.import(script, script.Parent.Parent, "serdes").Serdes
|
|
14
|
+
local readMessage = TS.import(script, script.Parent.Parent, "utility").readMessage
|
|
15
|
+
if setLuneContext == nil then
|
|
16
|
+
setLuneContext = function() end
|
|
17
|
+
end
|
|
18
|
+
setLuneContext("both")
|
|
19
|
+
local guardFailed = function(message, data)
|
|
20
|
+
return `[tether::warning]: Type validation guard failed for message '{message}' - check your sent data\nSent data: {repr(data)}`
|
|
21
|
+
end
|
|
22
|
+
local defaultMesssageEmitterOptions = {
|
|
23
|
+
batchRemotes = true,
|
|
24
|
+
batchRate = 1 / 24,
|
|
25
|
+
doNotBatch = {},
|
|
26
|
+
}
|
|
27
|
+
local MessageEmitter
|
|
28
|
+
do
|
|
29
|
+
local super = Destroyable
|
|
30
|
+
MessageEmitter = setmetatable({}, {
|
|
31
|
+
__tostring = function()
|
|
32
|
+
return "MessageEmitter"
|
|
33
|
+
end,
|
|
34
|
+
__index = super,
|
|
35
|
+
})
|
|
36
|
+
MessageEmitter.__index = MessageEmitter
|
|
37
|
+
function MessageEmitter.new(...)
|
|
38
|
+
local self = setmetatable({}, MessageEmitter)
|
|
39
|
+
return self:constructor(...) or self
|
|
40
|
+
end
|
|
41
|
+
function MessageEmitter:constructor(options)
|
|
42
|
+
if options == nil then
|
|
43
|
+
options = defaultMesssageEmitterOptions
|
|
44
|
+
end
|
|
45
|
+
super.constructor(self)
|
|
46
|
+
self.options = options
|
|
47
|
+
self.server = ServerEmitter.new(self)
|
|
48
|
+
self.client = ClientEmitter.new(self)
|
|
49
|
+
self.middleware = MiddlewareProvider.new()
|
|
50
|
+
self.relayer = Relayer.new(self)
|
|
51
|
+
self.serdes = Serdes.new()
|
|
52
|
+
self.clientCallbacks = {}
|
|
53
|
+
self.clientFunctions = {}
|
|
54
|
+
self.serverCallbacks = {}
|
|
55
|
+
self.serverFunctions = {}
|
|
56
|
+
self.guards = {}
|
|
57
|
+
self.trash:add(function()
|
|
58
|
+
self.clientCallbacks = {}
|
|
59
|
+
self.serverCallbacks = {}
|
|
60
|
+
self.clientFunctions = {}
|
|
61
|
+
self.clientCallbacks = {}
|
|
62
|
+
self.serdes.serializers = {}
|
|
63
|
+
self.relayer:relayAll()
|
|
64
|
+
setmetatable(self, nil)
|
|
65
|
+
end)
|
|
66
|
+
end
|
|
67
|
+
function MessageEmitter:create(options, meta)
|
|
68
|
+
local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options))
|
|
69
|
+
if meta == nil then
|
|
70
|
+
warn("[tether::warning] Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas")
|
|
71
|
+
return emitter
|
|
72
|
+
end
|
|
73
|
+
-- lore
|
|
74
|
+
-- https://discord.com/channels/476080952636997633/506983834877689856/1363938149486821577
|
|
75
|
+
for kind, _binding in pairs(meta) do
|
|
76
|
+
local guard = _binding.guard
|
|
77
|
+
local serializerMetadata = _binding.serializerMetadata
|
|
78
|
+
local numberKind = tonumber(kind)
|
|
79
|
+
emitter.guards[numberKind] = guard
|
|
80
|
+
if serializerMetadata == nil then
|
|
81
|
+
continue
|
|
82
|
+
end
|
|
83
|
+
emitter.serdes:addSerializer(numberKind, serializerMetadata)
|
|
84
|
+
end
|
|
85
|
+
return emitter
|
|
86
|
+
end
|
|
87
|
+
function MessageEmitter:runClientMiddlewares(message, data, player)
|
|
88
|
+
if not self:validateData(message, data) then
|
|
89
|
+
return { true, data }
|
|
90
|
+
end
|
|
91
|
+
local players = player or Players:GetPlayers()
|
|
92
|
+
local ctx = {
|
|
93
|
+
message = message,
|
|
94
|
+
data = data,
|
|
95
|
+
getRawData = function()
|
|
96
|
+
return self.serdes:serializePacket(message, data)
|
|
97
|
+
end,
|
|
98
|
+
}
|
|
99
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
100
|
+
local result = globalMiddleware(players, ctx)
|
|
101
|
+
if not self:validateData(message, ctx.data, "Invalid data after global client middleware") then
|
|
102
|
+
return { false, ctx.data }
|
|
103
|
+
end
|
|
104
|
+
if result == DropRequest then
|
|
105
|
+
self.middleware:notifyRequestDropped(message, "Global client middleware")
|
|
106
|
+
return { true, ctx.data }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
110
|
+
local result = middleware(players, ctx)
|
|
111
|
+
if not self:validateData(message, ctx.data, "Invalid data after client middleware") then
|
|
112
|
+
return { false, ctx.data }
|
|
113
|
+
end
|
|
114
|
+
if result == DropRequest then
|
|
115
|
+
self.middleware:notifyRequestDropped(message, "Client middleware")
|
|
116
|
+
return { true, ctx.data }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
if not self:validateData(message, ctx.data) then
|
|
120
|
+
return { true, ctx.data }
|
|
121
|
+
end
|
|
122
|
+
return { false, ctx.data }
|
|
123
|
+
end
|
|
124
|
+
function MessageEmitter:runServerMiddlewares(message, data)
|
|
125
|
+
if not self:validateData(message, data) then
|
|
126
|
+
return { true, data }
|
|
127
|
+
end
|
|
128
|
+
local ctx = {
|
|
129
|
+
message = message,
|
|
130
|
+
data = data,
|
|
131
|
+
getRawData = function()
|
|
132
|
+
return self.serdes:serializePacket(message, data)
|
|
133
|
+
end,
|
|
134
|
+
}
|
|
135
|
+
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
136
|
+
if not self:validateData(message, ctx.data, "Invalid data after global server middleware") then
|
|
137
|
+
return { false, ctx.data }
|
|
138
|
+
end
|
|
139
|
+
local result = globalMiddleware(ctx)
|
|
140
|
+
if result == DropRequest then
|
|
141
|
+
self.middleware:notifyRequestDropped(message, "Global server middleware")
|
|
142
|
+
return { true, ctx.data }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
for _, middleware in self.middleware:getServer(message) do
|
|
146
|
+
if not self:validateData(message, ctx.data, "Invalid data after server middleware") then
|
|
147
|
+
return { false, ctx.data }
|
|
148
|
+
end
|
|
149
|
+
local result = middleware(ctx)
|
|
150
|
+
if result == DropRequest then
|
|
151
|
+
self.middleware:notifyRequestDropped(message, "Server middleware")
|
|
152
|
+
return { true, ctx.data }
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
if not self:validateData(message, ctx.data) then
|
|
156
|
+
return { true, ctx.data }
|
|
157
|
+
end
|
|
158
|
+
return { false, ctx.data }
|
|
159
|
+
end
|
|
160
|
+
function MessageEmitter:onRemoteFire(isServer, serializedPackets, player)
|
|
161
|
+
for _, packet in serializedPackets do
|
|
162
|
+
if buffer.len(packet.messageBuf) > 1 then
|
|
163
|
+
return warn("[tether::warning] Rejected packet because message buffer was larger than one byte")
|
|
164
|
+
end
|
|
165
|
+
local message = readMessage(packet)
|
|
166
|
+
self:executeEventCallbacks(isServer, message, packet, player)
|
|
167
|
+
self:executeFunctions(isServer, message, packet)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
function MessageEmitter:validateData(message, data, requestDropReason)
|
|
171
|
+
if requestDropReason == nil then
|
|
172
|
+
requestDropReason = "Invalid data"
|
|
173
|
+
end
|
|
174
|
+
local _guards = self.guards
|
|
175
|
+
local _message = message
|
|
176
|
+
local guard = _guards[_message]
|
|
177
|
+
local guardPassed = guard(data)
|
|
178
|
+
if not guardPassed then
|
|
179
|
+
warn(guardFailed(message, data))
|
|
180
|
+
self.middleware:notifyRequestDropped(message, requestDropReason)
|
|
181
|
+
end
|
|
182
|
+
return guardPassed
|
|
183
|
+
end
|
|
184
|
+
function MessageEmitter:executeFunctions(isServer, message, serializedPacket)
|
|
185
|
+
local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
|
|
186
|
+
local _message = message
|
|
187
|
+
local functions = functionsMap[_message]
|
|
188
|
+
if functions == nil then
|
|
189
|
+
return nil
|
|
190
|
+
end
|
|
191
|
+
local packet = self:deserializeAndValidate(message, serializedPacket)
|
|
192
|
+
for callback in functions do
|
|
193
|
+
callback(packet)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
function MessageEmitter:executeEventCallbacks(isServer, message, serializedPacket, player)
|
|
197
|
+
local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks
|
|
198
|
+
local _message = message
|
|
199
|
+
local callbacks = callbacksMap[_message]
|
|
200
|
+
if callbacks == nil then
|
|
201
|
+
return nil
|
|
202
|
+
end
|
|
203
|
+
local packet = self:deserializeAndValidate(message, serializedPacket)
|
|
204
|
+
for callback in callbacks do
|
|
205
|
+
if isServer then
|
|
206
|
+
local _arg0 = player ~= nil
|
|
207
|
+
assert(_arg0)
|
|
208
|
+
callback(player, packet)
|
|
209
|
+
else
|
|
210
|
+
callback(packet)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
function MessageEmitter:deserializeAndValidate(message, serializedPacket)
|
|
215
|
+
local packet = self.serdes:deserializePacket(message, serializedPacket)
|
|
216
|
+
self:validateData(message, packet)
|
|
217
|
+
return packet
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
return {
|
|
221
|
+
MessageEmitter = MessageEmitter,
|
|
222
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ContextualEmitter } from "./contextual-emitter";
|
|
2
|
+
import type { BaseMessage, ServerMessageCallback, ServerFunctionMessageCallback } from "../structs";
|
|
3
|
+
export declare class ServerEmitter<MessageData> extends ContextualEmitter<MessageData> {
|
|
4
|
+
readonly context = "server";
|
|
5
|
+
readonly on: <K extends keyof MessageData>(this: ServerEmitter<MessageData>, message: K & BaseMessage, callback: ServerMessageCallback<MessageData[K]>) => () => void;
|
|
6
|
+
readonly once: <K extends keyof MessageData>(this: ServerEmitter<MessageData>, message: K & BaseMessage, callback: ServerMessageCallback<MessageData[K]>) => () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Emits a message to the server
|
|
9
|
+
*
|
|
10
|
+
* @param message The message kind to be sent
|
|
11
|
+
* @param data The data associated with the message
|
|
12
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
13
|
+
*/
|
|
14
|
+
emit<K extends keyof MessageData>(message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
15
|
+
/**
|
|
16
|
+
* Sets a callback for a simulated remote function
|
|
17
|
+
*
|
|
18
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
19
|
+
*/
|
|
20
|
+
setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: ServerFunctionMessageCallback<MessageData[K], MessageData[R]>): () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Simulates a remote function invocation
|
|
23
|
+
*
|
|
24
|
+
* @param message The message kind to be sent
|
|
25
|
+
* @param returnMessage The message kind to be returned
|
|
26
|
+
* @param data The data associated with the message
|
|
27
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
28
|
+
*/
|
|
29
|
+
invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, data?: MessageData[K], unreliable?: boolean): Promise<MessageData[R]>;
|
|
30
|
+
}
|