@tailuge/messaging 1.2.0 → 1.4.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/MESSAGING_SPEC.md CHANGED
@@ -131,29 +131,27 @@ interface Table<T = any> {
131
131
 
132
132
  ## Data Models
133
133
 
134
- ### `_meta` (Server-Enriched Metadata)
134
+ ### `meta` (Server-Enriched Metadata)
135
135
 
136
- All messages published through the transport layer are automatically enriched by the server with metadata from HTTP headers and connection info. This `_meta` object is **added by the server** and should be used by clients as the absolute source of truth for timing (`ts`) and origin.
136
+ All messages published through the transport layer are automatically enriched by the server with metadata from HTTP headers and connection info. This `meta` object is **added by the server** and should be used by clients as the absolute source of truth for timing (`ts`).
137
137
 
138
138
  ```typescript
139
139
  interface Meta {
140
140
  ts: string; // ISO timestamp of the request (Source of Truth for time)
141
- locale: string; // Accept-Language header (use for flag rendering)
142
141
  ua: string; // User-Agent header
143
142
  ip: string; // Client remote address
144
- origin: string; // Origin header value
145
143
  host: string; // Host header value
146
- path: string; // Request URI path
147
144
  method: string; // HTTP method (always POST for publish)
148
145
  country: string; // Country code from IP (e.g., "US", "GB", "XX")
146
+ city: string; // City from IP geolocation
149
147
  }
150
148
  ```
151
149
 
152
- **Note**: The client should NOT include `locale` or `ua` in published messages — the server adds these automatically from HTTP headers. This ensures reliable, tamper-resistant metadata for UI features like flag rendering.
150
+ **Note**: The client should NOT include `ua` in published messages — the server adds this automatically from HTTP headers. This ensures reliable, tamper-resistant metadata for UI features like flag rendering.
153
151
 
154
152
  ### `PresenceMessage`
155
153
 
156
- Information about a user in the lobby. The `locale` and `ua` fields are **not** set by the client — they are provided by the server via `_meta`.
154
+ Information about a user in the lobby. The `ua` field is **not** set by the client — it is provided by the server via `meta`.
157
155
 
158
156
  ```typescript
159
157
  interface PresenceMessage {
@@ -164,8 +162,8 @@ interface PresenceMessage {
164
162
  ruleType?: string;
165
163
  opponentId?: string | null;
166
164
  seek?: Seek;
167
- lastSeen?: number; // Managed internally for pruning (derived from _meta.ts)
168
- _meta?: Meta; // Server-enriched metadata (received messages only)
165
+ lastSeen?: number; // Managed internally for pruning (derived from meta.ts)
166
+ meta?: Meta; // Server-enriched metadata (received messages only)
169
167
 
170
168
  // Current game state:
171
169
  // - If present: user is playing or spectating at that table (available for spectating)
@@ -187,7 +185,7 @@ interface ChallengeMessage {
187
185
  recipientId: string;
188
186
  ruleType: string;
189
187
  tableId?: string; // Optional: table created by challenger
190
- _meta?: Meta; // Server-enriched metadata (received messages only)
188
+ meta?: Meta; // Server-enriched metadata (received messages only)
191
189
  }
192
190
  ```
193
191
 
@@ -215,7 +213,7 @@ interface TableMessage<T = any> {
215
213
  type: string;
216
214
  senderId: string;
217
215
  data: T; // Application-specific payload
218
- _meta?: Meta; // Server-enriched metadata (received messages only)
216
+ meta?: Meta; // Server-enriched metadata (received messages only)
219
217
  }
220
218
  ```
221
219
 
@@ -363,7 +361,7 @@ table.onMessage((msg) => {
363
361
  if (msg.type === "MOVE") {
364
362
  // msg.data is typed as Move
365
363
  applyMove(msg.data);
366
- console.log("Move received at:", msg._meta?.ts);
364
+ console.log("Move received at:", msg.meta?.ts);
367
365
  }
368
366
  });
369
367
 
package/SKILL.md CHANGED
@@ -1,30 +1,33 @@
1
- # @tailuge/messaging Agent Skill
1
+ ---
2
+ name: tailuge-messaging
3
+ description: |
4
+ Integration guide for @tailuge/messaging library. Use when building applications with:
5
+ - Real-time presence/lobby systems
6
+ - User matchmaking via challenges
7
+ - Game table communication with players and spectators
8
+ - Nchan-powered transport layer
9
+ ---
2
10
 
3
- Use this skill when integrating the `@tailuge/messaging` library into a new project or codebase.
11
+ # @tailuge/messaging
4
12
 
5
- ## Installation
13
+ Quick integration guide. See [MESSAGING_SPEC.md](./MESSAGING_SPEC.md) for full API contract.
14
+
15
+ ## Install
6
16
 
7
17
  ```bash
8
18
  npm install @tailuge/messaging
9
19
  ```
10
20
 
11
- ## Initialization
12
-
13
- Create a `MessagingClient` instance with your Nchan server base URL:
21
+ ## Quick Start
14
22
 
15
23
  ```typescript
16
24
  import { MessagingClient } from '@tailuge/messaging';
17
25
 
18
- const client = new MessagingClient({
19
- baseUrl: "https://your-nchan-server.com",
20
- });
21
-
22
- await client.start();
26
+ const client = new MessagingClient({ baseUrl: "https://your-nchan-server.com" });
27
+ client.start();
23
28
  ```
24
29
 
25
- ## Getting Online User Count
26
-
27
- Join the lobby and listen for user changes:
30
+ ## Lobby & Presence
28
31
 
29
32
  ```typescript
30
33
  const lobby = await client.joinLobby({
@@ -35,57 +38,27 @@ const lobby = await client.joinLobby({
35
38
  });
36
39
 
37
40
  lobby.onUsersChange((users) => {
38
- console.log("Online users:", users.length);
39
- });
40
- ```
41
-
42
- ## Getting User List
43
-
44
- The same `onUsersChange` callback provides the full user list:
45
-
46
- ```typescript
47
- lobby.onUsersChange((users) => {
48
- // Users are sorted alphabetically by userName
49
- users.forEach(user => {
50
- console.log(`${user.userName} (${user.userId})`);
51
- if (user.tableId) {
52
- console.log(` Playing at table: ${user.tableId}`);
53
- }
41
+ console.log(`Online: ${users.length}`);
42
+ users.forEach(u => {
43
+ const flag = countryToFlag(u.meta?.country);
44
+ console.log(`${flag} ${u.userName}`);
54
45
  });
55
46
  });
56
47
  ```
57
48
 
58
- ## Sending a Challenge
59
-
60
- To challenge another user to a game:
49
+ ## Challenge Opponent
61
50
 
62
51
  ```typescript
63
- // challenge(userId, ruleType) returns the table ID
64
52
  const tableId = await lobby.challenge(targetUserId, "billiards");
65
- console.log(`Challenge sent, table created: ${tableId}`);
66
- ```
67
53
 
68
- ## Receiving Challenges
69
-
70
- Listen for incoming challenges:
71
-
72
- ```typescript
73
54
  lobby.onChallenge((challenge) => {
74
- console.log(`${challenge.challengerName} challenged you to ${challenge.ruleType}`);
75
-
76
55
  if (challenge.type === "offer") {
77
- // Accept the challenge
78
56
  lobby.acceptChallenge(challenge.challengerId, challenge.ruleType, challenge.tableId);
79
-
80
- // Or decline
81
- // lobby.declineChallenge(challenge.challengerId, challenge.ruleType);
82
57
  }
83
58
  });
84
59
  ```
85
60
 
86
- ## Joining a Table
87
-
88
- Connect to a specific game table:
61
+ ## Table Messaging
89
62
 
90
63
  ```typescript
91
64
  interface Move { x: number; y: number }
@@ -93,18 +66,14 @@ const table = await client.joinTable<Move>("table-xyz", "user-123");
93
66
 
94
67
  table.onMessage((msg) => {
95
68
  if (msg.type === "MOVE") {
96
- const move = msg.data as Move;
97
- console.log(`Move received at: ${msg._meta?.ts}`);
69
+ console.log(`Move at: ${msg.meta?.ts}`);
98
70
  }
99
71
  });
100
72
 
101
- // Send a move
102
73
  await table.publish("MOVE", { x: 10, y: 20 });
103
74
  ```
104
75
 
105
- ## Spectator Updates
106
-
107
- Listen for spectator changes at a table:
76
+ ## Spectators
108
77
 
109
78
  ```typescript
110
79
  table.onSpectatorChange((spectators) => {
@@ -114,79 +83,31 @@ table.onSpectatorChange((spectators) => {
114
83
 
115
84
  ## Cleanup
116
85
 
117
- When done, stop the client:
118
-
119
86
  ```typescript
120
87
  await client.stop();
121
88
  ```
122
89
 
123
- ## Key Interfaces
124
-
125
- ### PresenceMessage
126
- ```typescript
127
- interface PresenceMessage {
128
- messageType: "presence";
129
- type: "join" | "heartbeat" | "leave";
130
- userId: string;
131
- userName: string;
132
- ruleType?: string;
133
- tableId?: string;
134
- // ... other fields
135
- }
136
- ```
137
-
138
- ### Meta
139
- ```typescript
140
- interface Meta {
141
- ts: string; // ISO timestamp from server
142
- locale: string; // Accept-Language header
143
- ua: string; // User-Agent header
144
- ip: string; // Client remote address
145
- origin: string; // Origin header value
146
- host: string; // Host header value
147
- path: string; // Request URI path
148
- method: string; // HTTP method
149
- country: string; // Country code from IP (e.g., "US", "GB", "XX")
150
- }
151
- ```
152
-
153
- ### ChallengeMessage
154
- ```typescript
155
- interface ChallengeMessage {
156
- messageType: "challenge";
157
- type: "offer" | "accept" | "decline" | "cancel";
158
- challengerId: string;
159
- challengerName: string;
160
- recipientId: string;
161
- ruleType: string;
162
- tableId?: string;
163
- }
164
- ```
90
+ ## Key Imports
165
91
 
166
- ### TableMessage<T>
167
92
  ```typescript
168
- interface TableMessage<T = any> {
169
- type: string;
170
- senderId: string;
171
- data: T;
172
- _meta?: Meta;
173
- }
93
+ import {
94
+ MessagingClient,
95
+ canChallenge,
96
+ canSpectate,
97
+ activeGames,
98
+ } from '@tailuge/messaging';
174
99
  ```
175
100
 
176
- ## Helper Predicates
177
-
178
- The library exports helper functions:
101
+ ## Predicates
179
102
 
180
103
  ```typescript
181
- import { canChallenge, canSpectate } from '@tailuge/messaging';
182
-
183
- // Check if you can challenge a user
184
104
  if (canChallenge(targetUser, currentUserId)) {
185
- lobby.challenge(targetUser.userId, "billiards");
105
+ await lobby.challenge(targetUser.userId, "billiards");
186
106
  }
187
107
 
188
- // Check if you can spectate a user
189
108
  if (canSpectate(targetUser, currentTableId)) {
190
- const table = await client.joinTable(targetUser.tableId, currentUserId);
109
+ await client.joinTable(targetUser.tableId, currentUserId);
191
110
  }
192
111
  ```
112
+
113
+ See [MESSAGING_SPEC.md](./MESSAGING_SPEC.md) for complete interface definitions.
package/dist/types.d.ts CHANGED
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * Server-enriched metadata added to all messages by Nchan.
3
- * This is the absolute source of truth for timing and origin.
3
+ * This is the absolute source of truth for timing.
4
4
  */
5
5
  export interface Meta {
6
6
  ts: string;
7
- locale: string;
8
7
  ua: string;
9
8
  ip: string;
10
- origin: string;
11
9
  host: string;
12
- path: string;
13
10
  method: string;
14
11
  country: string;
15
12
  }
@@ -32,7 +29,7 @@ export interface PresenceMessage {
32
29
  opponentId?: string | null;
33
30
  seek?: Seek;
34
31
  lastSeen?: number;
35
- _meta?: Meta;
32
+ meta?: Meta;
36
33
  tableId?: string;
37
34
  }
38
35
  /**
@@ -46,7 +43,7 @@ export interface ChallengeMessage {
46
43
  recipientId: string;
47
44
  ruleType: string;
48
45
  tableId?: string;
49
- _meta?: Meta;
46
+ meta?: Meta;
50
47
  }
51
48
  /**
52
49
  * Generic structure for table/game events
@@ -55,7 +52,7 @@ export interface TableMessage<T = unknown> {
55
52
  type: string;
56
53
  senderId: string;
57
54
  data: T;
58
- _meta?: Meta;
55
+ meta?: Meta;
59
56
  }
60
57
  /**
61
58
  * Lobby-level information about an active game table
package/dist/types.js CHANGED
@@ -12,17 +12,14 @@ export function isChallengeMessage(msg) {
12
12
  * Returns true if target is not self, not in a game, and not seeking
13
13
  */
14
14
  export function canChallenge(target, currentUserId) {
15
- return (target.userId !== currentUserId &&
16
- !target.tableId &&
17
- !target.seek);
15
+ return target.userId !== currentUserId && !target.tableId && !target.seek;
18
16
  }
19
17
  /**
20
18
  * Predicate: can the current user spectate this target's game?
21
19
  * Returns true if target is at a table and it's not the current user's table
22
20
  */
23
21
  export function canSpectate(target, currentTableId) {
24
- return (!!target.tableId &&
25
- target.tableId !== currentTableId);
22
+ return !!target.tableId && target.tableId !== currentTableId;
26
23
  }
27
24
  /**
28
25
  * Filter: derive active games from presence list
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2FA;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAQ;IACxC,OAAO,GAAG,EAAE,WAAW,KAAK,UAAU,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAQ;IACzC,OAAO,GAAG,EAAE,WAAW,KAAK,WAAW,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB,EAAE,aAAqB;IACzE,OAAO,CACL,MAAM,CAAC,MAAM,KAAK,aAAa;QAC/B,CAAC,MAAM,CAAC,OAAO;QACf,CAAC,MAAM,CAAC,IAAI,CACb,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAuB,EAAE,cAAuB;IAC1E,OAAO,CACL,CAAC,CAAC,MAAM,CAAC,OAAO;QAChB,MAAM,CAAC,OAAO,KAAK,cAAc,CAClC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAwB;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE;oBACxB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,OAAO,EAAE,EAAE;oBACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAE,CAAC,OAAO,CAAC,IAAI,CAAC;gBACtC,EAAE,EAAE,IAAI,CAAC,MAAM;gBACf,IAAI,EAAE,IAAI,CAAC,QAAQ;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAI,IAAY;IAC1C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAwFA;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAQ;IACxC,OAAO,GAAG,EAAE,WAAW,KAAK,UAAU,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAQ;IACzC,OAAO,GAAG,EAAE,WAAW,KAAK,WAAW,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB,EAAE,aAAqB;IACzE,OAAO,MAAM,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAuB,EAAE,cAAuB;IAC1E,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,KAAK,cAAc,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAwB;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE;oBACxB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,OAAO,EAAE,EAAE;oBACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAE,CAAC,OAAO,CAAC,IAAI,CAAC;gBACtC,EAAE,EAAE,IAAI,CAAC,MAAM;gBACf,IAAI,EAAE,IAAI,CAAC,QAAQ;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAI,IAAY;IAC1C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailuge/messaging",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "A stateful messaging library for Nchan-powered real-time applications.",
6
6
  "main": "./dist/index.js",