@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 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 { Middleware } from "./middleware";
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
- function rateLimit<Data>(interval: number): Middleware<Data>;
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 { MiddlewareProvider, DropRequest, type ClientMiddleware, type ServerMiddleware, type Middleware } from "./middleware";
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
- local _middleware = TS.import(script, script, "middleware")
7
- exports.MiddlewareProvider = _middleware.MiddlewareProvider
8
- exports.DropRequest = _middleware.DropRequest
6
+ exports.DropRequest = TS.import(script, script, "middleware").DropRequest
9
7
  return exports
@@ -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
- private readonly middleware?;
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>(middleware?: MiddlewareProvider<MessageData>, metaForEachMessage?: Modding.Many<{
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;
@@ -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 DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
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(middleware)
28
+ function MessageEmitter:constructor()
27
29
  super.constructor(self)
28
- self.middleware = 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(middleware, metaForEachMessage)
88
- local emitter = MessageEmitter.new(middleware)
89
+ function MessageEmitter:create(metaForEachMessage)
90
+ local emitter = MessageEmitter.new()
89
91
  if metaForEachMessage == nil then
90
- warn("[Tether]: Failed to generate serializer metadata for MessageEmitter")
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
@@ -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
- useClient<Kind extends keyof MessageData>(message: Kind, middlewares: ClientMiddleware<MessageData[Kind]> | ClientMiddleware<MessageData[Kind]>[], order?: number): this;
16
- useServer<Kind extends keyof MessageData>(message: Kind, middlewares: ServerMiddleware<MessageData[Kind]> | ServerMiddleware<MessageData[Kind]>[], order?: number): this;
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 {};
@@ -1,13 +1,5 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
- local function createSymbol(name)
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "out/init.lua",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",