@axpecter/lync 1.4.3 → 1.5.0

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.
Files changed (52) hide show
  1. package/README.md +41 -2
  2. package/package.json +1 -1
  3. package/src/Types.luau +13 -1
  4. package/src/api/Group.luau +35 -28
  5. package/src/api/Namespace.luau +107 -89
  6. package/src/api/Packet.luau +69 -51
  7. package/src/api/Query.luau +101 -95
  8. package/src/api/Scope.luau +32 -18
  9. package/src/api/Signal.luau +78 -56
  10. package/src/codec/Base.luau +17 -17
  11. package/src/codec/composite/Array.luau +74 -74
  12. package/src/codec/composite/Map.luau +80 -80
  13. package/src/codec/composite/Optional.luau +14 -14
  14. package/src/codec/composite/Shared.luau +35 -35
  15. package/src/codec/composite/Struct.luau +108 -108
  16. package/src/codec/composite/Tagged.luau +71 -71
  17. package/src/codec/composite/Tuple.luau +35 -35
  18. package/src/codec/datatype/Buffer.luau +18 -18
  19. package/src/codec/datatype/CFrame.luau +24 -24
  20. package/src/codec/datatype/Color.luau +8 -12
  21. package/src/codec/datatype/Instance.luau +13 -13
  22. package/src/codec/datatype/IntVector.luau +17 -17
  23. package/src/codec/datatype/NumberRange.luau +7 -7
  24. package/src/codec/datatype/Ray.luau +16 -16
  25. package/src/codec/datatype/Rect.luau +13 -13
  26. package/src/codec/datatype/Region.luau +32 -36
  27. package/src/codec/datatype/Sequence.luau +41 -41
  28. package/src/codec/datatype/String.luau +28 -28
  29. package/src/codec/datatype/UDim.luau +17 -17
  30. package/src/codec/datatype/Vector.luau +14 -18
  31. package/src/codec/meta/Auto.luau +75 -73
  32. package/src/codec/meta/Bitfield.luau +46 -46
  33. package/src/codec/meta/Custom.luau +7 -7
  34. package/src/codec/meta/Enum.luau +25 -25
  35. package/src/codec/meta/Nothing.luau +3 -3
  36. package/src/codec/meta/Quantized.luau +63 -60
  37. package/src/codec/meta/Unknown.luau +11 -11
  38. package/src/codec/primitive/Bool.luau +12 -12
  39. package/src/codec/primitive/Float16.luau +28 -28
  40. package/src/codec/primitive/Number.luau +41 -41
  41. package/src/codec/primitive/Varint.luau +19 -19
  42. package/src/init.luau +81 -61
  43. package/src/internal/Baseline.luau +7 -7
  44. package/src/internal/Channel.luau +56 -56
  45. package/src/internal/Middleware.luau +29 -29
  46. package/src/internal/Pool.luau +15 -15
  47. package/src/internal/Registry.luau +19 -19
  48. package/src/transport/Bridge.luau +17 -17
  49. package/src/transport/Client.luau +46 -46
  50. package/src/transport/Gate.luau +56 -64
  51. package/src/transport/Reader.luau +34 -34
  52. package/src/transport/Server.luau +89 -89
@@ -1,13 +1,13 @@
1
1
  --!strict
2
- --!optimize 2
2
+ --!native
3
3
  -- Post-deserialization security: NaN/inf, rate limiting, custom validate.
4
4
 
5
- local Types = require (script.Parent.Parent.Types)
5
+ local Types = require(script.Parent.Parent.Types)
6
6
 
7
7
  type Registration = Types.Registration
8
8
  type RateLimitConfig = Types.RateLimitConfig
9
9
 
10
- -- Constants ----------------------------------------------------------
10
+ -- Constants -----------------------------------------------------------
11
11
 
12
12
  local DEFAULT_MAX_DEPTH = 16
13
13
 
@@ -15,7 +15,7 @@ local isfinite = math.isfinite
15
15
  local min = math.min
16
16
  local clock = os.clock
17
17
 
18
- -- State --------------------------------------------------------------
18
+ -- State ---------------------------------------------------------------
19
19
 
20
20
  type Bucket = {
21
21
  tokens: number,
@@ -27,24 +27,23 @@ local _buckets = {} :: { [Player]: { [number]: Bucket } }
27
27
  local _dropHandlers =
28
28
  {} :: { (player: Player, reason: string, packetName: string, data: any?) -> () }
29
29
 
30
- -- Private ------------------------------------------------------------
30
+ -- Private -------------------------------------------------------------
31
31
 
32
- local function fireDrop (player: Player, reason: string, packetName: string, data: any?): ()
32
+ local function fireDrop(player: Player, reason: string, packetName: string, data: any?): ()
33
33
  for _, handler in _dropHandlers do
34
- handler (player, reason, packetName, data)
34
+ handler(player, reason, packetName, data)
35
35
  end
36
36
  end
37
37
 
38
- @native
39
- local function isClean (value: any, depth: number): boolean
38
+ local function isClean(value: any, depth: number): boolean
40
39
  if depth > _maxDepth then
41
40
  return false
42
41
  end
43
42
 
44
- local t = type (value)
43
+ local t = type(value)
45
44
 
46
45
  if t == "number" then
47
- return isfinite (value)
46
+ return isfinite(value)
48
47
  end
49
48
 
50
49
  -- String, boolean, nil are always clean. Exit before table/vector/userdata checks.
@@ -55,7 +54,7 @@ local function isClean (value: any, depth: number): boolean
55
54
  if t == "table" then
56
55
  local deeper = depth + 1
57
56
  for k, v in value do
58
- if not isClean (k, deeper) or not isClean (v, deeper) then
57
+ if not isClean(k, deeper) or not isClean(v, deeper) then
59
58
  return false
60
59
  end
61
60
  end
@@ -63,60 +62,53 @@ local function isClean (value: any, depth: number): boolean
63
62
  end
64
63
 
65
64
  if t == "vector" then
66
- return isfinite (value.X) and isfinite (value.Y) and isfinite (value.Z)
65
+ return isfinite(value.X) and isfinite(value.Y) and isfinite(value.Z)
67
66
  end
68
67
 
69
68
  if t == "userdata" then
70
- local ut = typeof (value)
69
+ local ut = typeof(value)
71
70
 
72
71
  if ut == "Vector2" then
73
- return isfinite (value.X) and isfinite (value.Y)
72
+ return isfinite(value.X) and isfinite(value.Y)
74
73
  elseif ut == "Color3" then
75
- return isfinite (value.R) and isfinite (value.G) and isfinite (value.B)
74
+ return isfinite(value.R) and isfinite(value.G) and isfinite(value.B)
76
75
  elseif ut == "CFrame" then
77
76
  local position = value.Position
78
- if
79
- not isfinite (position.X)
80
- or not isfinite (position.Y)
81
- or not isfinite (position.Z)
82
- then
77
+ if not isfinite(position.X) or not isfinite(position.Y) or not isfinite(position.Z) then
83
78
  return false
84
79
  end
85
- local axis, angle = value:ToAxisAngle ()
86
- return isfinite (angle)
87
- and isfinite (axis.X)
88
- and isfinite (axis.Y)
89
- and isfinite (axis.Z)
80
+ local axis, angle = value:ToAxisAngle()
81
+ return isfinite(angle) and isfinite(axis.X) and isfinite(axis.Y) and isfinite(axis.Z)
90
82
  elseif ut == "UDim" then
91
- return isfinite (value.Scale)
83
+ return isfinite(value.Scale)
92
84
  elseif ut == "UDim2" then
93
- return isfinite (value.X.Scale) and isfinite (value.Y.Scale)
85
+ return isfinite(value.X.Scale) and isfinite(value.Y.Scale)
94
86
  elseif ut == "NumberRange" then
95
- return isfinite (value.Min) and isfinite (value.Max)
87
+ return isfinite(value.Min) and isfinite(value.Max)
96
88
  elseif ut == "Rect" then
97
- return isfinite (value.Min.X)
98
- and isfinite (value.Min.Y)
99
- and isfinite (value.Max.X)
100
- and isfinite (value.Max.Y)
89
+ return isfinite(value.Min.X)
90
+ and isfinite(value.Min.Y)
91
+ and isfinite(value.Max.X)
92
+ and isfinite(value.Max.Y)
101
93
  elseif ut == "Ray" then
102
94
  local origin = value.Origin
103
95
  local direction = value.Direction
104
- return isfinite (origin.X)
105
- and isfinite (origin.Y)
106
- and isfinite (origin.Z)
107
- and isfinite (direction.X)
108
- and isfinite (direction.Y)
109
- and isfinite (direction.Z)
96
+ return isfinite(origin.X)
97
+ and isfinite(origin.Y)
98
+ and isfinite(origin.Z)
99
+ and isfinite(direction.X)
100
+ and isfinite(direction.Y)
101
+ and isfinite(direction.Z)
110
102
  elseif ut == "Region3" then
111
103
  local position = value.CFrame.Position
112
- return isfinite (position.X) and isfinite (position.Y) and isfinite (position.Z)
104
+ return isfinite(position.X) and isfinite(position.Y) and isfinite(position.Z)
113
105
  end
114
106
  end
115
107
 
116
108
  return true
117
109
  end
118
110
 
119
- local function checkRate (player: Player, reg: Registration): boolean
111
+ local function checkRate(player: Player, reg: Registration): boolean
120
112
  local cfg = reg.rateLimit
121
113
  if not cfg then
122
114
  return true
@@ -128,7 +120,7 @@ local function checkRate (player: Player, reg: Registration): boolean
128
120
  end
129
121
 
130
122
  local id = reg.id
131
- local now = clock ()
123
+ local now = clock()
132
124
  local cap = cfg.burstAllowance or cfg.maxPerSecond
133
125
  local bucket = playerBuckets[id]
134
126
 
@@ -141,7 +133,7 @@ local function checkRate (player: Player, reg: Registration): boolean
141
133
  end
142
134
 
143
135
  local elapsed = now - bucket.lastRef
144
- bucket.tokens = min (cap, bucket.tokens + elapsed * cfg.maxPerSecond)
136
+ bucket.tokens = min(cap, bucket.tokens + elapsed * cfg.maxPerSecond)
145
137
  bucket.lastRef = now
146
138
 
147
139
  if bucket.tokens >= 1 then
@@ -152,38 +144,38 @@ local function checkRate (player: Player, reg: Registration): boolean
152
144
  return false
153
145
  end
154
146
 
155
- -- Public -------------------------------------------------------------
147
+ -- Public --------------------------------------------------------------
156
148
 
157
149
  local Gate = {}
158
150
 
159
151
  -- Override the max recursion depth for NaN/inf scanning. Default 16. Range: 4–32.
160
- function Gate.setMaxDepth (depth: number): ()
152
+ function Gate.setMaxDepth(depth: number): ()
161
153
  if depth < 4 or depth > 32 then
162
- error (`[Lync] Gate max depth must be 4–32, got {depth}`)
154
+ error(`[Lync] Gate max depth must be 4–32, got {depth}`)
163
155
  end
164
156
  _maxDepth = depth
165
157
  end
166
158
 
167
- function Gate.getMaxDepth (): number
159
+ function Gate.getMaxDepth(): number
168
160
  return _maxDepth
169
161
  end
170
162
 
171
- function Gate.check (value: any, reg: Registration, player: Player): boolean
172
- if not checkRate (player, reg) then
173
- fireDrop (player, "rate", reg.name, nil)
163
+ function Gate.check(value: any, reg: Registration, player: Player): boolean
164
+ if not checkRate(player, reg) then
165
+ fireDrop(player, "rate", reg.name, nil)
174
166
  return false
175
167
  end
176
168
 
177
- if not isClean (value, 1) then
178
- fireDrop (player, "nan", reg.name, value)
169
+ if not isClean(value, 1) then
170
+ fireDrop(player, "nan", reg.name, value)
179
171
  return false
180
172
  end
181
173
 
182
174
  local validate = reg.validate
183
175
  if validate then
184
- local ok, reason = validate (value, player)
176
+ local ok, reason = validate(value, player)
185
177
  if not ok then
186
- fireDrop (player, reason or "validate", reg.name, value)
178
+ fireDrop(player, reason or "validate", reg.name, value)
187
179
  return false
188
180
  end
189
181
  end
@@ -191,32 +183,32 @@ function Gate.check (value: any, reg: Registration, player: Player): boolean
191
183
  return true
192
184
  end
193
185
 
194
- function Gate.onDrop (
186
+ function Gate.onDrop(
195
187
  callback: (player: Player, reason: string, packetName: string, data: any?) -> ()
196
188
  ): () -> ()
197
- table.insert (_dropHandlers, callback)
189
+ table.insert(_dropHandlers, callback)
198
190
 
199
- return function (): ()
200
- local idx = table.find (_dropHandlers, callback)
191
+ return function(): ()
192
+ local idx = table.find(_dropHandlers, callback)
201
193
  if idx then
202
- table.remove (_dropHandlers, idx)
194
+ table.remove(_dropHandlers, idx)
203
195
  end
204
196
  end
205
197
  end
206
198
 
207
199
  -- Fires drop handlers without validation. Used by Reader for byte-budget violations.
208
- function Gate.reportDrop (player: Player?, reason: string, packetName: string, data: any?): ()
200
+ function Gate.reportDrop(player: Player?, reason: string, packetName: string, data: any?): ()
209
201
  if player and #_dropHandlers > 0 then
210
- fireDrop (player, reason, packetName, data)
202
+ fireDrop(player, reason, packetName, data)
211
203
  end
212
204
  end
213
205
 
214
- function Gate.onPlayerAdded (player: Player): ()
206
+ function Gate.onPlayerAdded(player: Player): ()
215
207
  _buckets[player] = {}
216
208
  end
217
209
 
218
- function Gate.onPlayerRemoved (player: Player): ()
210
+ function Gate.onPlayerRemoved(player: Player): ()
219
211
  _buckets[player] = nil
220
212
  end
221
213
 
222
- return table.freeze (Gate)
214
+ return table.freeze(Gate)
@@ -2,49 +2,49 @@
2
2
  --!optimize 2
3
3
  -- Deserializes incoming buffers and routes by registration kind.
4
4
 
5
- local Baseline = require (script.Parent.Parent.internal.Baseline)
6
- local Gate = require (script.Parent.Gate)
7
- local Middleware = require (script.Parent.Parent.internal.Middleware)
8
- local Registry = require (script.Parent.Parent.internal.Registry)
5
+ local Baseline = require(script.Parent.Parent.internal.Baseline)
6
+ local Gate = require(script.Parent.Gate)
7
+ local Middleware = require(script.Parent.Parent.internal.Middleware)
8
+ local Registry = require(script.Parent.Parent.internal.Registry)
9
9
 
10
10
  local registryGet = Registry.get
11
11
  local gateCheck = Gate.check
12
12
  local gateReportDrop = Gate.reportDrop
13
13
 
14
- -- Constants ----------------------------------------------------------
14
+ -- Constants -----------------------------------------------------------
15
15
 
16
16
  local KIND_PACKET = Registry.KIND_PACKET
17
17
  local KIND_REQUEST = Registry.KIND_REQUEST
18
18
 
19
19
  local MAX_INCOMING = 65536
20
20
 
21
- -- Public -------------------------------------------------------------
21
+ -- Public --------------------------------------------------------------
22
22
 
23
23
  local Reader = {}
24
24
 
25
25
  -- Shared incoming-data validation used by both Client and Server receive.
26
- function Reader.decodeIncoming (data: any): buffer?
27
- if typeof (data) ~= "buffer" then
26
+ function Reader.decodeIncoming(data: any): buffer?
27
+ if typeof(data) ~= "buffer" then
28
28
  return nil
29
29
  end
30
- if buffer.len (data) > MAX_INCOMING then
30
+ if buffer.len(data) > MAX_INCOMING then
31
31
  return nil
32
32
  end
33
33
  return data
34
34
  end
35
35
 
36
36
  -- Callers wrap this in pcall. Any throw aborts the entire buffer from this source.
37
- function Reader.process (
37
+ function Reader.process(
38
38
  incoming: buffer,
39
39
  refs: { Instance }?,
40
40
  player: Player?,
41
41
  startOffset: number
42
42
  ): ()
43
- local length = buffer.len (incoming)
43
+ local length = buffer.len(incoming)
44
44
  local pos = startOffset
45
45
 
46
46
  if Baseline.hasDelta then
47
- Baseline.setReadKey (player or false)
47
+ Baseline.setReadKey(player or false)
48
48
  end
49
49
 
50
50
  -- Cache flags once per frame-buffer, not per packet
@@ -52,12 +52,12 @@ function Reader.process (
52
52
  local isServer = player ~= nil
53
53
 
54
54
  while pos < length do
55
- local id = buffer.readu8 (incoming, pos)
55
+ local id = buffer.readu8(incoming, pos)
56
56
  pos += 1
57
57
 
58
- local reg = registryGet (id)
58
+ local reg = registryGet(id)
59
59
  if not reg then
60
- warn (`[Lync] Unknown packet ID {id}`)
60
+ warn(`[Lync] Unknown packet ID {id}`)
61
61
  break
62
62
  end
63
63
 
@@ -68,7 +68,7 @@ function Reader.process (
68
68
  break
69
69
  end
70
70
 
71
- local count = buffer.readu16 (incoming, pos)
71
+ local count = buffer.readu16(incoming, pos)
72
72
  pos += 2
73
73
 
74
74
  if count == 0 then
@@ -87,9 +87,9 @@ function Reader.process (
87
87
  break
88
88
  end
89
89
 
90
- local value, consumed = codec.read (incoming, pos, refs)
90
+ local value, consumed = codec.read(incoming, pos, refs)
91
91
  pos += consumed
92
- signal:fire (value, player)
92
+ signal:fire(value, player)
93
93
  end
94
94
  else
95
95
  -- Cold path: gate and/or middleware and/or byte budget active
@@ -102,25 +102,25 @@ function Reader.process (
102
102
  end
103
103
 
104
104
  if maxBytes and (pos - batchStart) > maxBytes then
105
- gateReportDrop (player, "size", reg.name, nil)
105
+ gateReportDrop(player, "size", reg.name, nil)
106
106
  break
107
107
  end
108
108
 
109
- local value, consumed = codec.read (incoming, pos, refs)
109
+ local value, consumed = codec.read(incoming, pos, refs)
110
110
  pos += consumed
111
111
 
112
- if needsGate and not gateCheck (value, reg, player :: Player) then
112
+ if needsGate and not gateCheck(value, reg, player :: Player) then
113
113
  continue
114
114
  end
115
115
 
116
116
  if hasReceiveMiddleware then
117
- local final = Middleware.runReceive (value, baseName, player)
117
+ local final = Middleware.runReceive(value, baseName, player)
118
118
  if final == nil then
119
119
  continue
120
120
  end
121
- signal:fire (final, player)
121
+ signal:fire(final, player)
122
122
  else
123
- signal:fire (value, player)
123
+ signal:fire(value, player)
124
124
  end
125
125
  end
126
126
  end
@@ -133,29 +133,29 @@ function Reader.process (
133
133
  local queryCodec = reg.codec
134
134
  local querySignal = reg.signal
135
135
 
136
- local correlationId = buffer.readu16 (incoming, pos)
137
- local status = buffer.readu8 (incoming, pos + 2)
136
+ local correlationId = buffer.readu16(incoming, pos)
137
+ local status = buffer.readu8(incoming, pos + 2)
138
138
  pos += 3
139
139
 
140
140
  if status == 1 then
141
141
  if kind == KIND_REQUEST then
142
- querySignal:fire (nil, player, correlationId)
142
+ querySignal:fire(nil, player, correlationId)
143
143
  else
144
- querySignal:fire (nil, correlationId)
144
+ querySignal:fire(nil, correlationId)
145
145
  end
146
146
  continue
147
147
  end
148
148
 
149
- local value, consumed = queryCodec.read (incoming, pos, refs)
149
+ local value, consumed = queryCodec.read(incoming, pos, refs)
150
150
  pos += consumed
151
151
 
152
- if isServer and reg.needsGate and not gateCheck (value, reg, player :: Player) then
152
+ if isServer and reg.needsGate and not gateCheck(value, reg, player :: Player) then
153
153
  continue
154
154
  end
155
155
 
156
156
  local final: any
157
157
  if hasReceiveMiddleware then
158
- final = Middleware.runReceive (value, reg.baseName, player)
158
+ final = Middleware.runReceive(value, reg.baseName, player)
159
159
  if final == nil then
160
160
  continue
161
161
  end
@@ -164,12 +164,12 @@ function Reader.process (
164
164
  end
165
165
 
166
166
  if kind == KIND_REQUEST then
167
- querySignal:fire (final, player, correlationId)
167
+ querySignal:fire(final, player, correlationId)
168
168
  else
169
- querySignal:fire (final, correlationId)
169
+ querySignal:fire(final, correlationId)
170
170
  end
171
171
  end
172
172
  end
173
173
  end
174
174
 
175
- return table.freeze (Reader)
175
+ return table.freeze(Reader)