rotur-sdk 1.0.0 → 1.0.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 ADDED
@@ -0,0 +1,302 @@
1
+ # rotur-sdk
2
+
3
+ The official TypeScript SDK for the [Rotur](https://rotur.dev) platform. Covers auth, profiles, posts, credits, keys, groups, items, gifts, tokens, validators, status, files, cosmetics, push notifications, and more — all with full type safety.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install rotur-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { Rotur } from "rotur-sdk";
15
+
16
+ const rotur = new Rotur();
17
+
18
+ // Browser auth - opens the Rotur login popup
19
+ await rotur.login();
20
+
21
+ // Or provide a token you already have
22
+ const rotur = new Rotur({ token: "your-token-here" });
23
+ ```
24
+
25
+ ## API Reference
26
+
27
+ ### Authentication
28
+
29
+ ```ts
30
+ // Popup-based OAuth flow (browser only)
31
+ await rotur.login({ system: "my-app", timeout: 60_000 });
32
+
33
+ // Check current auth state
34
+ rotur.loggedIn; // boolean
35
+ rotur.token; // string | null
36
+
37
+ // Set/refresh token manually
38
+ rotur.setToken("new-token");
39
+
40
+ // Logout (clears token + disconnects socket)
41
+ rotur.logout();
42
+ ```
43
+
44
+ ### Link-based Auth (for CLI / server)
45
+
46
+ ```ts
47
+ const code = await rotur.link.getCode();
48
+ console.log(`Visit https://rotur.dev/link and enter: ${code}`);
49
+
50
+ // Polls until the user links on the website
51
+ const token = await rotur.link.pollUntilLinked(code);
52
+ ```
53
+
54
+ ### Profiles
55
+
56
+ ```ts
57
+ const profile = await rotur.profiles.get("username");
58
+ const { exists } = await rotur.profiles.exists("username");
59
+ const avatarUrl = rotur.profiles.getAvatarUrl("username");
60
+ ```
61
+
62
+ ### Account (me)
63
+
64
+ ```ts
65
+ await rotur.me.get();
66
+ await rotur.me.update("bio", "hello world");
67
+ await rotur.me.transfer("recipient", 100, "a note");
68
+ await rotur.me.claimDaily();
69
+ await rotur.me.badges();
70
+ await rotur.me.block("username");
71
+ await rotur.me.unblock("username");
72
+ await rotur.me.checkAuth();
73
+ ```
74
+
75
+ ### Posts
76
+
77
+ ```ts
78
+ const post = await rotur.posts.create("Hello from the SDK!");
79
+ await rotur.posts.reply(post.id, "Nice post!");
80
+ await rotur.posts.like(post.id);
81
+ await rotur.posts.repost(post.id);
82
+ await rotur.posts.pin(post.id);
83
+
84
+ const feed = await rotur.posts.feed(100, 0);
85
+ const top = await rotur.posts.top(50, 24);
86
+ const results = await rotur.posts.search("query");
87
+ ```
88
+
89
+ ### Friends
90
+
91
+ ```ts
92
+ const { friends } = await rotur.friends.list();
93
+ await rotur.friends.request("username");
94
+ await rotur.friends.accept("username");
95
+ await rotur.friends.remove("username");
96
+ ```
97
+
98
+ ### Following
99
+
100
+ ```ts
101
+ await rotur.following.follow("username");
102
+ await rotur.following.unfollow("username");
103
+ const { followers } = await rotur.following.followers("username");
104
+ const { following } = await rotur.following.following("username");
105
+ ```
106
+
107
+ ### Keys
108
+
109
+ ```ts
110
+ await rotur.keys.create("my-key", { price: 50, subscription: true });
111
+ const myKeys = await rotur.keys.mine();
112
+ const key = await rotur.keys.get("key-id");
113
+ const { owned } = await rotur.keys.check("username", "key-name");
114
+ await rotur.keys.buy("key-id");
115
+ ```
116
+
117
+ ### Items
118
+
119
+ ```ts
120
+ const item = await rotur.items.create({ name: "sword", price: 100, selling: true });
121
+ const item = await rotur.items.get("sword");
122
+ const items = await rotur.items.list("username");
123
+ await rotur.items.buy("sword");
124
+ await rotur.items.sell("sword");
125
+ ```
126
+
127
+ ### Gifts
128
+
129
+ ```ts
130
+ await rotur.gifts.create(500, { note: "Happy birthday!", expiresInHrs: 48 });
131
+ const { gift } = await rotur.gifts.get("code");
132
+ await rotur.gifts.claim("code");
133
+ ```
134
+
135
+ ### Tokens (sub-tokens / scoped access)
136
+
137
+ ```ts
138
+ const { permissions, groups } = await rotur.tokens.permissions();
139
+ const { tokens } = await rotur.tokens.list();
140
+
141
+ const sub = await rotur.tokens.create("bot-token", [
142
+ "posts:view", "posts:create", "account:profile"
143
+ ], { expiresInHrs: 24, origin: "my-app" });
144
+
145
+ await rotur.tokens.revoke(sub.id);
146
+ ```
147
+
148
+ ### Groups
149
+
150
+ ```ts
151
+ const group = await rotur.groups.create("devs", "Developers", {
152
+ description: "A group for devs",
153
+ public: true,
154
+ joinPolicy: "OPEN",
155
+ });
156
+
157
+ await rotur.groups.join("devs");
158
+ await rotur.groups.represent("devs");
159
+ const announcements = await rotur.groups.announcements("devs");
160
+ const roles = await rotur.groups.roles("devs");
161
+ await rotur.groups.assignRole("devs", userId, roleId);
162
+ ```
163
+
164
+ ### Cosmetics
165
+
166
+ ```ts
167
+ const shop = await rotur.cosmetics.shop({ sort: "newest", limit: 20 });
168
+ const mine = await rotur.cosmetics.mine();
169
+ await rotur.cosmetics.purchase("cosmetic-id");
170
+ await rotur.cosmetics.equip("cosmetic-id");
171
+ await rotur.cosmetics.unequip("hat");
172
+ ```
173
+
174
+ ### Files
175
+
176
+ ```ts
177
+ const files = await rotur.files.index();
178
+ const { used, max } = await rotur.files.usage();
179
+ const file = await rotur.files.getByUUID("uuid");
180
+ const file = await rotur.files.getByPath("path/to/file");
181
+ await rotur.files.upload({ /* file data */ });
182
+ ```
183
+
184
+ ### Push Notifications
185
+
186
+ ```ts
187
+ const { public_key } = await rotur.push.vapidKeys();
188
+ await rotur.push.register(endpoint, p256dh, auth, "my-app", "fingerprint");
189
+ const { endpoints } = await rotur.push.endpoints();
190
+ await rotur.push.send("username", "my-app", { title: "Hi!", body: "You have a message" });
191
+ ```
192
+
193
+ ### Status & Validators
194
+
195
+ ```ts
196
+ const status = await rotur.status.get("username");
197
+ const { validator } = await rotur.validators.generate("key");
198
+ const result = await rotur.validators.validate(validator, "key");
199
+ ```
200
+
201
+ ### Standing, Stats, DevFund, Check
202
+
203
+ ```ts
204
+ const standing = await rotur.standing.get("username");
205
+ const economy = await rotur.stats.economy();
206
+ await rotur.devfund.escrowTransfer(100, "petition-id");
207
+ const { banned } = await rotur.check.banned(["user1", "user2"]);
208
+ ```
209
+
210
+ ## WebSocket (Real-time)
211
+
212
+ ```ts
213
+ // Connect after login
214
+ const { user_id, username } = await rotur.connectSocket();
215
+
216
+ // Join presence rooms
217
+ rotur.socket.join(["lobby", "chat"]);
218
+
219
+ // Listen for events
220
+ rotur.socket.on("member_join", (msg) => {
221
+ console.log(`${msg.username} joined ${msg.room}`);
222
+ });
223
+
224
+ rotur.socket.on("status_update", (msg) => {
225
+ console.log(`${msg.username} is now ${msg.presence}`);
226
+ });
227
+
228
+ // Set your own status
229
+ rotur.socket.setStatus("Building something cool", "online");
230
+
231
+ // Rich presence — "Playing" activity
232
+ rotur.socket.setPlaying("My Game", {
233
+ title: "Level 3",
234
+ status: "In-game",
235
+ url: "https://mygame.com",
236
+ });
237
+
238
+ // "Listening to" activity
239
+ rotur.socket.setMusic("Spotify", {
240
+ title: "Song Name",
241
+ artist: "Artist",
242
+ album: "Album",
243
+ start: Date.now(),
244
+ end: Date.now() + 180_000,
245
+ });
246
+
247
+ // Wildcard — receive every message
248
+ rotur.socket.on("*", (msg) => console.log(msg.cmd, msg));
249
+
250
+ // Disconnect
251
+ rotur.socket.disconnect();
252
+ ```
253
+
254
+ The socket auto-reconnects with exponential backoff and sends heartbeats every 25s.
255
+
256
+ ## Error Handling
257
+
258
+ ```ts
259
+ import { ApiError, AuthError } from "rotur-sdk";
260
+
261
+ try {
262
+ await rotur.me.transfer("user", 1000);
263
+ } catch (e) {
264
+ if (e instanceof ApiError) {
265
+ console.log(e.status); // HTTP status code
266
+ console.log(e.data); // response body
267
+ console.log(e.message); // human-readable error
268
+ }
269
+ }
270
+
271
+ try {
272
+ await rotur.login();
273
+ } catch (e) {
274
+ if (e instanceof AuthError) {
275
+ console.log(e.code); // "timeout" | "aborted" | "popup_blocked" | "no_token"
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## Building
281
+
282
+ ```bash
283
+ npm run build # build CJS + ESM + types via tsup
284
+ npm run dev # watch mode
285
+ npm run typecheck # tsc --noEmit
286
+ npm test # vitest
287
+ ```
288
+
289
+ ## Exports
290
+
291
+ | Export | Description |
292
+ |---|---|
293
+ | `Rotur` | Main client class |
294
+ | `RoturSocket` | WebSocket connection (real-time presence) |
295
+ | `ApiError` | HTTP error with `status` and `data` |
296
+ | `performAuth`, `AuthError` | Browser auth flow |
297
+ | `AuthOptions`, `AuthResult` | Auth option types |
298
+ | All types from `types.ts` | `UserProfile`, `NetPost`, `GroupPublic`, `WSMessage`, etc. |
299
+
300
+ ## License
301
+
302
+ MIT
package/dist/index.d.mts CHANGED
@@ -496,6 +496,7 @@ type WSMessage = {
496
496
  };
497
497
 
498
498
  type Handler = (msg: any) => void;
499
+ type KeyChangeCallback = (key: string, value: any, oldValue: any) => void;
499
500
  declare class RoturSocket extends EventTarget {
500
501
  private url;
501
502
  private ws;
@@ -507,11 +508,18 @@ declare class RoturSocket extends EventTarget {
507
508
  private _username;
508
509
  private handlers;
509
510
  private rooms;
511
+ private keyCache;
512
+ private keyChangeCallbacks;
510
513
  constructor(url?: string);
511
514
  get connected(): boolean;
512
515
  get userId(): string | null;
513
516
  get username(): string | null;
514
517
  get joinedRooms(): string[];
518
+ getKey(key: string): any;
519
+ getAllKeys(): Record<string, any>;
520
+ onKeyChange(callback: KeyChangeCallback): () => void;
521
+ offKeyChange(callback: KeyChangeCallback): void;
522
+ private notifyKeyChange;
515
523
  connect(token: string): Promise<{
516
524
  user_id: UserId;
517
525
  username: Username;
@@ -529,8 +537,7 @@ declare class RoturSocket extends EventTarget {
529
537
  leave(rooms: string | string[]): void;
530
538
  listRooms(): Promise<string[]>;
531
539
  roomState(room: string): void;
532
- setPresence(presence: Presence): void;
533
- setStatus(status: string): void;
540
+ setStatus(status: string, presence?: Presence): void;
534
541
  addActivity(activity: Activity & {
535
542
  id: string;
536
543
  }): void;
@@ -579,6 +586,7 @@ declare class Rotur {
579
586
  get token(): string | null;
580
587
  get loggedIn(): boolean;
581
588
  setToken(token: string): void;
589
+ private _socketReady;
582
590
  login(options?: {
583
591
  system?: string;
584
592
  timeout?: number;
@@ -616,6 +624,9 @@ declare class MeNamespace extends Namespace {
616
624
  refreshToken(): Promise<{
617
625
  token: string;
618
626
  }>;
627
+ getKey(key: string): any;
628
+ getAllKeys(): Record<string, any>;
629
+ onKeyChange(callback: (key: string, value: any, oldValue: any) => void): () => void;
619
630
  transfer(to: Username, amount: number, note?: string): Promise<any>;
620
631
  claimDaily(): Promise<{
621
632
  message: string;
@@ -875,27 +886,6 @@ declare class CosmeticsNamespace extends Namespace {
875
886
  message: string;
876
887
  }>;
877
888
  overlays(filepath: string): Promise<Response>;
878
- /** Admin: List all cosmetics in catalog */
879
- adminList(options?: {
880
- type?: string;
881
- }): Promise<{
882
- cosmetics: CosmeticCatalogEntryPublic[];
883
- total: number;
884
- }>;
885
- /** Admin: Create a cosmetic */
886
- adminCreate(data: AdminCosmeticCreate): Promise<{
887
- message: string;
888
- cosmetic: CosmeticCatalogEntryPublic;
889
- }>;
890
- /** Admin: Update a cosmetic */
891
- adminUpdate(id: string, data: AdminCosmeticUpdate): Promise<{
892
- message: string;
893
- cosmetic: CosmeticCatalogEntryPublic;
894
- }>;
895
- /** Admin: Delete a cosmetic */
896
- adminDelete(id: string): Promise<{
897
- message: string;
898
- }>;
899
889
  }
900
890
  declare class PushNamespace extends Namespace {
901
891
  vapidKeys(): Promise<{
package/dist/index.d.ts CHANGED
@@ -496,6 +496,7 @@ type WSMessage = {
496
496
  };
497
497
 
498
498
  type Handler = (msg: any) => void;
499
+ type KeyChangeCallback = (key: string, value: any, oldValue: any) => void;
499
500
  declare class RoturSocket extends EventTarget {
500
501
  private url;
501
502
  private ws;
@@ -507,11 +508,18 @@ declare class RoturSocket extends EventTarget {
507
508
  private _username;
508
509
  private handlers;
509
510
  private rooms;
511
+ private keyCache;
512
+ private keyChangeCallbacks;
510
513
  constructor(url?: string);
511
514
  get connected(): boolean;
512
515
  get userId(): string | null;
513
516
  get username(): string | null;
514
517
  get joinedRooms(): string[];
518
+ getKey(key: string): any;
519
+ getAllKeys(): Record<string, any>;
520
+ onKeyChange(callback: KeyChangeCallback): () => void;
521
+ offKeyChange(callback: KeyChangeCallback): void;
522
+ private notifyKeyChange;
515
523
  connect(token: string): Promise<{
516
524
  user_id: UserId;
517
525
  username: Username;
@@ -529,8 +537,7 @@ declare class RoturSocket extends EventTarget {
529
537
  leave(rooms: string | string[]): void;
530
538
  listRooms(): Promise<string[]>;
531
539
  roomState(room: string): void;
532
- setPresence(presence: Presence): void;
533
- setStatus(status: string): void;
540
+ setStatus(status: string, presence?: Presence): void;
534
541
  addActivity(activity: Activity & {
535
542
  id: string;
536
543
  }): void;
@@ -579,6 +586,7 @@ declare class Rotur {
579
586
  get token(): string | null;
580
587
  get loggedIn(): boolean;
581
588
  setToken(token: string): void;
589
+ private _socketReady;
582
590
  login(options?: {
583
591
  system?: string;
584
592
  timeout?: number;
@@ -616,6 +624,9 @@ declare class MeNamespace extends Namespace {
616
624
  refreshToken(): Promise<{
617
625
  token: string;
618
626
  }>;
627
+ getKey(key: string): any;
628
+ getAllKeys(): Record<string, any>;
629
+ onKeyChange(callback: (key: string, value: any, oldValue: any) => void): () => void;
619
630
  transfer(to: Username, amount: number, note?: string): Promise<any>;
620
631
  claimDaily(): Promise<{
621
632
  message: string;
@@ -875,27 +886,6 @@ declare class CosmeticsNamespace extends Namespace {
875
886
  message: string;
876
887
  }>;
877
888
  overlays(filepath: string): Promise<Response>;
878
- /** Admin: List all cosmetics in catalog */
879
- adminList(options?: {
880
- type?: string;
881
- }): Promise<{
882
- cosmetics: CosmeticCatalogEntryPublic[];
883
- total: number;
884
- }>;
885
- /** Admin: Create a cosmetic */
886
- adminCreate(data: AdminCosmeticCreate): Promise<{
887
- message: string;
888
- cosmetic: CosmeticCatalogEntryPublic;
889
- }>;
890
- /** Admin: Update a cosmetic */
891
- adminUpdate(id: string, data: AdminCosmeticUpdate): Promise<{
892
- message: string;
893
- cosmetic: CosmeticCatalogEntryPublic;
894
- }>;
895
- /** Admin: Delete a cosmetic */
896
- adminDelete(id: string): Promise<{
897
- message: string;
898
- }>;
899
889
  }
900
890
  declare class PushNamespace extends Namespace {
901
891
  vapidKeys(): Promise<{
package/dist/index.js CHANGED
@@ -139,6 +139,8 @@ var RoturSocket = class extends EventTarget {
139
139
  _username = null;
140
140
  handlers = /* @__PURE__ */ new Map();
141
141
  rooms = /* @__PURE__ */ new Set();
142
+ keyCache = {};
143
+ keyChangeCallbacks = /* @__PURE__ */ new Set();
142
144
  get connected() {
143
145
  return this._connected;
144
146
  }
@@ -151,6 +153,27 @@ var RoturSocket = class extends EventTarget {
151
153
  get joinedRooms() {
152
154
  return [...this.rooms];
153
155
  }
156
+ getKey(key) {
157
+ return this.keyCache[key];
158
+ }
159
+ getAllKeys() {
160
+ return { ...this.keyCache };
161
+ }
162
+ onKeyChange(callback) {
163
+ this.keyChangeCallbacks.add(callback);
164
+ return () => this.keyChangeCallbacks.delete(callback);
165
+ }
166
+ offKeyChange(callback) {
167
+ this.keyChangeCallbacks.delete(callback);
168
+ }
169
+ notifyKeyChange(key, value, oldValue) {
170
+ for (const cb of this.keyChangeCallbacks) {
171
+ try {
172
+ cb(key, value, oldValue);
173
+ } catch {
174
+ }
175
+ }
176
+ }
154
177
  connect(token) {
155
178
  this.token = token;
156
179
  return new Promise((resolve, reject) => {
@@ -168,11 +191,19 @@ var RoturSocket = class extends EventTarget {
168
191
  this._connected = true;
169
192
  this._userId = msg.user_id;
170
193
  this._username = msg.username;
194
+ if (msg.user && typeof msg.user === "object") {
195
+ this.keyCache = { ...msg.user };
196
+ }
171
197
  this.startHeartbeat();
172
198
  resolve(msg);
173
199
  }
174
200
  if (cmd === "join_ok") this.rooms.add(msg.room);
175
201
  if (cmd === "leave_ok") this.rooms.delete(msg.room);
202
+ if (cmd === "key_update" && typeof msg.key === "string") {
203
+ const old = this.keyCache[msg.key];
204
+ this.keyCache[msg.key] = msg.value;
205
+ this.notifyKeyChange(msg.key, msg.value, old);
206
+ }
176
207
  this.emit(cmd, msg);
177
208
  };
178
209
  this.ws.onerror = (e) => {
@@ -248,11 +279,8 @@ var RoturSocket = class extends EventTarget {
248
279
  roomState(room) {
249
280
  this.send({ cmd: "room_state", room });
250
281
  }
251
- setPresence(presence) {
252
- this.send({ cmd: "set_status", presence });
253
- }
254
- setStatus(status) {
255
- this.send({ cmd: "set_status", status });
282
+ setStatus(status, presence) {
283
+ this.send({ cmd: "set_status", status, presence });
256
284
  }
257
285
  addActivity(activity) {
258
286
  this.send({ cmd: "add_activity", ...activity });
@@ -286,6 +314,7 @@ var RoturSocket = class extends EventTarget {
286
314
  this.token = null;
287
315
  this.cleanup();
288
316
  this.rooms.clear();
317
+ this.keyCache = {};
289
318
  if (this.ws) {
290
319
  this.ws.onclose = null;
291
320
  this.ws.close();
@@ -383,6 +412,9 @@ var Rotur = class {
383
412
  this._token = options?.token ?? null;
384
413
  this._http = new Http(() => this._token);
385
414
  this.socket = new RoturSocket(options?.wsUrl);
415
+ if (this._token) {
416
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
417
+ }
386
418
  }
387
419
  get token() {
388
420
  return this._token;
@@ -392,18 +424,26 @@ var Rotur = class {
392
424
  }
393
425
  setToken(token) {
394
426
  this._token = token;
427
+ if (!this.socket.connected) {
428
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
429
+ }
395
430
  }
431
+ _socketReady = null;
396
432
  async login(options) {
397
433
  const { token } = await performAuth(options);
398
434
  this._token = token;
435
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
399
436
  return this;
400
437
  }
401
438
  async connectSocket() {
402
439
  if (!this._token) throw new ApiError(401, { error: "Login first" });
403
- return this.socket.connect(this._token);
440
+ const result = await this.socket.connect(this._token);
441
+ this._socketReady = Promise.resolve(result);
442
+ return result;
404
443
  }
405
444
  logout() {
406
445
  this._token = null;
446
+ this._socketReady = null;
407
447
  this.socket.disconnect();
408
448
  }
409
449
  };
@@ -459,6 +499,15 @@ var MeNamespace = class extends Namespace {
459
499
  async refreshToken() {
460
500
  return this.$post("/me/refresh_token");
461
501
  }
502
+ getKey(key) {
503
+ return this.r.socket.getKey(key);
504
+ }
505
+ getAllKeys() {
506
+ return this.r.socket.getAllKeys();
507
+ }
508
+ onKeyChange(callback) {
509
+ return this.r.socket.onKeyChange(callback);
510
+ }
462
511
  async transfer(to, amount, note) {
463
512
  return this.$post("/me/transfer", { to, amount, note });
464
513
  }
@@ -932,22 +981,6 @@ var CosmeticsNamespace = class extends Namespace {
932
981
  async overlays(filepath) {
933
982
  return this.$get(`/cosmetics/overlays/${filepath}`, void 0, false);
934
983
  }
935
- /** Admin: List all cosmetics in catalog */
936
- async adminList(options) {
937
- return this.$get("/cosmetics/admin/list", options, true);
938
- }
939
- /** Admin: Create a cosmetic */
940
- async adminCreate(data) {
941
- return this.$post("/cosmetics/admin/create", data);
942
- }
943
- /** Admin: Update a cosmetic */
944
- async adminUpdate(id, data) {
945
- return this.$patch(`/cosmetics/admin/update/${id}`, data);
946
- }
947
- /** Admin: Delete a cosmetic */
948
- async adminDelete(id) {
949
- return this.$del(`/cosmetics/admin/delete/${id}`);
950
- }
951
984
  };
952
985
  var PushNamespace = class extends Namespace {
953
986
  async vapidKeys() {
package/dist/index.mjs CHANGED
@@ -109,6 +109,8 @@ var RoturSocket = class extends EventTarget {
109
109
  _username = null;
110
110
  handlers = /* @__PURE__ */ new Map();
111
111
  rooms = /* @__PURE__ */ new Set();
112
+ keyCache = {};
113
+ keyChangeCallbacks = /* @__PURE__ */ new Set();
112
114
  get connected() {
113
115
  return this._connected;
114
116
  }
@@ -121,6 +123,27 @@ var RoturSocket = class extends EventTarget {
121
123
  get joinedRooms() {
122
124
  return [...this.rooms];
123
125
  }
126
+ getKey(key) {
127
+ return this.keyCache[key];
128
+ }
129
+ getAllKeys() {
130
+ return { ...this.keyCache };
131
+ }
132
+ onKeyChange(callback) {
133
+ this.keyChangeCallbacks.add(callback);
134
+ return () => this.keyChangeCallbacks.delete(callback);
135
+ }
136
+ offKeyChange(callback) {
137
+ this.keyChangeCallbacks.delete(callback);
138
+ }
139
+ notifyKeyChange(key, value, oldValue) {
140
+ for (const cb of this.keyChangeCallbacks) {
141
+ try {
142
+ cb(key, value, oldValue);
143
+ } catch {
144
+ }
145
+ }
146
+ }
124
147
  connect(token) {
125
148
  this.token = token;
126
149
  return new Promise((resolve, reject) => {
@@ -138,11 +161,19 @@ var RoturSocket = class extends EventTarget {
138
161
  this._connected = true;
139
162
  this._userId = msg.user_id;
140
163
  this._username = msg.username;
164
+ if (msg.user && typeof msg.user === "object") {
165
+ this.keyCache = { ...msg.user };
166
+ }
141
167
  this.startHeartbeat();
142
168
  resolve(msg);
143
169
  }
144
170
  if (cmd === "join_ok") this.rooms.add(msg.room);
145
171
  if (cmd === "leave_ok") this.rooms.delete(msg.room);
172
+ if (cmd === "key_update" && typeof msg.key === "string") {
173
+ const old = this.keyCache[msg.key];
174
+ this.keyCache[msg.key] = msg.value;
175
+ this.notifyKeyChange(msg.key, msg.value, old);
176
+ }
146
177
  this.emit(cmd, msg);
147
178
  };
148
179
  this.ws.onerror = (e) => {
@@ -218,11 +249,8 @@ var RoturSocket = class extends EventTarget {
218
249
  roomState(room) {
219
250
  this.send({ cmd: "room_state", room });
220
251
  }
221
- setPresence(presence) {
222
- this.send({ cmd: "set_status", presence });
223
- }
224
- setStatus(status) {
225
- this.send({ cmd: "set_status", status });
252
+ setStatus(status, presence) {
253
+ this.send({ cmd: "set_status", status, presence });
226
254
  }
227
255
  addActivity(activity) {
228
256
  this.send({ cmd: "add_activity", ...activity });
@@ -256,6 +284,7 @@ var RoturSocket = class extends EventTarget {
256
284
  this.token = null;
257
285
  this.cleanup();
258
286
  this.rooms.clear();
287
+ this.keyCache = {};
259
288
  if (this.ws) {
260
289
  this.ws.onclose = null;
261
290
  this.ws.close();
@@ -353,6 +382,9 @@ var Rotur = class {
353
382
  this._token = options?.token ?? null;
354
383
  this._http = new Http(() => this._token);
355
384
  this.socket = new RoturSocket(options?.wsUrl);
385
+ if (this._token) {
386
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
387
+ }
356
388
  }
357
389
  get token() {
358
390
  return this._token;
@@ -362,18 +394,26 @@ var Rotur = class {
362
394
  }
363
395
  setToken(token) {
364
396
  this._token = token;
397
+ if (!this.socket.connected) {
398
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
399
+ }
365
400
  }
401
+ _socketReady = null;
366
402
  async login(options) {
367
403
  const { token } = await performAuth(options);
368
404
  this._token = token;
405
+ this._socketReady = this.socket.connect(this._token).catch(() => null);
369
406
  return this;
370
407
  }
371
408
  async connectSocket() {
372
409
  if (!this._token) throw new ApiError(401, { error: "Login first" });
373
- return this.socket.connect(this._token);
410
+ const result = await this.socket.connect(this._token);
411
+ this._socketReady = Promise.resolve(result);
412
+ return result;
374
413
  }
375
414
  logout() {
376
415
  this._token = null;
416
+ this._socketReady = null;
377
417
  this.socket.disconnect();
378
418
  }
379
419
  };
@@ -429,6 +469,15 @@ var MeNamespace = class extends Namespace {
429
469
  async refreshToken() {
430
470
  return this.$post("/me/refresh_token");
431
471
  }
472
+ getKey(key) {
473
+ return this.r.socket.getKey(key);
474
+ }
475
+ getAllKeys() {
476
+ return this.r.socket.getAllKeys();
477
+ }
478
+ onKeyChange(callback) {
479
+ return this.r.socket.onKeyChange(callback);
480
+ }
432
481
  async transfer(to, amount, note) {
433
482
  return this.$post("/me/transfer", { to, amount, note });
434
483
  }
@@ -902,22 +951,6 @@ var CosmeticsNamespace = class extends Namespace {
902
951
  async overlays(filepath) {
903
952
  return this.$get(`/cosmetics/overlays/${filepath}`, void 0, false);
904
953
  }
905
- /** Admin: List all cosmetics in catalog */
906
- async adminList(options) {
907
- return this.$get("/cosmetics/admin/list", options, true);
908
- }
909
- /** Admin: Create a cosmetic */
910
- async adminCreate(data) {
911
- return this.$post("/cosmetics/admin/create", data);
912
- }
913
- /** Admin: Update a cosmetic */
914
- async adminUpdate(id, data) {
915
- return this.$patch(`/cosmetics/admin/update/${id}`, data);
916
- }
917
- /** Admin: Delete a cosmetic */
918
- async adminDelete(id) {
919
- return this.$del(`/cosmetics/admin/delete/${id}`);
920
- }
921
954
  };
922
955
  var PushNamespace = class extends Namespace {
923
956
  async vapidKeys() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rotur-sdk",
3
- "version": "1.0.0",
4
- "description": "The official SDK for the Rotur platform auth, profiles, posts, credits, keys, groups, items, gifts, tokens, validators, status, files, cosmetics, push notifications, and more",
3
+ "version": "1.0.2",
4
+ "description": "The official SDK for the Rotur platform - auth, profiles, posts, credits, keys, groups, items, gifts, tokens, validators, status, files, cosmetics, push notifications, and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",