@cstar.help/js 0.1.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/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # @cstar.help/js
2
+
3
+ Official TypeScript SDK for the [cStar](https://cstar.help) customer support platform.
4
+
5
+ - **Zero dependencies** — works with Node.js 18+ native `fetch`
6
+ - **TypeScript-first** — full type definitions included
7
+ - **Four entry points** — tree-shake what you don't need
8
+ - **ESM + CJS** — works everywhere
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @cstar.help/js
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ### Server-side API Client
19
+
20
+ Manage tickets, customers, articles, and webhooks with your secret API key.
21
+
22
+ ```typescript
23
+ import { CStarClient } from '@cstar.help/js';
24
+
25
+ const cstar = new CStarClient({
26
+ apiKey: 'sk_live_abc123...',
27
+ teamId: 'tm_your_team_id',
28
+ });
29
+
30
+ // List open tickets
31
+ const { data: tickets } = await cstar.tickets.list({ status: 'open' });
32
+
33
+ // Create a ticket
34
+ const { data: ticket } = await cstar.tickets.create({
35
+ title: 'Help with billing',
36
+ customerId: 'cus_abc123',
37
+ });
38
+
39
+ // Get a customer with expanded fields
40
+ const { data: customer } = await cstar.customers.get('cus_abc123');
41
+
42
+ // Auto-paginate through all tickets
43
+ for await (const ticket of cstar.tickets.listAutoPaginating({ status: 'open' })) {
44
+ console.log(ticket.title);
45
+ }
46
+ ```
47
+
48
+ ### Customer Chat (Widget SDK)
49
+
50
+ Build custom chat experiences with identity verification.
51
+
52
+ ```typescript
53
+ import { ChatClient } from '@cstar.help/js/chat';
54
+
55
+ const chat = new ChatClient({ teamSlug: 'acme' });
56
+
57
+ // Identify the customer (HMAC signature from your server)
58
+ await chat.identify(
59
+ { externalId: 'usr_123', email: 'jane@example.com', name: 'Jane', timestamp: Date.now() / 1000 },
60
+ hmacSignature
61
+ );
62
+
63
+ // List conversations
64
+ const { conversations } = await chat.conversations.list();
65
+
66
+ // Send a message
67
+ await chat.messages.send('tkt_abc123', 'Thanks for the help!');
68
+
69
+ // Subscribe to new messages (polling)
70
+ const unsubscribe = chat.onMessage('tkt_abc123', (message) => {
71
+ console.log(`${message.sender_name}: ${message.content}`);
72
+ });
73
+
74
+ // Clean up
75
+ chat.disconnect();
76
+ ```
77
+
78
+ ### Public Knowledge Base
79
+
80
+ Search and browse your public help center — no authentication required.
81
+
82
+ ```typescript
83
+ import { LibraryClient } from '@cstar.help/js/library';
84
+
85
+ const library = new LibraryClient({ teamSlug: 'acme' });
86
+
87
+ // List categories
88
+ const categories = await library.categories();
89
+
90
+ // Search articles
91
+ const results = await library.search('reset password');
92
+
93
+ // Get a specific article
94
+ const article = await library.article('how-to-reset-password');
95
+ ```
96
+
97
+ ### Webhook Verification
98
+
99
+ Verify incoming webhook signatures on your server.
100
+
101
+ ```typescript
102
+ import { constructEvent } from '@cstar.help/js/webhook';
103
+
104
+ // In your webhook handler (Express, Hono, etc.)
105
+ const event = constructEvent(rawBody, signature, 'whsec_your_secret');
106
+
107
+ switch (event.type) {
108
+ case 'ticket.created':
109
+ console.log('New ticket:', event.data.title);
110
+ break;
111
+ case 'customer.created':
112
+ console.log('New customer:', event.data.email);
113
+ break;
114
+ }
115
+ ```
116
+
117
+ For edge environments (Cloudflare Workers, Deno), use the async variant:
118
+
119
+ ```typescript
120
+ import { constructEventAsync } from '@cstar.help/js/webhook';
121
+
122
+ const event = await constructEventAsync(rawBody, signature, secret);
123
+ ```
124
+
125
+ ## API Reference
126
+
127
+ ### CStarClient
128
+
129
+ | Resource | Methods |
130
+ |----------|---------|
131
+ | `cstar.tickets` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
132
+ | `cstar.tickets.messages` | `.list(ticketId)` `.create(ticketId, params)` |
133
+ | `cstar.customers` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
134
+ | `cstar.articles` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.listAutoPaginating()` |
135
+ | `cstar.webhooks` | `.list()` `.get(id)` `.create(params)` `.update(id, params)` `.del(id)` `.test(id)` |
136
+
137
+ ### Configuration
138
+
139
+ ```typescript
140
+ const cstar = new CStarClient({
141
+ apiKey: 'sk_live_...', // Required — secret or publishable key
142
+ teamId: 'tm_...', // Required — your team ID
143
+ baseUrl: 'https://...', // Optional — defaults to https://app.cstar.help
144
+ version: '2026-03-01', // Optional — API version header
145
+ maxRetries: 3, // Optional — retry attempts for 5xx/429
146
+ timeout: 30000, // Optional — request timeout in ms
147
+ });
148
+ ```
149
+
150
+ ### Test Mode
151
+
152
+ Test mode is auto-detected from your API key prefix:
153
+
154
+ ```typescript
155
+ // Uses test data — no real customers affected
156
+ const cstar = new CStarClient({
157
+ apiKey: 'sk_test_abc123...',
158
+ teamId: 'tm_your_team_id',
159
+ });
160
+
161
+ console.log(cstar.isTestMode); // true
162
+ ```
163
+
164
+ ### Error Handling
165
+
166
+ All API errors throw typed exceptions:
167
+
168
+ ```typescript
169
+ import { CStarError, CStarNotFoundError, CStarValidationError } from '@cstar.help/js';
170
+
171
+ try {
172
+ await cstar.tickets.get('tkt_nonexistent');
173
+ } catch (err) {
174
+ if (err instanceof CStarNotFoundError) {
175
+ console.log('Ticket not found:', err.message);
176
+ } else if (err instanceof CStarValidationError) {
177
+ console.log('Invalid param:', err.param);
178
+ } else if (err instanceof CStarError) {
179
+ console.log('API error:', err.code, err.statusCode);
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Idempotency
185
+
186
+ Safely retry create operations:
187
+
188
+ ```typescript
189
+ const { data: ticket } = await cstar.tickets.create(
190
+ { title: 'Billing issue', customerId: 'cus_abc' },
191
+ { idempotencyKey: 'create-billing-issue-usr123' }
192
+ );
193
+ ```
194
+
195
+ ## Requirements
196
+
197
+ - Node.js 18+ (for native `fetch` and `AbortSignal.timeout`)
198
+ - TypeScript 5.0+ (for type definitions)
199
+
200
+ ## License
201
+
202
+ MIT
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/chat/index.ts
21
+ var chat_exports = {};
22
+ __export(chat_exports, {
23
+ ChatClient: () => ChatClient
24
+ });
25
+ module.exports = __toCommonJS(chat_exports);
26
+ var DEFAULT_BASE_URL = "https://app.cstar.help";
27
+ var DEFAULT_POLLING_INTERVAL = 3e3;
28
+ var ChatClient = class {
29
+ constructor(config) {
30
+ this.sessionToken = null;
31
+ this.teamId = null;
32
+ this.pollingTimers = /* @__PURE__ */ new Map();
33
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
34
+ this.teamSlug = config.teamSlug;
35
+ this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
36
+ this.conversations = new ConversationsApi(this);
37
+ this.messages = new MessagesApi(this);
38
+ }
39
+ /**
40
+ * Identify a customer using HMAC signature verification.
41
+ * Must be called before accessing conversations or messages.
42
+ *
43
+ * @param customer - Customer identity fields
44
+ * @param signature - HMAC-SHA256 hex signature of the signed fields
45
+ * @param testMode - Whether to use test mode (relaxed timestamp validation)
46
+ */
47
+ async identify(customer, signature, testMode = false) {
48
+ const response = await this.fetch(
49
+ "/api/widget/verify-identity",
50
+ {
51
+ method: "POST",
52
+ body: {
53
+ teamSlug: this.teamSlug,
54
+ testMode,
55
+ customer,
56
+ signature
57
+ }
58
+ }
59
+ );
60
+ this.sessionToken = response.data.sessionToken;
61
+ return response.data;
62
+ }
63
+ /**
64
+ * Subscribe to new messages on a conversation.
65
+ * Falls back to polling when Supabase Realtime is not available.
66
+ *
67
+ * @returns Unsubscribe function
68
+ */
69
+ onMessage(ticketId, callback) {
70
+ let lastMessageId = null;
71
+ const poll = async () => {
72
+ try {
73
+ const messages = await this.messages.list(ticketId);
74
+ if (messages.length > 0) {
75
+ const latest = messages[messages.length - 1];
76
+ if (lastMessageId && latest.id !== lastMessageId) {
77
+ const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
78
+ const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
79
+ for (const msg of newMessages) {
80
+ callback(msg);
81
+ }
82
+ }
83
+ lastMessageId = latest.id;
84
+ }
85
+ } catch {
86
+ }
87
+ };
88
+ poll();
89
+ const timer = setInterval(poll, this.pollingInterval);
90
+ this.pollingTimers.set(ticketId, timer);
91
+ return () => {
92
+ clearInterval(timer);
93
+ this.pollingTimers.delete(ticketId);
94
+ };
95
+ }
96
+ /**
97
+ * Subscribe to typing indicator events.
98
+ * Requires Supabase Realtime — returns a no-op if unavailable.
99
+ *
100
+ * @returns Unsubscribe function
101
+ */
102
+ onTyping(_ticketId, _callback) {
103
+ return () => {
104
+ };
105
+ }
106
+ /** Disconnect all subscriptions and clean up. */
107
+ disconnect() {
108
+ for (const timer of this.pollingTimers.values()) {
109
+ clearInterval(timer);
110
+ }
111
+ this.pollingTimers.clear();
112
+ this.sessionToken = null;
113
+ }
114
+ /** @internal Make an authenticated request to the widget API. */
115
+ async fetch(path, options) {
116
+ const url = this.buildUrl(path, options.query);
117
+ const headers = {
118
+ "Content-Type": "application/json"
119
+ };
120
+ if (this.sessionToken) {
121
+ headers["Authorization"] = `Bearer ${this.sessionToken}`;
122
+ }
123
+ const response = await globalThis.fetch(url, {
124
+ method: options.method,
125
+ headers,
126
+ body: options.body ? JSON.stringify(options.body) : void 0
127
+ });
128
+ if (!response.ok) {
129
+ const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
130
+ throw new Error(errorBody.error?.message ?? `Widget API error: ${response.status}`);
131
+ }
132
+ return response.json();
133
+ }
134
+ /** @internal Get the stored team ID for requests that need it. */
135
+ getTeamId() {
136
+ return this.teamId;
137
+ }
138
+ /** @internal Set team ID from identify response or other source. */
139
+ setTeamId(teamId) {
140
+ this.teamId = teamId;
141
+ }
142
+ buildUrl(path, query) {
143
+ const base = `${this.baseUrl}${path}`;
144
+ if (!query) return base;
145
+ const params = new URLSearchParams();
146
+ for (const [key, value] of Object.entries(query)) {
147
+ if (value !== void 0) params.set(key, value);
148
+ }
149
+ const qs = params.toString();
150
+ return qs ? `${base}?${qs}` : base;
151
+ }
152
+ };
153
+ var ConversationsApi = class {
154
+ constructor(chat) {
155
+ this.chat = chat;
156
+ }
157
+ /** List conversations for the identified customer. */
158
+ async list(params) {
159
+ const teamId = this.chat.getTeamId();
160
+ const response = await this.chat.fetch(
161
+ "/api/widget/conversations",
162
+ {
163
+ method: "GET",
164
+ query: {
165
+ teamId: teamId ?? void 0,
166
+ limit: params?.limit?.toString(),
167
+ cursor: params?.cursor
168
+ }
169
+ }
170
+ );
171
+ return response.data;
172
+ }
173
+ /** Create a new conversation (ticket). */
174
+ async create(params) {
175
+ const teamId = this.chat.getTeamId();
176
+ const response = await this.chat.fetch(
177
+ "/api/widget/tickets",
178
+ {
179
+ method: "POST",
180
+ body: {
181
+ teamId,
182
+ subject: params.subject,
183
+ message: params.message
184
+ }
185
+ }
186
+ );
187
+ return {
188
+ id: response.data.id,
189
+ ticketNumber: "",
190
+ subject: response.data.subject,
191
+ status: response.data.status,
192
+ messageCount: params.message ? 1 : 0,
193
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
194
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
195
+ closedAt: null
196
+ };
197
+ }
198
+ };
199
+ var MessagesApi = class {
200
+ constructor(chat) {
201
+ this.chat = chat;
202
+ }
203
+ /** List messages for a conversation. */
204
+ async list(ticketId) {
205
+ const teamId = this.chat.getTeamId();
206
+ const response = await this.chat.fetch(
207
+ "/api/widget/messages",
208
+ {
209
+ method: "GET",
210
+ query: {
211
+ teamId: teamId ?? void 0,
212
+ ticketId
213
+ }
214
+ }
215
+ );
216
+ return response.data.messages;
217
+ }
218
+ /** Send a message to a conversation. */
219
+ async send(ticketId, content) {
220
+ const teamId = this.chat.getTeamId();
221
+ const response = await this.chat.fetch(
222
+ "/api/widget/messages",
223
+ {
224
+ method: "POST",
225
+ body: {
226
+ teamId,
227
+ ticketId,
228
+ content
229
+ }
230
+ }
231
+ );
232
+ return response.data.message;
233
+ }
234
+ };
235
+ // Annotate the CommonJS export names for ESM import in node:
236
+ 0 && (module.exports = {
237
+ ChatClient
238
+ });
@@ -0,0 +1,172 @@
1
+ /** Configuration for the ChatClient. */
2
+ interface ChatConfig {
3
+ /** Team slug (e.g., 'acme'). Used to identify the team. */
4
+ teamSlug: string;
5
+ /** Override the base URL (defaults to https://app.cstar.help). */
6
+ baseUrl?: string;
7
+ /** Supabase project URL for realtime subscriptions (optional). */
8
+ supabaseUrl?: string;
9
+ /** Supabase anon key for realtime subscriptions (optional). */
10
+ supabaseAnonKey?: string;
11
+ /** Polling interval in ms when Supabase Realtime is not available (defaults to 3000). */
12
+ pollingInterval?: number;
13
+ }
14
+ /** Parameters for identifying a customer via HMAC signature verification. */
15
+ interface IdentifyParams {
16
+ /** Customer ID from your system. */
17
+ externalId: string;
18
+ /** Customer email address. */
19
+ email: string;
20
+ /** Customer display name. */
21
+ name?: string;
22
+ /** Avatar URL (not included in signature). */
23
+ avatarUrl?: string;
24
+ /** Custom metadata (not included in signature). */
25
+ metadata?: Record<string, {
26
+ value: string;
27
+ agentVisible: boolean;
28
+ }>;
29
+ /** Unix timestamp (seconds). Used for replay protection. */
30
+ timestamp: number;
31
+ }
32
+ /** Identity verification response. */
33
+ interface IdentifyResult {
34
+ verified: true;
35
+ customer: {
36
+ id: string;
37
+ externalId: string;
38
+ email: string;
39
+ name: string;
40
+ };
41
+ sessionToken: string;
42
+ expiresAt: string;
43
+ }
44
+ /** A customer conversation (maps to a ticket). */
45
+ interface Conversation {
46
+ id: string;
47
+ ticketNumber: string;
48
+ subject: string;
49
+ status: string;
50
+ messageCount: number;
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ closedAt: string | null;
54
+ }
55
+ /** Paginated conversation list response. */
56
+ interface ConversationListResponse {
57
+ conversations: Conversation[];
58
+ cursor: string | null;
59
+ hasMore: boolean;
60
+ }
61
+ /** A message in a conversation. */
62
+ interface WidgetMessage {
63
+ id: string;
64
+ content: string;
65
+ sender: 'customer' | 'agent';
66
+ sender_name: string;
67
+ created_at: string;
68
+ read_at: string | null;
69
+ attachments: unknown[];
70
+ }
71
+ /** Parameters for listing conversations. */
72
+ interface ConversationListParams {
73
+ limit?: number;
74
+ cursor?: string;
75
+ }
76
+ /** Parameters for creating a new conversation. */
77
+ interface ConversationCreateParams {
78
+ subject: string;
79
+ message?: string;
80
+ }
81
+ /** Typing indicator payload from realtime subscription. */
82
+ interface TypingPayload {
83
+ agent_id: string;
84
+ agent_name: string;
85
+ is_typing: boolean;
86
+ }
87
+ /** Callback for message events. */
88
+ type MessageCallback = (message: WidgetMessage) => void;
89
+ /** Callback for typing indicator events. */
90
+ type TypingCallback = (payload: TypingPayload) => void;
91
+
92
+ /**
93
+ * Customer-facing chat client for the cStar widget API.
94
+ *
95
+ * ```typescript
96
+ * const chat = new ChatClient({ teamSlug: 'acme' });
97
+ * await chat.identify(
98
+ * { externalId: 'usr_123', email: 'jane@co.com', name: 'Jane', timestamp: Date.now() / 1000 },
99
+ * 'hmac_signature_hex'
100
+ * );
101
+ * const { conversations } = await chat.conversations.list();
102
+ * ```
103
+ */
104
+ declare class ChatClient {
105
+ private readonly baseUrl;
106
+ private readonly teamSlug;
107
+ private readonly pollingInterval;
108
+ private sessionToken;
109
+ private teamId;
110
+ private pollingTimers;
111
+ /** Sub-resource for managing conversations. */
112
+ readonly conversations: ConversationsApi;
113
+ /** Sub-resource for managing messages. */
114
+ readonly messages: MessagesApi;
115
+ constructor(config: ChatConfig);
116
+ /**
117
+ * Identify a customer using HMAC signature verification.
118
+ * Must be called before accessing conversations or messages.
119
+ *
120
+ * @param customer - Customer identity fields
121
+ * @param signature - HMAC-SHA256 hex signature of the signed fields
122
+ * @param testMode - Whether to use test mode (relaxed timestamp validation)
123
+ */
124
+ identify(customer: IdentifyParams, signature: string, testMode?: boolean): Promise<IdentifyResult>;
125
+ /**
126
+ * Subscribe to new messages on a conversation.
127
+ * Falls back to polling when Supabase Realtime is not available.
128
+ *
129
+ * @returns Unsubscribe function
130
+ */
131
+ onMessage(ticketId: string, callback: MessageCallback): () => void;
132
+ /**
133
+ * Subscribe to typing indicator events.
134
+ * Requires Supabase Realtime — returns a no-op if unavailable.
135
+ *
136
+ * @returns Unsubscribe function
137
+ */
138
+ onTyping(_ticketId: string, _callback: TypingCallback): () => void;
139
+ /** Disconnect all subscriptions and clean up. */
140
+ disconnect(): void;
141
+ /** @internal Make an authenticated request to the widget API. */
142
+ fetch<T>(path: string, options: {
143
+ method: string;
144
+ body?: unknown;
145
+ query?: Record<string, string | undefined>;
146
+ }): Promise<T>;
147
+ /** @internal Get the stored team ID for requests that need it. */
148
+ getTeamId(): string | null;
149
+ /** @internal Set team ID from identify response or other source. */
150
+ setTeamId(teamId: string): void;
151
+ private buildUrl;
152
+ }
153
+ /** Conversations sub-resource on ChatClient. */
154
+ declare class ConversationsApi {
155
+ private readonly chat;
156
+ constructor(chat: ChatClient);
157
+ /** List conversations for the identified customer. */
158
+ list(params?: ConversationListParams): Promise<ConversationListResponse>;
159
+ /** Create a new conversation (ticket). */
160
+ create(params: ConversationCreateParams): Promise<Conversation>;
161
+ }
162
+ /** Messages sub-resource on ChatClient. */
163
+ declare class MessagesApi {
164
+ private readonly chat;
165
+ constructor(chat: ChatClient);
166
+ /** List messages for a conversation. */
167
+ list(ticketId: string): Promise<WidgetMessage[]>;
168
+ /** Send a message to a conversation. */
169
+ send(ticketId: string, content: string): Promise<WidgetMessage>;
170
+ }
171
+
172
+ export { ChatClient, type ChatConfig, type Conversation, type ConversationCreateParams, type ConversationListParams, type ConversationListResponse, type IdentifyParams, type IdentifyResult, type MessageCallback, type TypingCallback, type WidgetMessage };