@pmate/chat-sdk 0.1.13 → 0.1.15

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.
@@ -1,13 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from "react";
3
3
  export function HoldToTalkButton({ tokens, active, disabled, text = "按住 说话", activeText = "松开 结束", onPressStart, onPressMove, onPressEnd, onPressCancel, }) {
4
- return (_jsx("button", { type: "button", disabled: disabled, onContextMenu: (event) => {
4
+ const lockPointerGesture = (event) => {
5
+ event.preventDefault();
6
+ };
7
+ return (_jsx("button", { type: "button", "data-pmate-hold-to-talk": "true", disabled: disabled, onContextMenu: (event) => {
5
8
  event.preventDefault();
6
9
  }, onSelect: (event) => {
7
10
  event.preventDefault();
8
11
  }, onDragStart: (event) => {
9
12
  event.preventDefault();
10
13
  }, onPointerDown: (event) => {
14
+ lockPointerGesture(event);
11
15
  try {
12
16
  event.currentTarget.setPointerCapture(event.pointerId);
13
17
  }
@@ -15,7 +19,16 @@ export function HoldToTalkButton({ tokens, active, disabled, text = "按住 说
15
19
  // ignore unsupported pointer capture cases in test environments
16
20
  }
17
21
  onPressStart?.(event);
18
- }, onPointerMove: onPressMove, onPointerUp: onPressEnd, onPointerCancel: onPressCancel, style: {
22
+ }, onPointerMove: (event) => {
23
+ lockPointerGesture(event);
24
+ onPressMove?.(event);
25
+ }, onPointerUp: (event) => {
26
+ lockPointerGesture(event);
27
+ onPressEnd?.(event);
28
+ }, onPointerCancel: (event) => {
29
+ lockPointerGesture(event);
30
+ onPressCancel?.(event);
31
+ }, style: {
19
32
  width: "100%",
20
33
  borderRadius: tokens.radius.mobileHoldToTalk,
21
34
  background: active ? tokens.color.surfaceInputRecording : "transparent",
@@ -37,6 +50,7 @@ export function HoldToTalkButton({ tokens, active, disabled, text = "按住 说
37
50
  WebkitUserSelect: "none",
38
51
  WebkitTouchCallout: "none",
39
52
  WebkitTapHighlightColor: "transparent",
53
+ overscrollBehavior: "contain",
40
54
  touchAction: "none",
41
55
  transition: `all ${tokens.motion.stateTransition}`,
42
56
  }, children: active ? activeText : text }));
@@ -15,12 +15,14 @@ export type BotLoginSession = {
15
15
  accountId?: string;
16
16
  app?: string;
17
17
  kind?: string;
18
+ profileId?: string;
18
19
  };
19
20
  session?: {
20
21
  identity?: {
21
22
  accountId?: string;
22
23
  app?: string;
23
24
  kind?: string;
25
+ profileId?: string;
24
26
  };
25
27
  };
26
28
  };
@@ -3,6 +3,7 @@ import { type PmateBotKeyFile } from "./botKey.js";
3
3
  import { type ChatBody, type ChatRuntimeMessage, type RealtimeClient } from "./protocol.js";
4
4
  export type ChatClientOptions = {
5
5
  appId: string;
6
+ profileId?: string;
6
7
  botKey?: PmateBotKeyFile | string | unknown;
7
8
  session?: BotLoginSession;
8
9
  hubUrl?: string;
@@ -15,6 +16,7 @@ export declare class ChatClient {
15
16
  private readonly options;
16
17
  private client;
17
18
  private session;
19
+ private profileIdValue;
18
20
  private botKey;
19
21
  private readonly pendingAcks;
20
22
  private readonly messageHandlers;
@@ -61,6 +63,9 @@ export declare class ChatClient {
61
63
  private handleIncoming;
62
64
  private waitUntilConnected;
63
65
  private accountId;
66
+ private resolveProfileId;
67
+ private profileId;
68
+ private fetchProfiles;
64
69
  private requireClient;
65
70
  }
66
71
  export declare function createChatClient(options: ChatClientOptions): ChatClient;
@@ -2,11 +2,13 @@ import { loginPmateBot } from "./botAuth.js";
2
2
  import { parsePmateBotKeyJson } from "./botKey.js";
3
3
  import { ChatMsgKind, ChatMsgOp, ChatRealtimeEvents, RuntimeWebsocketClient, createRuntimeMsg, } from "./protocol.js";
4
4
  const DEFAULT_HUB_URL = "wss://hub.pmate.chat";
5
+ const DEFAULT_AUTH_API_BASE_URL = "https://auth-api-v2.pmate.chat";
5
6
  const DEFAULT_ACK_TIMEOUT_MS = 5_000;
6
7
  export class ChatClient {
7
8
  options;
8
9
  client = null;
9
10
  session = null;
11
+ profileIdValue = null;
10
12
  botKey = null;
11
13
  pendingAcks = new Map();
12
14
  messageHandlers = new Set();
@@ -29,9 +31,9 @@ export class ChatClient {
29
31
  fetchImpl: this.options.fetchImpl,
30
32
  });
31
33
  }
32
- const accountId = this.accountId();
34
+ const profileId = await this.resolveProfileId();
33
35
  if (!this.client) {
34
- const hubUrl = normalizeWsUrl(this.options.hubUrl ?? DEFAULT_HUB_URL, accountId);
36
+ const hubUrl = normalizeWsUrl(this.options.hubUrl ?? DEFAULT_HUB_URL, profileId);
35
37
  this.client = new RuntimeWebsocketClient(hubUrl);
36
38
  }
37
39
  this.client.on(ChatRealtimeEvents.Message, (message) => {
@@ -89,7 +91,12 @@ export class ChatClient {
89
91
  kind: "group",
90
92
  threadKind: "group",
91
93
  action: "groupCreate",
92
- payload: { appId: this.options.appId, groupId: input.groupId, members: input.members, title: input.title },
94
+ payload: {
95
+ appId: this.options.appId,
96
+ groupId: input.groupId,
97
+ members: input.members,
98
+ title: input.title,
99
+ },
93
100
  }, input.members);
94
101
  }
95
102
  async sendGroup(input) {
@@ -101,7 +108,7 @@ export class ChatClient {
101
108
  }, input.recipients);
102
109
  }
103
110
  async sendChat(to, body, recipients) {
104
- const msg = createRuntimeMsg(this.accountId(), to, ChatMsgOp.CHAT, body, ChatMsgKind.DM);
111
+ const msg = createRuntimeMsg(this.profileId(), to, ChatMsgOp.CHAT, body, ChatMsgKind.DM);
105
112
  if (recipients) {
106
113
  msg.recipients = recipients;
107
114
  }
@@ -121,9 +128,11 @@ export class ChatClient {
121
128
  if (!token) {
122
129
  throw new Error("Missing PMate session token");
123
130
  }
124
- const auth = createRuntimeMsg(this.accountId(), "@hub", ChatMsgOp.AUTH, {
131
+ const profileId = this.profileId();
132
+ const auth = createRuntimeMsg(profileId, "@hub", ChatMsgOp.AUTH, {
125
133
  token,
126
134
  as: "user",
135
+ profileId,
127
136
  }, ChatMsgKind.DM);
128
137
  await this.sendAndWaitForAck(auth);
129
138
  }
@@ -190,6 +199,48 @@ export class ChatClient {
190
199
  }
191
200
  return accountId;
192
201
  }
202
+ async resolveProfileId() {
203
+ const explicit = this.options.profileId ||
204
+ this.session?.session?.identity?.profileId ||
205
+ this.session?.identity?.profileId;
206
+ if (explicit) {
207
+ this.profileIdValue = explicit;
208
+ return explicit;
209
+ }
210
+ const accountId = this.accountId();
211
+ const appId = this.session?.session?.identity?.app ||
212
+ this.session?.identity?.app ||
213
+ this.options.appId;
214
+ const token = this.session?.token;
215
+ const profiles = await this.fetchProfiles(appId, accountId, token);
216
+ const profileId = profiles[0]?.id;
217
+ if (!profileId) {
218
+ throw new Error(`Missing profile id for app ${appId} and account ${accountId}`);
219
+ }
220
+ this.profileIdValue = profileId;
221
+ return profileId;
222
+ }
223
+ profileId() {
224
+ if (!this.profileIdValue) {
225
+ throw new Error("ChatClient is missing profile id; call connect() first");
226
+ }
227
+ return this.profileIdValue;
228
+ }
229
+ async fetchProfiles(appId, accountId, token) {
230
+ const fetcher = this.options.fetchImpl ?? fetch;
231
+ const baseUrl = (this.options.authBaseUrl ?? DEFAULT_AUTH_API_BASE_URL).replace(/\/+$/, "");
232
+ const url = new URL(`${baseUrl}/profiles`);
233
+ url.searchParams.set("app", appId);
234
+ url.searchParams.set("account", accountId);
235
+ const response = await fetcher(url.toString(), {
236
+ headers: token ? { Authorization: `Bearer ${token}` } : undefined,
237
+ });
238
+ const json = (await response.json());
239
+ if (!response.ok || !json.success || !Array.isArray(json.data)) {
240
+ throw new Error(json.message || "Failed to resolve chat profile");
241
+ }
242
+ return json.data;
243
+ }
193
244
  requireClient() {
194
245
  if (!this.client) {
195
246
  throw new Error("ChatClient is not connected");
@@ -200,13 +251,13 @@ export class ChatClient {
200
251
  export function createChatClient(options) {
201
252
  return new ChatClient(options);
202
253
  }
203
- function normalizeWsUrl(baseUrl, userId) {
254
+ function normalizeWsUrl(baseUrl, profileId) {
204
255
  const url = new URL(baseUrl);
205
256
  if (url.protocol === "http:")
206
257
  url.protocol = "ws:";
207
258
  if (url.protocol === "https:")
208
259
  url.protocol = "wss:";
209
- url.searchParams.set("userId", userId);
260
+ url.searchParams.set("userId", profileId);
210
261
  return url.toString();
211
262
  }
212
263
  function cleanObject(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/chat-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Cross-platform PMate chat input components, voice input primitives, and ASR helpers",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -29,14 +29,6 @@
29
29
  "publishConfig": {
30
30
  "access": "public"
31
31
  },
32
- "scripts": {
33
- "build": "tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
34
- "prepack": "pnpm run build",
35
- "npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
36
- "test": "vitest run",
37
- "storybook": "storybook dev -p 6009",
38
- "build-storybook": "storybook build --output-dir dist"
39
- },
40
32
  "dependencies": {
41
33
  "@emoji-mart/data": "^1.2.1",
42
34
  "@emoji-mart/react": "^1.1.1",
@@ -51,14 +43,21 @@
51
43
  "devDependencies": {
52
44
  "@storybook/addon-docs": "^9.0.17",
53
45
  "@storybook/react-vite": "9.0.17",
54
- "@tailwindcss/vite": "catalog:",
55
- "@types/react": "catalog:",
56
- "@types/react-dom": "catalog:",
57
- "react": "catalog:",
58
- "react-dom": "catalog:",
46
+ "@tailwindcss/vite": "^4.1.3",
47
+ "@types/react": "^19.2.7",
48
+ "@types/react-dom": "^19.2.3",
49
+ "react": "^19.1.1",
50
+ "react-dom": "^19.1.1",
59
51
  "storybook": "^9.0.17",
60
- "tailwindcss": "catalog:",
52
+ "tailwindcss": "^4.0.14",
61
53
  "typescript": "5.9.2",
62
54
  "vitest": "^4.0.17"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
58
+ "npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
59
+ "test": "vitest run",
60
+ "storybook": "storybook dev -p 6009",
61
+ "build-storybook": "storybook build --output-dir dist"
63
62
  }
64
- }
63
+ }