@cstar.help/js 0.5.0 → 0.5.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.
@@ -294,6 +294,7 @@ var ChatClient = class {
294
294
  this.supabaseAnonKey = config.supabaseAnonKey;
295
295
  this.conversations = new ConversationsApi(this);
296
296
  this.messages = new MessagesApi(this);
297
+ this.community = new ChatCommunityApi(this);
297
298
  }
298
299
  /**
299
300
  * Identify a customer using HMAC signature verification.
@@ -532,6 +533,84 @@ var ConversationsApi = class {
532
533
  };
533
534
  }
534
535
  };
536
+ var ChatCommunityApi = class {
537
+ constructor(chat) {
538
+ this.chat = chat;
539
+ }
540
+ /** Create a new community post as the identified customer. */
541
+ async createPost(params) {
542
+ const teamId = this.chat.getTeamId();
543
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
544
+ const response = await this.chat.fetch("/api/widget/community", {
545
+ method: "POST",
546
+ body: {
547
+ teamId,
548
+ action: "createPost",
549
+ title: params.title,
550
+ body: params.body,
551
+ topicId: params.topicId
552
+ }
553
+ });
554
+ return response.data.post;
555
+ }
556
+ /** Toggle vote on a post. Returns new vote state and count. */
557
+ async vote(postId) {
558
+ const teamId = this.chat.getTeamId();
559
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
560
+ const response = await this.chat.fetch("/api/widget/community", {
561
+ method: "POST",
562
+ body: { teamId, action: "vote", postId }
563
+ });
564
+ return response.data;
565
+ }
566
+ /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
567
+ async comment(postId, params) {
568
+ const teamId = this.chat.getTeamId();
569
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
570
+ const response = await this.chat.fetch("/api/widget/community", {
571
+ method: "POST",
572
+ body: {
573
+ teamId,
574
+ action: "createComment",
575
+ postId,
576
+ commentBody: params.body,
577
+ parentCommentId: params.parentCommentId ?? null
578
+ }
579
+ });
580
+ return response.data.comment;
581
+ }
582
+ /** Toggle follow on a post. */
583
+ async follow(postId) {
584
+ const teamId = this.chat.getTeamId();
585
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
586
+ const response = await this.chat.fetch("/api/widget/community", {
587
+ method: "POST",
588
+ body: { teamId, action: "follow", postId }
589
+ });
590
+ return response.data;
591
+ }
592
+ /** Check if the current user has voted on a post. */
593
+ async getVoteStatus(postId) {
594
+ const teamId = this.chat.getTeamId();
595
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
596
+ const response = await this.chat.fetch("/api/widget/community", {
597
+ method: "GET",
598
+ query: { teamId, postId }
599
+ });
600
+ return response.data.voted;
601
+ }
602
+ /** Check vote status for multiple posts in one request. */
603
+ async bulkVoteStatus(postIds) {
604
+ if (postIds.length === 0) return {};
605
+ const teamId = this.chat.getTeamId();
606
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
607
+ const response = await this.chat.fetch("/api/widget/community", {
608
+ method: "GET",
609
+ query: { teamId, postIds: postIds.join(",") }
610
+ });
611
+ return response.data;
612
+ }
613
+ };
535
614
  var MessagesApi = class {
536
615
  constructor(chat) {
537
616
  this.chat = chat;
@@ -97,6 +97,61 @@ type MessageCallback = (message: WidgetMessage) => void;
97
97
  /** Callback for typing indicator events. */
98
98
  type TypingCallback = (payload: TypingPayload) => void;
99
99
 
100
+ /** Parameters for creating a post as an identified customer. */
101
+ interface CommunityCreatePostParams {
102
+ /** Post title. */
103
+ title: string;
104
+ /** Post body/content (optional). */
105
+ body?: string;
106
+ /** Topic UUID to post in. */
107
+ topicId: string;
108
+ }
109
+ /** Parameters for creating a comment as an identified customer. */
110
+ interface CommunityCreateCommentParams {
111
+ /** Comment body text. */
112
+ body: string;
113
+ /** Parent comment ID for replies (optional, 1-level nesting). */
114
+ parentCommentId?: string | null;
115
+ }
116
+ /** Response from creating a post. */
117
+ interface CommunityCreatedPost {
118
+ id: string;
119
+ title: string;
120
+ slug: string;
121
+ body: string;
122
+ status: string;
123
+ visibility: string;
124
+ voteCount: number;
125
+ commentCount: number;
126
+ topicName: string;
127
+ topicSlug: string;
128
+ authorName: string;
129
+ authorAvatar: string;
130
+ isTeamMember: boolean;
131
+ createdAt: string;
132
+ }
133
+ /** Response from creating a comment. */
134
+ interface CommunityCreatedComment {
135
+ id: string;
136
+ body: string;
137
+ isOfficial: boolean;
138
+ voteScore: number;
139
+ parentCommentId: string | null;
140
+ authorName: string;
141
+ authorAvatar: string;
142
+ isTeamMember: boolean;
143
+ createdAt: string;
144
+ }
145
+ /** Response from toggling a vote. */
146
+ interface CommunityVoteResult {
147
+ voted: boolean;
148
+ voteCount: number;
149
+ }
150
+ /** Response from toggling a follow. */
151
+ interface CommunityFollowResult {
152
+ following: boolean;
153
+ }
154
+
100
155
  /**
101
156
  * Customer-facing chat client for the cStar widget API.
102
157
  *
@@ -136,6 +191,8 @@ declare class ChatClient {
136
191
  readonly conversations: ConversationsApi;
137
192
  /** Sub-resource for managing messages. */
138
193
  readonly messages: MessagesApi;
194
+ /** Sub-resource for community actions (create posts, vote, comment). Uses the same session as chat. */
195
+ readonly community: ChatCommunityApi;
139
196
  constructor(config: ChatConfig);
140
197
  /**
141
198
  * Identify a customer using HMAC signature verification.
@@ -196,6 +253,26 @@ declare class ConversationsApi {
196
253
  /** Create a new conversation (ticket). */
197
254
  create(params: ConversationCreateParams): Promise<Conversation>;
198
255
  }
256
+ /**
257
+ * Community sub-resource on ChatClient.
258
+ * Uses the same session token from `identify()` to authenticate community actions.
259
+ */
260
+ declare class ChatCommunityApi {
261
+ private readonly chat;
262
+ constructor(chat: ChatClient);
263
+ /** Create a new community post as the identified customer. */
264
+ createPost(params: CommunityCreatePostParams): Promise<CommunityCreatedPost>;
265
+ /** Toggle vote on a post. Returns new vote state and count. */
266
+ vote(postId: string): Promise<CommunityVoteResult>;
267
+ /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
268
+ comment(postId: string, params: CommunityCreateCommentParams): Promise<CommunityCreatedComment>;
269
+ /** Toggle follow on a post. */
270
+ follow(postId: string): Promise<CommunityFollowResult>;
271
+ /** Check if the current user has voted on a post. */
272
+ getVoteStatus(postId: string): Promise<boolean>;
273
+ /** Check vote status for multiple posts in one request. */
274
+ bulkVoteStatus(postIds: string[]): Promise<Record<string, boolean>>;
275
+ }
199
276
  /** Messages sub-resource on ChatClient. */
200
277
  declare class MessagesApi {
201
278
  private readonly chat;
@@ -97,6 +97,61 @@ type MessageCallback = (message: WidgetMessage) => void;
97
97
  /** Callback for typing indicator events. */
98
98
  type TypingCallback = (payload: TypingPayload) => void;
99
99
 
100
+ /** Parameters for creating a post as an identified customer. */
101
+ interface CommunityCreatePostParams {
102
+ /** Post title. */
103
+ title: string;
104
+ /** Post body/content (optional). */
105
+ body?: string;
106
+ /** Topic UUID to post in. */
107
+ topicId: string;
108
+ }
109
+ /** Parameters for creating a comment as an identified customer. */
110
+ interface CommunityCreateCommentParams {
111
+ /** Comment body text. */
112
+ body: string;
113
+ /** Parent comment ID for replies (optional, 1-level nesting). */
114
+ parentCommentId?: string | null;
115
+ }
116
+ /** Response from creating a post. */
117
+ interface CommunityCreatedPost {
118
+ id: string;
119
+ title: string;
120
+ slug: string;
121
+ body: string;
122
+ status: string;
123
+ visibility: string;
124
+ voteCount: number;
125
+ commentCount: number;
126
+ topicName: string;
127
+ topicSlug: string;
128
+ authorName: string;
129
+ authorAvatar: string;
130
+ isTeamMember: boolean;
131
+ createdAt: string;
132
+ }
133
+ /** Response from creating a comment. */
134
+ interface CommunityCreatedComment {
135
+ id: string;
136
+ body: string;
137
+ isOfficial: boolean;
138
+ voteScore: number;
139
+ parentCommentId: string | null;
140
+ authorName: string;
141
+ authorAvatar: string;
142
+ isTeamMember: boolean;
143
+ createdAt: string;
144
+ }
145
+ /** Response from toggling a vote. */
146
+ interface CommunityVoteResult {
147
+ voted: boolean;
148
+ voteCount: number;
149
+ }
150
+ /** Response from toggling a follow. */
151
+ interface CommunityFollowResult {
152
+ following: boolean;
153
+ }
154
+
100
155
  /**
101
156
  * Customer-facing chat client for the cStar widget API.
102
157
  *
@@ -136,6 +191,8 @@ declare class ChatClient {
136
191
  readonly conversations: ConversationsApi;
137
192
  /** Sub-resource for managing messages. */
138
193
  readonly messages: MessagesApi;
194
+ /** Sub-resource for community actions (create posts, vote, comment). Uses the same session as chat. */
195
+ readonly community: ChatCommunityApi;
139
196
  constructor(config: ChatConfig);
140
197
  /**
141
198
  * Identify a customer using HMAC signature verification.
@@ -196,6 +253,26 @@ declare class ConversationsApi {
196
253
  /** Create a new conversation (ticket). */
197
254
  create(params: ConversationCreateParams): Promise<Conversation>;
198
255
  }
256
+ /**
257
+ * Community sub-resource on ChatClient.
258
+ * Uses the same session token from `identify()` to authenticate community actions.
259
+ */
260
+ declare class ChatCommunityApi {
261
+ private readonly chat;
262
+ constructor(chat: ChatClient);
263
+ /** Create a new community post as the identified customer. */
264
+ createPost(params: CommunityCreatePostParams): Promise<CommunityCreatedPost>;
265
+ /** Toggle vote on a post. Returns new vote state and count. */
266
+ vote(postId: string): Promise<CommunityVoteResult>;
267
+ /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
268
+ comment(postId: string, params: CommunityCreateCommentParams): Promise<CommunityCreatedComment>;
269
+ /** Toggle follow on a post. */
270
+ follow(postId: string): Promise<CommunityFollowResult>;
271
+ /** Check if the current user has voted on a post. */
272
+ getVoteStatus(postId: string): Promise<boolean>;
273
+ /** Check vote status for multiple posts in one request. */
274
+ bulkVoteStatus(postIds: string[]): Promise<Record<string, boolean>>;
275
+ }
199
276
  /** Messages sub-resource on ChatClient. */
200
277
  declare class MessagesApi {
201
278
  private readonly chat;
@@ -258,6 +258,7 @@ var ChatClient = class {
258
258
  this.supabaseAnonKey = config.supabaseAnonKey;
259
259
  this.conversations = new ConversationsApi(this);
260
260
  this.messages = new MessagesApi(this);
261
+ this.community = new ChatCommunityApi(this);
261
262
  }
262
263
  /**
263
264
  * Identify a customer using HMAC signature verification.
@@ -496,6 +497,84 @@ var ConversationsApi = class {
496
497
  };
497
498
  }
498
499
  };
500
+ var ChatCommunityApi = class {
501
+ constructor(chat) {
502
+ this.chat = chat;
503
+ }
504
+ /** Create a new community post as the identified customer. */
505
+ async createPost(params) {
506
+ const teamId = this.chat.getTeamId();
507
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
508
+ const response = await this.chat.fetch("/api/widget/community", {
509
+ method: "POST",
510
+ body: {
511
+ teamId,
512
+ action: "createPost",
513
+ title: params.title,
514
+ body: params.body,
515
+ topicId: params.topicId
516
+ }
517
+ });
518
+ return response.data.post;
519
+ }
520
+ /** Toggle vote on a post. Returns new vote state and count. */
521
+ async vote(postId) {
522
+ const teamId = this.chat.getTeamId();
523
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
524
+ const response = await this.chat.fetch("/api/widget/community", {
525
+ method: "POST",
526
+ body: { teamId, action: "vote", postId }
527
+ });
528
+ return response.data;
529
+ }
530
+ /** Add a comment to a post. Supports 1-level nesting via parentCommentId. */
531
+ async comment(postId, params) {
532
+ const teamId = this.chat.getTeamId();
533
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
534
+ const response = await this.chat.fetch("/api/widget/community", {
535
+ method: "POST",
536
+ body: {
537
+ teamId,
538
+ action: "createComment",
539
+ postId,
540
+ commentBody: params.body,
541
+ parentCommentId: params.parentCommentId ?? null
542
+ }
543
+ });
544
+ return response.data.comment;
545
+ }
546
+ /** Toggle follow on a post. */
547
+ async follow(postId) {
548
+ const teamId = this.chat.getTeamId();
549
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
550
+ const response = await this.chat.fetch("/api/widget/community", {
551
+ method: "POST",
552
+ body: { teamId, action: "follow", postId }
553
+ });
554
+ return response.data;
555
+ }
556
+ /** Check if the current user has voted on a post. */
557
+ async getVoteStatus(postId) {
558
+ const teamId = this.chat.getTeamId();
559
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
560
+ const response = await this.chat.fetch("/api/widget/community", {
561
+ method: "GET",
562
+ query: { teamId, postId }
563
+ });
564
+ return response.data.voted;
565
+ }
566
+ /** Check vote status for multiple posts in one request. */
567
+ async bulkVoteStatus(postIds) {
568
+ if (postIds.length === 0) return {};
569
+ const teamId = this.chat.getTeamId();
570
+ if (!teamId) throw new Error("Not identified. Call chat.identify() first.");
571
+ const response = await this.chat.fetch("/api/widget/community", {
572
+ method: "GET",
573
+ query: { teamId, postIds: postIds.join(",") }
574
+ });
575
+ return response.data;
576
+ }
577
+ };
499
578
  var MessagesApi = class {
500
579
  constructor(chat) {
501
580
  this.chat = chat;
@@ -26,9 +26,30 @@ module.exports = __toCommonJS(community_exports);
26
26
  var DEFAULT_BASE_URL = "https://www.cstar.help";
27
27
  var CommunityClient = class {
28
28
  constructor(config) {
29
+ this.sessionToken = null;
30
+ this.teamId = null;
29
31
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
30
32
  this.teamSlug = config.teamSlug;
31
33
  }
34
+ /**
35
+ * Set a session token from identity verification.
36
+ * This enables authenticated operations (create posts, vote, comment).
37
+ *
38
+ * The token is the same `sessionToken` returned by `ChatClient.identify()`.
39
+ */
40
+ setSessionToken(token, teamId) {
41
+ this.sessionToken = token;
42
+ if (teamId) this.teamId = teamId;
43
+ }
44
+ /** Clear the session token and return to read-only mode. */
45
+ clearSession() {
46
+ this.sessionToken = null;
47
+ this.teamId = null;
48
+ }
49
+ /** Whether the client has an active session token. */
50
+ get isAuthenticated() {
51
+ return this.sessionToken !== null;
52
+ }
32
53
  /** List all public community topics. */
33
54
  async topics() {
34
55
  const response = await this.fetch(
@@ -66,6 +87,134 @@ var CommunityClient = class {
66
87
  q: query
67
88
  });
68
89
  }
90
+ // ── Authenticated Actions (require setSessionToken) ──
91
+ /**
92
+ * Create a new community post. Requires an active session token.
93
+ *
94
+ * @throws Error if no session token is set or the request fails
95
+ */
96
+ async createPost(params) {
97
+ const teamId = this.resolveTeamId();
98
+ const response = await this.authenticatedFetch("/api/widget/community", {
99
+ method: "POST",
100
+ body: {
101
+ teamId,
102
+ action: "createPost",
103
+ title: params.title,
104
+ body: params.body,
105
+ topicId: params.topicId
106
+ }
107
+ });
108
+ return response.data.post;
109
+ }
110
+ /**
111
+ * Toggle vote on a post. Returns new vote state and count.
112
+ * Requires an active session token.
113
+ */
114
+ async vote(postId) {
115
+ const teamId = this.resolveTeamId();
116
+ const response = await this.authenticatedFetch("/api/widget/community", {
117
+ method: "POST",
118
+ body: { teamId, action: "vote", postId }
119
+ });
120
+ return response.data;
121
+ }
122
+ /**
123
+ * Add a comment to a post. Requires an active session token.
124
+ */
125
+ async comment(postId, params) {
126
+ const teamId = this.resolveTeamId();
127
+ const response = await this.authenticatedFetch("/api/widget/community", {
128
+ method: "POST",
129
+ body: {
130
+ teamId,
131
+ action: "createComment",
132
+ postId,
133
+ commentBody: params.body,
134
+ parentCommentId: params.parentCommentId ?? null
135
+ }
136
+ });
137
+ return response.data.comment;
138
+ }
139
+ /**
140
+ * Toggle follow on a post. Requires an active session token.
141
+ */
142
+ async follow(postId) {
143
+ const teamId = this.resolveTeamId();
144
+ const response = await this.authenticatedFetch("/api/widget/community", {
145
+ method: "POST",
146
+ body: { teamId, action: "follow", postId }
147
+ });
148
+ return response.data;
149
+ }
150
+ /**
151
+ * Check if the current user has voted on a specific post.
152
+ * Requires an active session token.
153
+ */
154
+ async getVoteStatus(postId) {
155
+ const teamId = this.resolveTeamId();
156
+ const response = await this.authenticatedFetch("/api/widget/community", {
157
+ method: "GET",
158
+ query: { teamId, postId }
159
+ });
160
+ return response.data.voted;
161
+ }
162
+ /**
163
+ * Check vote status for multiple posts in a single request.
164
+ * Returns a map of postId → voted boolean.
165
+ * Requires an active session token.
166
+ */
167
+ async bulkVoteStatus(postIds) {
168
+ if (postIds.length === 0) return {};
169
+ const teamId = this.resolveTeamId();
170
+ const response = await this.authenticatedFetch("/api/widget/community", {
171
+ method: "GET",
172
+ query: { teamId, postIds: postIds.join(",") }
173
+ });
174
+ return response.data;
175
+ }
176
+ // ── Internal Helpers ──
177
+ /**
178
+ * Resolve team slug to team ID (cached after first call).
179
+ * For authenticated operations that require teamId.
180
+ */
181
+ resolveTeamId() {
182
+ if (this.teamId) return this.teamId;
183
+ throw new Error(
184
+ "Team ID is required for authenticated operations. Pass it via setSessionToken(token, teamId) or use ChatClient.identify() which provides it automatically."
185
+ );
186
+ }
187
+ async authenticatedFetch(path, options) {
188
+ if (!this.sessionToken) {
189
+ throw new Error(
190
+ "Session token required. Call setSessionToken() or use ChatClient.identify() first."
191
+ );
192
+ }
193
+ let url = `${this.baseUrl}${path}`;
194
+ if (options.query) {
195
+ const params = new URLSearchParams();
196
+ for (const [key, value] of Object.entries(options.query)) {
197
+ if (value !== void 0) params.set(key, value);
198
+ }
199
+ const qs = params.toString();
200
+ if (qs) url += `?${qs}`;
201
+ }
202
+ const headers = {
203
+ "Content-Type": "application/json",
204
+ Authorization: `Bearer ${this.sessionToken}`
205
+ };
206
+ const response = await globalThis.fetch(url, {
207
+ method: options.method,
208
+ headers,
209
+ body: options.body ? JSON.stringify(options.body) : void 0
210
+ });
211
+ if (!response.ok) {
212
+ const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
213
+ const message = typeof errorBody.error === "string" ? errorBody.error : errorBody.error?.message ?? `Community API error: ${response.status}`;
214
+ throw new Error(message);
215
+ }
216
+ return response.json();
217
+ }
69
218
  async fetch(path, query) {
70
219
  let url = `${this.baseUrl}${path}`;
71
220
  if (query) {