@rbxts/zyntex-sdk 1.0.3 → 6.0.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.
package/src/api.luau ADDED
@@ -0,0 +1,237 @@
1
+ --!strict
2
+ local HttpService: HttpService = game:GetService("HttpService")
3
+ local RunService = game:GetService("RunService")
4
+ local Random = Random.new()
5
+
6
+ --==============================--
7
+ -- Session
8
+ --==============================--
9
+
10
+ local Session = {}
11
+ Session.__index = Session
12
+
13
+ -- Randomized small interval to naturally jitter pushes across servers
14
+ local FLUSH_DELTA = Random:NextInteger(5, 8)
15
+
16
+ local BUFFER: { { [string]: any } } = {}
17
+
18
+ local _onFlush: (data: any) -> () = function(_)
19
+ -- no-op by default
20
+ end
21
+
22
+ --==============================--
23
+ -- Types
24
+ --==============================--
25
+
26
+ export type SessionType = {
27
+ rootUrl: string,
28
+ gameToken: string,
29
+ jobID: string,
30
+
31
+ _firstFlushDone: boolean,
32
+ }
33
+
34
+ local Response = {}
35
+ Response.__index = Response
36
+
37
+ export type ResponseType = {
38
+ success: boolean,
39
+ user_message: string,
40
+ data: any?,
41
+ statusCode: number,
42
+ }
43
+
44
+ export type Session = typeof(setmetatable({} :: SessionType, Session))
45
+ export type Response = typeof(setmetatable({} :: ResponseType, Response))
46
+
47
+ --==============================--
48
+ -- Utilities
49
+ --==============================--
50
+
51
+ local function nowISO(): string
52
+ local dt = DateTime.now():ToUniversalTime()
53
+ return string.format(
54
+ "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
55
+ dt.Year, dt.Month, dt.Day,
56
+ dt.Hour, dt.Minute, dt.Second,
57
+ dt.Millisecond
58
+ )
59
+ end
60
+
61
+ local function toBatchItem(method: string, path: string, body: { [string]: any }?): { [string]: any }
62
+ return {
63
+ method = string.upper(method) .. " " .. path,
64
+ t = nowISO(),
65
+ -- keep native Lua table; server expects an object
66
+ payload = body or {},
67
+ -- lightweight client-side correlation id (handy for debugging server logs)
68
+ rid = HttpService:GenerateGUID(false),
69
+ }
70
+ end
71
+
72
+ --==============================--
73
+ -- Response helpers
74
+ --==============================--
75
+
76
+ function Response.new(success: boolean, user_message: string, statusCode: number, data: any?): Response
77
+ local self = {}
78
+ self.success = success
79
+ self.user_message = user_message
80
+ self.data = data
81
+ self.statusCode = statusCode
82
+ return setmetatable(self, Response)
83
+ end
84
+
85
+ function Response.fromRaw(res: { [string]: any }, statusCode: number): Response
86
+ local self = {}
87
+ local decoded = HttpService:JSONDecode(res["Body"])
88
+
89
+ self.success = decoded.success :: boolean
90
+ self.user_message = decoded.user_message :: string
91
+ self.data = decoded.data
92
+ self.statusCode = statusCode
93
+
94
+ return setmetatable(self, Response)
95
+ end
96
+
97
+ --==============================--
98
+ -- Low-level HTTP
99
+ --==============================--
100
+
101
+ function Session:_requestRaw(url: string, method: string, body: any?): Response
102
+ if not string.find(url, "http") then
103
+ url = self.rootUrl .. url
104
+ end
105
+ local res = HttpService:RequestAsync({
106
+ Url = url,
107
+ Method = string.upper(method),
108
+ Body = if body ~= nil then HttpService:JSONEncode(body) else nil,
109
+ Headers = {
110
+ ["Authorization"] = `Game-Token {self.gameToken}`,
111
+ ["Job-ID"] = self.jobID,
112
+ ["Content-Type"] = "application/json",
113
+ },
114
+ })
115
+ return Response.fromRaw(res, res.StatusCode)
116
+ end
117
+
118
+ --==============================--
119
+ -- Public API (enqueue writes, direct GET)
120
+ --==============================--
121
+
122
+ -- Queue a POST (batched via /roblox/push)
123
+ function Session.post(self: Session, path: string, body: { [string]: any }?, bypassBuffer: boolean?): string?
124
+ if string.find(path, "telemetry") or bypassBuffer then
125
+ self:_requestRaw(path, "POST", body)
126
+ return
127
+ end
128
+ local item = toBatchItem("POST", path, body)
129
+ table.insert(BUFFER, item)
130
+ return item.rid
131
+ end
132
+
133
+ -- Queue a DELETE (batched via /roblox/push)
134
+ function Session.delete(self: Session, path: string, body: { [string]: any }?, _autoError: boolean?): string
135
+ local item = toBatchItem("DELETE", path, body)
136
+ table.insert(BUFFER, item)
137
+ return item.rid
138
+ end
139
+
140
+ -- Direct GET (rare; kept for convenience)
141
+ function Session.get(self: Session, path: string): Response
142
+ return self:_requestRaw(self.rootUrl .. path, "GET", nil)
143
+ end
144
+
145
+ --==============================--
146
+ -- Flush (push)
147
+ --==============================--
148
+
149
+ -- Flush the buffer immediately to /roblox/push.
150
+ function Session.Flush(self: Session, sinceISO: string?): Response?
151
+ if #BUFFER == 0 then
152
+ return nil
153
+ end
154
+
155
+ -- drain atomically
156
+ local payload = BUFFER
157
+ BUFFER = {}
158
+
159
+ local url = self.rootUrl .. "/roblox/push"
160
+ if sinceISO then
161
+ url ..= "?since=" .. HttpService:UrlEncode(sinceISO)
162
+ end
163
+
164
+ local ok, resOrErr = pcall(function()
165
+ return self:_requestRaw(url, "POST", payload)
166
+ end)
167
+
168
+ if not ok then
169
+ -- put the batch back so next cycle retries
170
+ for _, item in payload do
171
+ table.insert(BUFFER, item)
172
+ end
173
+ warn(`[Zyntex]: /roblox/push flush failed, retrying later: {tostring(resOrErr)}`)
174
+ return nil
175
+ end
176
+
177
+ local res: Response = resOrErr :: Response
178
+
179
+ -- Invoke hook so Zyntex can react (ban/mute handling, manifest, listen_data)
180
+ -- This is intentionally fire-and-forget; hook can be a small, synchronous handler.
181
+ -- Expected shape: res.data = { results = [...], manifest?, listen_data? }
182
+ local okHook, hookErr = pcall(function()
183
+ if res.data ~= nil then
184
+ _onFlush(res.data)
185
+ end
186
+ end)
187
+ if not okHook then
188
+ warn(`[Zyntex]: onFlush callback errored: {tostring(hookErr)}`)
189
+ end
190
+
191
+ -- Track “first flush that contained POST /servers”
192
+ if not self._firstFlushDone then
193
+ for _, item in payload do
194
+ -- cheap prefix check; format is "METHOD /path"
195
+ if string.sub(item.method, 1, 13) == "POST /servers" then
196
+ self._firstFlushDone = true
197
+ break
198
+ end
199
+ end
200
+ end
201
+
202
+ return res
203
+ end
204
+
205
+ -- Alias; some callers prefer explicit naming
206
+ function Session.FlushNow(self: Session, sinceISO: string?): Response?
207
+ return self:Flush(sinceISO)
208
+ end
209
+
210
+ --==============================--
211
+ -- onFlush hook setter
212
+ --==============================--
213
+
214
+ -- Zyntex sets this to handle per-result errors, e.g.:
215
+ -- - method == "POST /players"
216
+ -- - success == false
217
+ -- - error == "403: reason" (ban) or "401: reason" (mute)
218
+ -- From there, Zyntex can kick/mute locally and/or print the reason.
219
+ function Session.onFlush(self: Session, cb: (data: any) -> ()): ()
220
+ _onFlush = cb or function(_) end
221
+ end
222
+
223
+ --==============================--
224
+ -- Constructor
225
+ --==============================--
226
+
227
+ function Session.new(gameToken: string, rootUrl: string?, jobId: string?): Session
228
+ local self: any = {}
229
+ self.gameToken = gameToken
230
+ self.rootUrl = rootUrl or "https://api.zyntex.dev"
231
+ self.jobID = RunService:IsStudio() and `DEV-SERVER-{HttpService:GenerateGUID(false)}` or game.JobId
232
+ self._firstFlushDone = false
233
+
234
+ return setmetatable(self, Session)
235
+ end
236
+
237
+ return Session
package/src/index.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ // types/zyntex.d.ts
2
+ interface ZyntexConfig {}
3
+ interface Zyntex {
4
+ init(config: ZyntexConfig): void;
5
+ // Add other Zyntex instance methods here
6
+ }
7
+
8
+ interface ZyntexModule {
9
+ /**
10
+ * Creates a new Zyntex instance using the provided game token.
11
+ */
12
+ (gameToken: string): Zyntex;
13
+
14
+ /**
15
+ * The current version string of the Zyntex module.
16
+ */
17
+ readonly version: string;
18
+
19
+ /**
20
+ * Used by the upgrader plugin to detect Zyntex.
21
+ */
22
+ readonly isZyntex: true;
23
+ }
24
+
25
+ declare const ZyntexModule: ZyntexModule;
26
+ export default ZyntexModule;
@@ -1,26 +1,26 @@
1
- local Zyntex = require(script:FindFirstChild("Zyntex"))
2
- local Session = require(script:FindFirstChild("api"))
3
-
4
- return {
5
- default = setmetatable({}, {
6
- --[[
7
- Gets the current version of this module.
8
- ]]
9
- __index = function(self, index)
10
- if string.lower(index) == "version" then
11
- return Zyntex.VERSION
12
- end
13
- if string.lower(index) == "isZyntex" then --// Important for the Zyntex upgrader plugin. When it searches for the module, make sure it has this.
14
- return true
15
- end
16
- end,
17
- --[[
18
- Returns a new Zyntex object.
19
- https://docs.zyntex.dev
20
- ]]
21
- __call = function(self, gameToken: string): Zyntex.Zyntex
22
- return Zyntex.new(gameToken)
23
- end,
24
- })
25
-
1
+ local Zyntex = require(script:FindFirstChild("Zyntex"))
2
+ local Session = require(script:FindFirstChild("api"))
3
+
4
+ return {
5
+ default = setmetatable({}, {
6
+ --[[
7
+ Gets the current version of this module.
8
+ ]]
9
+ __index = function(self, index)
10
+ if string.lower(index) == "version" then
11
+ return Zyntex.VERSION
12
+ end
13
+ if string.lower(index) == "isZyntex" then --// Important for the Zyntex upgrader plugin. When it searches for the module, make sure it has this.
14
+ return true
15
+ end
16
+ end,
17
+ --[[
18
+ Returns a new Zyntex object.
19
+ https://docs.zyntex.dev
20
+ ]]
21
+ __call = function(self, gameToken: string): Zyntex.Zyntex
22
+ return Zyntex.new(gameToken)
23
+ end,
24
+ })
25
+
26
26
  }
@@ -1,21 +1,21 @@
1
- --[[
2
- The Zyntex configuration type.
3
- ]]
4
- export type Config = {
5
- --[[
6
- Wether or not to list to and execute requests made by the dashboard server.
7
- Admins require the servers.rce permission to remotely execute code.
8
- Defaults to "true"
9
- ]]
10
- enableRCE: boolean?;
11
- --[[
12
- Whether or not to display debug logging
13
- ]]
14
- debug: boolean?;
15
- --[[
16
- Wether or not to simulate high-requests
17
- ]]
18
- simulate: boolean?;
19
- }
20
-
21
- return true;
1
+ --[[
2
+ The Zyntex configuration type.
3
+ ]]
4
+ export type Config = {
5
+ --[[
6
+ Wether or not to list to and execute requests made by the dashboard server.
7
+ Admins require the servers.rce permission to remotely execute code.
8
+ Defaults to "true"
9
+ ]]
10
+ enableRCE: boolean?;
11
+ --[[
12
+ Whether or not to display debug logging
13
+ ]]
14
+ debug: boolean?;
15
+ --[[
16
+ Wether or not to simulate high-requests
17
+ ]]
18
+ simulate: boolean?;
19
+ }
20
+
21
+ return true;
@@ -1,6 +1,6 @@
1
- local SystemChatEvent: RemoteEvent = game:GetService("ReplicatedStorage"):WaitForChild("zyntex.events"):WaitForChild("SystemChat") :: RemoteEvent
2
- local Channel = game.TextChatService:WaitForChild("TextChannels"):WaitForChild("RBXGeneral")
3
-
4
- SystemChatEvent.OnClientEvent:Connect(function(msg: string)
5
- Channel:DisplaySystemMessage(msg)
6
- end)
1
+ local SystemChatEvent: RemoteEvent = game:GetService("ReplicatedStorage"):WaitForChild("zyntex.events"):WaitForChild("SystemChat") :: RemoteEvent
2
+ local Channel = game.TextChatService:WaitForChild("TextChannels"):WaitForChild("RBXGeneral")
3
+
4
+ SystemChatEvent.OnClientEvent:Connect(function(msg: string)
5
+ Channel:DisplaySystemMessage(msg)
6
+ end)