mezon-light-sdk 1.0.2 → 1.0.3

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,46 +1,259 @@
1
- # mezon-p2p-chat-sdk
1
+ # Mezon Light SDK
2
2
 
3
- SDK chat P2P cho mezon-js.
3
+ A lightweight SDK for Mezon chat integration, providing simple APIs for authentication, real-time messaging, and direct message management.
4
4
 
5
- # Version SDK Example using to demo
6
- ` pip install p2p-chat-sdk `
7
- # Frontend (Client) use SDK
5
+ ## Installation
8
6
 
9
- ## 1: Import
10
-
11
- ```
12
- import { P2PClient, P2PSocket, P2PMessage } from 'p2p-chat-sdk';
7
+ ```bash
8
+ npm install mezon-light-sdk
13
9
  ```
14
10
 
15
- ## 2: Create P2PClient:
16
- ```
17
- const p2p_client = await P2PClient.authenticate({ id_token, user_id, username, serverkey });
11
+ ## Features
12
+
13
+ - 🔐 Simple authentication with ID tokens
14
+ - 💬 Real-time messaging via WebSocket (protobuf-based)
15
+ - 📨 Direct message (DM) and group DM support
16
+ - 🔄 Automatic session management with token refresh
17
+ - 📎 Attachment support for messages
18
+ - 🎯 TypeScript-first with full type definitions
19
+ - ⚡ Exponential backoff for socket connection reliability
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Authenticate with ID Token
24
+
25
+ ```typescript
26
+ import { LightClient, AuthenticationError } from 'mezon-light-sdk';
27
+
28
+ try {
29
+ const client = await LightClient.authenticate({
30
+ id_token: 'your-id-token',
31
+ user_id: 'user-123',
32
+ username: 'johndoe',
33
+ serverkey: 'your-server-key', // optional, uses DEFAULT_SERVER_KEY
34
+ gateway_url: 'https://gw.mezon.ai' // optional, uses MEZON_GW_URL
35
+ });
36
+
37
+ console.log('Authenticated as:', client.userId);
38
+ } catch (error) {
39
+ if (error instanceof AuthenticationError) {
40
+ console.error('Auth failed:', error.message, error.statusCode);
41
+ }
42
+ }
18
43
  ```
19
- ## 3: Connect Socket:
44
+
45
+ ### 2. Restore Session from Storage
46
+
47
+ After login, persist the session data and restore it later:
48
+
49
+ ```typescript
50
+ import { LightClient, SessionError } from 'mezon-light-sdk';
51
+
52
+ // Export session for storage
53
+ const sessionData = client.exportSession();
54
+ // Returns: { token, refresh_token, api_url, user_id }
55
+ localStorage.setItem('mezon_session', JSON.stringify(sessionData));
56
+
57
+ // Later: restore from storage
58
+ try {
59
+ const savedData = JSON.parse(localStorage.getItem('mezon_session')!);
60
+ const client = LightClient.initClient(savedData);
61
+ } catch (error) {
62
+ if (error instanceof SessionError) {
63
+ console.error('Session restore failed:', error.message);
64
+ }
65
+ }
20
66
  ```
21
- const p2p_socket = new P2PSocket(p2p_client.getClient(), p2p_client.getSession());
22
- await p2p_socket.connect();
67
+
68
+ ### 3. Session Management
69
+
70
+ Check and refresh the session before connecting:
71
+
72
+ ```typescript
73
+ // Check if session token is expired
74
+ if (client.isSessionExpired()) {
75
+ // Check if refresh token is still valid
76
+ if (!client.isRefreshSessionExpired()) {
77
+ await client.refreshSession();
78
+ // Update stored session data
79
+ localStorage.setItem('mezon_session', JSON.stringify(client.exportSession()));
80
+ } else {
81
+ // Both tokens expired - need to re-authenticate
82
+ console.log('Session fully expired, please login again');
83
+ }
84
+ }
85
+
86
+ // Access tokens and session directly if needed
87
+ const token = client.getToken();
88
+ const refreshToken = client.getRefreshToken();
89
+ const session = client.getSession();
23
90
  ```
24
- ## 4: Listen Message:
91
+
92
+ ### 4. Connect to Real-time Socket
93
+
94
+ ```typescript
95
+ import { LightSocket, SocketError } from 'mezon-light-sdk';
96
+
97
+ const socket = new LightSocket(client, client.session);
98
+
99
+ await socket.connect({
100
+ onError: (error) => console.error('Socket error:', error),
101
+ onDisconnect: () => console.log('Socket disconnected'),
102
+ verbose: false // set to true for debug logging
103
+ });
104
+
105
+ // Check connection status
106
+ console.log('Connected:', socket.isConnected);
25
107
  ```
26
- p2p_socket.setChannelMessageHandler((msg: P2PMessage) => {
27
- console.log('New message:', msg);
108
+
109
+ ### 5. Listen for Messages
110
+
111
+ ```typescript
112
+ // Register a message handler (returns unsubscribe function)
113
+ const unsubscribe = socket.onChannelMessage((message) => {
114
+ console.log(`Message from ${message.sender_id}: ${message.content}`);
115
+ console.log('Channel:', message.channel_id);
116
+ console.log('Timestamp:', message.create_time_seconds);
117
+ });
118
+
119
+ // Alternative: use setChannelMessageHandler (does not return unsubscribe)
120
+ socket.setChannelMessageHandler((message) => {
121
+ console.log('Received:', message.content);
122
+ });
123
+
124
+ // Multiple handlers can be registered
125
+ const unsubscribe2 = socket.onChannelMessage((message) => {
126
+ // Another handler for the same messages
28
127
  });
29
128
 
129
+ // Unsubscribe when no longer needed
130
+ unsubscribe();
131
+ unsubscribe2();
30
132
  ```
31
133
 
32
- ## 5: Start DM
134
+ ### 6. Create and Join Channels
33
135
 
136
+ ```typescript
137
+ // Create a DM with a single user
138
+ const dmChannel = await client.createDM('peer-user-id');
139
+ await socket.joinDMChannel(dmChannel.channel_id!);
140
+
141
+ // Create a group DM with multiple users
142
+ const groupDM = await client.createGroupDM(['user-1', 'user-2', 'user-3']);
143
+ await socket.joinGroupChannel(groupDM.channel_id!);
144
+
145
+ // Leave channels
146
+ await socket.leaveDMChannel(dmChannel.channel_id!);
147
+ await socket.leaveGroupChannel(groupDM.channel_id!);
34
148
  ```
35
- const channel = await p2p_client.createDM(peerId);
36
- await p2p_socket.joinDMChannel(channel.channel_id);
37
149
 
150
+ ### 7. Send Messages
151
+
152
+ ```typescript
153
+ // Send a DM message
154
+ await socket.sendDM({
155
+ channelId: 'channel-123',
156
+ content: { t: 'Hello, world!' },
157
+ hideLink: false // optional, whether to hide link previews
158
+ });
159
+
160
+ // Send a group message
161
+ await socket.sendGroup({
162
+ channelId: 'group-channel-456',
163
+ content: { t: 'Hello everyone!' },
164
+ attachments: [
165
+ {
166
+ filename: 'image.png',
167
+ url: 'https://cdn.mezon.ai/path/to/image.png',
168
+ filetype: 'image/png',
169
+ size: 42439,
170
+ width: 716,
171
+ height: 522
172
+ }
173
+ ],
174
+ hideLink: true
175
+ });
38
176
  ```
39
177
 
40
- ## 6: Send Message:
178
+ ### 8. Disconnect
41
179
 
180
+ ```typescript
181
+ // Disconnect when done
182
+ socket.disconnect();
42
183
  ```
43
- await p2p_socket.sendDM(channelId, { t: inputMsg });
184
+
185
+ ## API Reference
186
+
187
+ ### LightClient
188
+
189
+ | Property/Method | Description |
190
+ |-----------------|-------------|
191
+ | `LightClient.authenticate(config)` | Static: Authenticate with ID token |
192
+ | `LightClient.initClient(config)` | Static: Initialize from existing tokens |
193
+ | `client.userId` | Get current user ID |
194
+ | `client.session` | Get underlying Session object |
195
+ | `client.client` | Get underlying MezonApi client |
196
+ | `client.createDM(peerId)` | Create a DM channel with one user |
197
+ | `client.createGroupDM(userIds)` | Create a group DM with multiple users |
198
+ | `client.refreshSession()` | Refresh the session using refresh token |
199
+ | `client.isSessionExpired()` | Check if session token is expired |
200
+ | `client.isRefreshSessionExpired()` | Check if refresh token is expired |
201
+ | `client.getToken()` | Get the current auth token |
202
+ | `client.getRefreshToken()` | Get the refresh token |
203
+ | `client.getSession()` | Get the current session object |
204
+ | `client.exportSession()` | Export session data for persistence |
205
+ | `client.createSocket(verbose?, adapter?, timeout?)` | Create a raw socket instance |
206
+
207
+ ### LightSocket
208
+
209
+ | Property/Method | Description |
210
+ |-----------------|-------------|
211
+ | `new LightSocket(client, session)` | Create a new socket instance |
212
+ | `socket.connect(options?)` | Connect to real-time server |
213
+ | `socket.disconnect()` | Disconnect from server |
214
+ | `socket.isConnected` | Check if socket is connected |
215
+ | `socket.socket` | Get underlying Socket (throws if not connected) |
216
+ | `socket.onChannelMessage(handler)` | Register message handler, returns unsubscribe fn |
217
+ | `socket.setChannelMessageHandler(handler)` | Register message handler (alias) |
218
+ | `socket.joinDMChannel(channelId)` | Join a DM channel |
219
+ | `socket.joinGroupChannel(channelId)` | Join a group channel |
220
+ | `socket.leaveDMChannel(channelId)` | Leave a DM channel |
221
+ | `socket.leaveGroupChannel(channelId)` | Leave a group channel |
222
+ | `socket.sendDM(payload)` | Send a DM message |
223
+ | `socket.sendGroup(payload)` | Send a group message |
224
+ | `socket.setErrorHandler(handler)` | Set custom error handler |
225
+
226
+ ### Types
227
+
228
+ ```typescript
229
+ interface ClientInitConfig {
230
+ token: string; // Auth token
231
+ refresh_token: string; // Refresh token
232
+ api_url: string; // API URL
233
+ user_id: string; // User ID
234
+ serverkey?: string; // Optional server key
235
+ }
236
+
237
+ interface AuthenticateConfig {
238
+ id_token: string; // ID token from provider
239
+ user_id: string; // User ID
240
+ username: string; // Username
241
+ serverkey?: string; // Optional server key
242
+ gateway_url?: string; // Optional gateway URL
243
+ }
244
+
245
+ interface SendMessagePayload {
246
+ channelId: string; // Target channel
247
+ content: unknown; // Message content
248
+ attachments?: ApiMessageAttachment[]; // Optional attachments
249
+ hideLink?: boolean; // Hide link previews
250
+ }
251
+
252
+ interface SocketConnectOptions {
253
+ onError?: (error: unknown) => void; // Error callback
254
+ onDisconnect?: () => void; // Disconnect callback
255
+ verbose?: boolean; // Enable debug logging
256
+ }
44
257
  ```
45
- <img width="767" height="775" alt="Screenshot 2025-12-15 132741" src="https://github.com/user-attachments/assets/3019ca86-de5f-49b7-a05a-3895c754029d" />
46
258
 
259
+ ---
@@ -0,0 +1,150 @@
1
+ /** A message sent on a channel. */
2
+ export interface ChannelMessage {
3
+ id: string;
4
+ avatar?: string;
5
+ channel_id: string;
6
+ channel_label: string;
7
+ clan_id?: string;
8
+ code: number;
9
+ content: string;
10
+ attachments?: Array<ApiMessageAttachment>;
11
+ referenced_message?: string[];
12
+ persistent?: boolean;
13
+ sender_id: string;
14
+ update_time?: string;
15
+ clan_logo?: string;
16
+ category_name?: string;
17
+ username?: string;
18
+ clan_nick?: string;
19
+ clan_avatar?: string;
20
+ display_name?: string;
21
+ create_time_seconds?: number;
22
+ update_time_seconds?: number;
23
+ mode?: number;
24
+ message_id?: string;
25
+ hide_editted?: boolean;
26
+ is_public?: boolean;
27
+ topic_id?: string;
28
+ }
29
+ /** */
30
+ export interface ApiMessageAttachment {
31
+ filename?: string;
32
+ filetype?: string;
33
+ height?: number;
34
+ size?: number;
35
+ url?: string;
36
+ width?: number;
37
+ thumbnail?: string;
38
+ channel_id?: string;
39
+ mode?: number;
40
+ channel_label?: string;
41
+ message_id?: string;
42
+ sender_id?: string;
43
+ duration?: number;
44
+ create_time_seconds?: number;
45
+ }
46
+ /** Authenticate against the server with a refresh token. */
47
+ export interface ApiSessionRefreshRequest {
48
+ is_remember?: boolean;
49
+ token?: string;
50
+ vars?: Record<string, string>;
51
+ }
52
+ /** A user's session used to authenticate messages. */
53
+ export interface ApiSession {
54
+ created?: boolean;
55
+ refresh_token?: string;
56
+ token?: string;
57
+ is_remember?: boolean;
58
+ api_url?: string;
59
+ id_token?: string;
60
+ }
61
+ /** */
62
+ export interface ApiChannelDescription {
63
+ active?: number;
64
+ age_restricted?: number;
65
+ category_id?: string;
66
+ category_name?: string;
67
+ channel_id?: string;
68
+ channel_label?: string;
69
+ channel_private?: number;
70
+ clan_id?: string;
71
+ clan_name?: string;
72
+ count_mess_unread?: number;
73
+ create_time_seconds?: number;
74
+ creator_id?: string;
75
+ creator_name?: string;
76
+ e2ee?: number;
77
+ is_mute?: boolean;
78
+ last_pin_message?: string;
79
+ last_seen_message?: ApiChannelMessageHeader;
80
+ last_sent_message?: ApiChannelMessageHeader;
81
+ meeting_code?: string;
82
+ channel_avatar?: string;
83
+ parent_id?: string;
84
+ type?: number;
85
+ update_time_seconds?: number;
86
+ app_id?: string;
87
+ topic?: string;
88
+ user_ids?: Array<string>;
89
+ usernames?: Array<string>;
90
+ display_names?: Array<string>;
91
+ onlines?: Array<boolean>;
92
+ avatars?: Array<string>;
93
+ member_count?: number;
94
+ }
95
+ /** */
96
+ export interface ApiChannelMessageHeader {
97
+ attachment?: Uint8Array;
98
+ content?: string;
99
+ id?: string;
100
+ mention?: Uint8Array;
101
+ reaction?: Uint8Array;
102
+ reference?: Uint8Array;
103
+ repliers?: Array<string>;
104
+ sender_id?: string;
105
+ timestamp_seconds?: number;
106
+ }
107
+ /** Create a channel within clan. */
108
+ export interface ApiCreateChannelDescRequest {
109
+ app_id?: string;
110
+ category_id?: string;
111
+ channel_id?: string;
112
+ channel_label?: string;
113
+ channel_private?: number;
114
+ clan_id?: string;
115
+ parent_id?: string;
116
+ type?: number;
117
+ user_ids?: Array<string>;
118
+ }
119
+ export interface ApiAuthenticationIdToken {
120
+ /** ID token from identity provider */
121
+ id_token: string;
122
+ /** User ID associated with the token. */
123
+ user_id: string;
124
+ /** Username associated with the token. */
125
+ username: string;
126
+ }
127
+ export interface AuthenticationIdTokenResponse {
128
+ /** Authentication token */
129
+ token: string;
130
+ /** Refresh token for session renewal */
131
+ refresh_token: string;
132
+ /** API URL for the authenticated user */
133
+ api_url: string;
134
+ /** User ID of the authenticated user */
135
+ user_id: string;
136
+ }
137
+ export declare class MezonApi {
138
+ readonly serverKey: string;
139
+ readonly timeoutMs: number;
140
+ basePath: string;
141
+ constructor(serverKey: string, timeoutMs: number, basePath: string);
142
+ setBasePath(basePath: string): void;
143
+ /** Create a new channel with the current user as the owner. */
144
+ authenticateIdToken(basicAuthUsername: string, basicAuthPassword: string, body: ApiAuthenticationIdToken, options?: any): Promise<AuthenticationIdTokenResponse>;
145
+ /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */
146
+ sessionRefresh(basicAuthUsername: string, basicAuthPassword: string, body: ApiSessionRefreshRequest, options?: any): Promise<ApiSession>;
147
+ /** Create a new channel with the current user as the owner. */
148
+ createChannelDesc(bearerToken: string, body: ApiCreateChannelDescRequest, options?: any): Promise<ApiChannelDescription>;
149
+ buildFullUrl(basePath: string, fragment: string, queryParams: Map<string, any>): string;
150
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ // tslint:disable
3
+ /* Code generated by openapi-gen/main.go. DO NOT EDIT. */
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.MezonApi = void 0;
29
+ const utils_1 = require("./utils");
30
+ const tsproto = __importStar(require("./proto/api"));
31
+ const js_base64_1 = require("js-base64");
32
+ class MezonApi {
33
+ constructor(serverKey, timeoutMs, basePath) {
34
+ this.serverKey = serverKey;
35
+ this.timeoutMs = timeoutMs;
36
+ this.basePath = basePath;
37
+ }
38
+ setBasePath(basePath) {
39
+ this.basePath = basePath;
40
+ }
41
+ /** Create a new channel with the current user as the owner. */
42
+ authenticateIdToken(basicAuthUsername, basicAuthPassword, body, options = {}) {
43
+ if (body === null || body === undefined) {
44
+ throw new Error("'body' is a required parameter but is null or undefined.");
45
+ }
46
+ const urlPath = "/v2/account/authenticate/idtoken";
47
+ const queryParams = new Map();
48
+ let bodyJson = "";
49
+ bodyJson = JSON.stringify(body || {});
50
+ const fullUrl = this.buildFullUrl(this.basePath, urlPath, queryParams);
51
+ const fetchOptions = (0, utils_1.buildFetchOptions)("POST", options, bodyJson);
52
+ if (basicAuthUsername) {
53
+ fetchOptions.headers["Authorization"] = "Basic " + (0, js_base64_1.encode)(basicAuthUsername + ":" + basicAuthPassword);
54
+ }
55
+ return Promise.race([
56
+ fetch(fullUrl, fetchOptions).then((response) => {
57
+ if (response.status == 204) {
58
+ return response;
59
+ }
60
+ else if (response.status >= 200 && response.status < 300) {
61
+ return response.json();
62
+ }
63
+ else {
64
+ throw response;
65
+ }
66
+ }),
67
+ new Promise((_, reject) => setTimeout(reject, this.timeoutMs, "Request timed out.")),
68
+ ]);
69
+ }
70
+ /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */
71
+ sessionRefresh(basicAuthUsername, basicAuthPassword, body, options = {}) {
72
+ if (body === null || body === undefined) {
73
+ throw new Error("'body' is a required parameter but is null or undefined.");
74
+ }
75
+ const urlPath = "/mezon.api.Mezon/SessionRefresh";
76
+ const queryParams = new Map();
77
+ const bodyWriter = tsproto.SessionRefreshRequest.encode(tsproto.SessionRefreshRequest.fromPartial(body));
78
+ const encodedBody = bodyWriter.finish();
79
+ const fullUrl = this.buildFullUrl(this.basePath, urlPath, queryParams);
80
+ const fetchOptions = (0, utils_1.buildFetchOptions)("POST", options, "");
81
+ fetchOptions.body = encodedBody;
82
+ if (basicAuthUsername) {
83
+ fetchOptions.headers["Authorization"] = "Basic " + (0, js_base64_1.encode)(basicAuthUsername + ":" + basicAuthPassword);
84
+ }
85
+ return Promise.race([
86
+ fetch(fullUrl, fetchOptions).then(async (response) => {
87
+ if (response.status == 204) {
88
+ return {};
89
+ }
90
+ else if (response.status >= 200 && response.status < 300) {
91
+ const buffer = await response.arrayBuffer();
92
+ return tsproto.Session.decode(new Uint8Array(buffer));
93
+ }
94
+ else {
95
+ throw response;
96
+ }
97
+ }),
98
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out.")), this.timeoutMs)),
99
+ ]);
100
+ }
101
+ /** Create a new channel with the current user as the owner. */
102
+ createChannelDesc(bearerToken, body, options = {}) {
103
+ if (body === null || body === undefined) {
104
+ throw new Error("'body' is a required parameter but is null or undefined.");
105
+ }
106
+ const urlPath = "/mezon.api.Mezon/CreateChannelDesc";
107
+ const queryParams = new Map();
108
+ const bodyWriter = tsproto.CreateChannelDescRequest.encode(tsproto.CreateChannelDescRequest.fromPartial(body));
109
+ const encodedBody = bodyWriter.finish();
110
+ const fullUrl = this.buildFullUrl(this.basePath, urlPath, queryParams);
111
+ const fetchOptions = (0, utils_1.buildFetchOptions)("POST", options, "");
112
+ fetchOptions.body = encodedBody;
113
+ if (bearerToken) {
114
+ fetchOptions.headers["Authorization"] = "Bearer " + bearerToken;
115
+ }
116
+ return Promise.race([
117
+ fetch(fullUrl, fetchOptions).then(async (response) => {
118
+ if (response.status == 204) {
119
+ return {};
120
+ }
121
+ else if (response.status >= 200 && response.status < 300) {
122
+ const buffer = await response.arrayBuffer();
123
+ return tsproto.ChannelDescription.decode(new Uint8Array(buffer));
124
+ }
125
+ else {
126
+ throw response;
127
+ }
128
+ }),
129
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out.")), this.timeoutMs)),
130
+ ]);
131
+ }
132
+ buildFullUrl(basePath, fragment, queryParams) {
133
+ let fullPath = basePath + fragment + "?";
134
+ for (let [k, v] of queryParams) {
135
+ if (v instanceof Array) {
136
+ fullPath += v.reduce((prev, curr) => {
137
+ return prev + encodeURIComponent(k) + "=" + encodeURIComponent(curr) + "&";
138
+ }, "");
139
+ }
140
+ else {
141
+ if (v != null) {
142
+ fullPath += encodeURIComponent(k) + "=" + encodeURIComponent(v) + "&";
143
+ }
144
+ }
145
+ }
146
+ return fullPath;
147
+ }
148
+ }
149
+ exports.MezonApi = MezonApi;