@avibra/pulse-core 1.0.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.
Files changed (53) hide show
  1. package/dist/api/APIClient.d.ts +50 -0
  2. package/dist/api/APIClient.d.ts.map +1 -0
  3. package/dist/api/APIClient.js +324 -0
  4. package/dist/api/APIClient.js.map +1 -0
  5. package/dist/audio/CallRecorder.d.ts +64 -0
  6. package/dist/audio/CallRecorder.d.ts.map +1 -0
  7. package/dist/audio/CallRecorder.js +370 -0
  8. package/dist/audio/CallRecorder.js.map +1 -0
  9. package/dist/core/EventTracker.d.ts +21 -0
  10. package/dist/core/EventTracker.d.ts.map +1 -0
  11. package/dist/core/EventTracker.js +133 -0
  12. package/dist/core/EventTracker.js.map +1 -0
  13. package/dist/core/SessionManager.d.ts +18 -0
  14. package/dist/core/SessionManager.d.ts.map +1 -0
  15. package/dist/core/SessionManager.js +70 -0
  16. package/dist/core/SessionManager.js.map +1 -0
  17. package/dist/core/TriggerManager.d.ts +15 -0
  18. package/dist/core/TriggerManager.d.ts.map +1 -0
  19. package/dist/core/TriggerManager.js +61 -0
  20. package/dist/core/TriggerManager.js.map +1 -0
  21. package/dist/core/UserIdentityManager.d.ts +20 -0
  22. package/dist/core/UserIdentityManager.d.ts.map +1 -0
  23. package/dist/core/UserIdentityManager.js +71 -0
  24. package/dist/core/UserIdentityManager.js.map +1 -0
  25. package/dist/index.d.ts +15 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +19 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/platform.d.ts +64 -0
  30. package/dist/platform.d.ts.map +1 -0
  31. package/dist/platform.js +8 -0
  32. package/dist/platform.js.map +1 -0
  33. package/dist/types.d.ts +175 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +6 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/utils/crypto.d.ts +3 -0
  38. package/dist/utils/crypto.d.ts.map +1 -0
  39. package/dist/utils/crypto.js +34 -0
  40. package/dist/utils/crypto.js.map +1 -0
  41. package/dist/utils/logger.d.ts +8 -0
  42. package/dist/utils/logger.d.ts.map +1 -0
  43. package/dist/utils/logger.js +29 -0
  44. package/dist/utils/logger.js.map +1 -0
  45. package/dist/utils/queue.d.ts +21 -0
  46. package/dist/utils/queue.d.ts.map +1 -0
  47. package/dist/utils/queue.js +81 -0
  48. package/dist/utils/queue.js.map +1 -0
  49. package/dist/utils/retry.d.ts +12 -0
  50. package/dist/utils/retry.d.ts.map +1 -0
  51. package/dist/utils/retry.js +66 -0
  52. package/dist/utils/retry.js.map +1 -0
  53. package/package.json +25 -0
@@ -0,0 +1,50 @@
1
+ import type { NetworkAdapter, StorageAdapter } from '../platform';
2
+ import { APIResponse, AudioChunkRequest, AudioChunkResponse, TrackEventPayload, IdentifyRequest, QueuedRequest, RetryConfig, SurveyTrigger, TriggerResponse, VoiceSessionStartRequest, VoiceSessionStartResponse, VoiceTokenResponse, PulseEnvironment } from '../types';
3
+ export interface APIClientConfig {
4
+ partnerId: string;
5
+ apiKey?: string;
6
+ mode?: PulseEnvironment;
7
+ baseUrl?: string;
8
+ retry?: Partial<RetryConfig>;
9
+ maxOfflineQueueSize?: number;
10
+ }
11
+ export declare class APIClient {
12
+ private readonly partnerId;
13
+ private readonly apiKey;
14
+ private readonly baseUrl;
15
+ private readonly retryConfig;
16
+ private readonly offlineQueue;
17
+ private readonly network;
18
+ private draining;
19
+ constructor(config: APIClientConfig, network: NetworkAdapter, storage: StorageAdapter);
20
+ postEvent(event: TrackEventPayload): Promise<APIResponse<TriggerResponse>>;
21
+ identify(payload: IdentifyRequest): Promise<APIResponse<void>>;
22
+ getTriggers(userId: string): Promise<APIResponse<SurveyTrigger[]>>;
23
+ getSignalDetails(signalId: string): Promise<APIResponse<Record<string, unknown>>>;
24
+ getVoiceToken(signalId: string, userId: string): Promise<APIResponse<VoiceTokenResponse>>;
25
+ startVoiceSession(payload: VoiceSessionStartRequest): Promise<APIResponse<VoiceSessionStartResponse>>;
26
+ completeVoiceSurvey(params: {
27
+ sessionId: string;
28
+ signalId: string;
29
+ userId: string;
30
+ sessionDate: string;
31
+ transcript: string;
32
+ audioBlob: Blob | ArrayBuffer;
33
+ lotteryCount?: number;
34
+ }): Promise<void>;
35
+ private fetchS3UploadUrl;
36
+ sendAudioChunk(request: AudioChunkRequest): Promise<APIResponse<AudioChunkResponse>>;
37
+ queueOffline(item: QueuedRequest): void;
38
+ getOfflineQueueSize(): number;
39
+ getBaseUrl(): string;
40
+ getPartnerId(): string;
41
+ destroy(): void;
42
+ private post;
43
+ private get;
44
+ private executePost;
45
+ private postFormData;
46
+ private parseResponse;
47
+ private buildHeaders;
48
+ private drainOfflineQueue;
49
+ }
50
+ //# sourceMappingURL=APIClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"APIClient.d.ts","sourceRoot":"","sources":["../../src/api/APIClient.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EACH,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EACnB,MAAM,UAAU,CAAC;AAalB,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAeD,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAS;gBAGrB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,cAAc;IAad,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;IAI1E,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAI9D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;IAIlE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAIjF,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAoEzF,iBAAiB,CAC1B,OAAO,EAAE,wBAAwB,GAClC,OAAO,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;IAIrC,mBAAmB,CAAC,MAAM,EAAE;QACrC,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,IAAI,GAAG,WAAW,CAAC;QAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,IAAI,CAAC;YA2CH,gBAAgB;IAyBjB,cAAc,CACvB,OAAO,EAAE,iBAAiB,GAC3B,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAUpC,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IACvC,mBAAmB,IAAI,MAAM;IAC7B,UAAU,IAAI,MAAM;IACpB,YAAY,IAAI,MAAM;IAEtB,OAAO,IAAI,IAAI;YAMR,IAAI;YA2BJ,GAAG;YAiBH,WAAW;YAgBX,YAAY;YAqBZ,aAAa;IAmC3B,OAAO,CAAC,YAAY;YAeN,iBAAiB;CA+BlC"}
@@ -0,0 +1,324 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Pulse Core — API Client
3
+ // Uses NetworkAdapter + StorageAdapter instead of navigator.onLine / localStorage.
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ import { generateNonce } from '../utils/crypto';
6
+ import { logger } from '../utils/logger';
7
+ import { OfflineQueue } from '../utils/queue';
8
+ import { NonRetryableError, RetryableError, withRetry } from '../utils/retry';
9
+ const BASE_URLS = {
10
+ staging: 'https://pulse.avibra.com/staging/api/v1',
11
+ st2: 'https://pulse.avibra.com/st2/api/v1',
12
+ prod: 'https://pulse.avibra.com/api/v1',
13
+ };
14
+ const DEFAULT_FETCH_TIMEOUT_MS = 15000;
15
+ function fetchWithTimeout(url, init, timeoutMs) {
16
+ return new Promise((resolve, reject) => {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => {
19
+ controller.abort();
20
+ reject(new Error(`Request timed out after ${timeoutMs}ms`));
21
+ }, timeoutMs);
22
+ fetch(url, { ...init, signal: controller.signal })
23
+ .then(resolve, reject)
24
+ .finally(() => clearTimeout(timer));
25
+ });
26
+ }
27
+ export class APIClient {
28
+ constructor(config, network, storage) {
29
+ this.draining = false;
30
+ this.partnerId = config.partnerId;
31
+ this.apiKey = config.apiKey;
32
+ this.baseUrl = config.baseUrl ?? BASE_URLS[config.mode ?? 'prod'];
33
+ this.retryConfig = config.retry ?? {};
34
+ this.network = network;
35
+ this.offlineQueue = new OfflineQueue(config.maxOfflineQueueSize ?? 100, storage, network);
36
+ this.offlineQueue.onDrain(() => this.drainOfflineQueue());
37
+ }
38
+ // ─── Public API Methods ─────────────────────────────────────────────────────
39
+ async postEvent(event) {
40
+ return this.post('/users/track', event);
41
+ }
42
+ async identify(payload) {
43
+ return this.post('/users/identify', payload);
44
+ }
45
+ async getTriggers(userId) {
46
+ return this.get(`/users/${encodeURIComponent(userId)}/signals?partner_id=${encodeURIComponent(this.partnerId)}`);
47
+ }
48
+ async getSignalDetails(signalId) {
49
+ return this.get(`/signal/${encodeURIComponent(signalId)}`);
50
+ }
51
+ async getVoiceToken(signalId, userId) {
52
+ const url = `${this.baseUrl}/voice-survey/config?user_id=${encodeURIComponent(userId)}&partner_id=${encodeURIComponent(this.partnerId)}&signal_id=${encodeURIComponent(signalId)}`;
53
+ logger.debug(`[APIClient] GET ${url}`);
54
+ try {
55
+ const nonce = generateNonce();
56
+ const timestamp = new Date().toISOString();
57
+ const headers = this.buildHeaders(timestamp, nonce);
58
+ const response = await fetchWithTimeout(url, { method: 'GET', headers }, DEFAULT_FETCH_TIMEOUT_MS);
59
+ if (!response.ok) {
60
+ const text = await response.text();
61
+ return { success: false, error: { code: `HTTP_${response.status}`, message: text } };
62
+ }
63
+ const raw = (await response.json());
64
+ logger.debug('[APIClient] Raw incentive from API:', JSON.stringify(raw['incentive'] ?? null));
65
+ // API returns snake_case; normalize to camelCase expected by VoiceTokenResponse
66
+ const rawIncentive = raw['incentive'];
67
+ const topicsList = (raw['topics'] ?? []);
68
+ let incentive = null;
69
+ if (rawIncentive && rawIncentive['enabled'] === true) {
70
+ const props = rawIncentive['properties'];
71
+ const incentiveType = props?.type?.toLowerCase() ?? '';
72
+ const topicCount = topicsList.length;
73
+ // Default checkin_text based on incentive type
74
+ let defaultCheckinText = 'Complete the survey to earn your reward!';
75
+ if (incentiveType === 'lottery' || incentiveType === 'raffle') {
76
+ defaultCheckinText = `Earn up to ${topicCount} ticket${topicCount === 1 ? '' : 's'}`;
77
+ }
78
+ else if (incentiveType === 'gift_card' || incentiveType === 'gift card') {
79
+ defaultCheckinText = `Complete to earn a $${props?.value ?? ''} gift card`;
80
+ }
81
+ // Default success_text based on incentive type
82
+ let defaultSuccessText = 'Thank you for completing the survey!';
83
+ if (incentiveType === 'lottery' || incentiveType === 'raffle') {
84
+ defaultSuccessText = 'Your tickets have been added. Good luck!';
85
+ }
86
+ else if (incentiveType === 'gift_card' || incentiveType === 'gift card') {
87
+ defaultSuccessText = `Your $${props?.value ?? ''} gift card will be sent to your email.`;
88
+ }
89
+ incentive = {
90
+ enabled: true,
91
+ properties: props ?? { type: '', value: 0 },
92
+ delivery: (rawIncentive['delivery'] ?? ''),
93
+ checkin_text: rawIncentive['checkin_text'] || defaultCheckinText,
94
+ success_text: rawIncentive['success_text'] || defaultSuccessText,
95
+ };
96
+ }
97
+ const data = {
98
+ token: (raw['token'] ?? ''),
99
+ model: (raw['model_name'] ?? raw['model'] ?? ''),
100
+ voiceName: (raw['voice_name'] ?? raw['voiceName'] ?? ''),
101
+ openingQuestion: (raw['opening_salutation'] ?? raw['opening_question'] ?? raw['openingQuestion'] ?? ''),
102
+ maxTurns: (raw['max_turns'] ?? raw['maxTurns'] ?? 10),
103
+ systemInstruction: (raw['system_instruction'] ?? raw['systemInstruction'] ?? ''),
104
+ wsUrl: (raw['ws_url'] ?? raw['wsUrl'] ?? ''),
105
+ signalId: (raw['signal_id'] ?? raw['signalId'] ?? signalId),
106
+ topics: topicsList,
107
+ incentive,
108
+ };
109
+ logger.debug('[APIClient] Resolved incentive:', JSON.stringify(data.incentive));
110
+ if (!data.wsUrl) {
111
+ const wsScheme = this.baseUrl.startsWith('https') ? 'wss' : 'ws';
112
+ const host = this.baseUrl.replace(/^https?:\/\//, '');
113
+ data.wsUrl = `${wsScheme}://${host}/voice/ws`;
114
+ }
115
+ return { success: true, data };
116
+ }
117
+ catch (err) {
118
+ return { success: false, error: { code: 'NETWORK_ERROR', message: String(err) } };
119
+ }
120
+ }
121
+ async startVoiceSession(payload) {
122
+ return this.post('/voice/session/start', payload);
123
+ }
124
+ async completeVoiceSurvey(params) {
125
+ try {
126
+ // Step 1: Get presigned S3 upload URL (need the key for complete API)
127
+ const s3UrlResp = await this.fetchS3UploadUrl(params.userId, 'wav');
128
+ const audioS3Key = s3UrlResp?.key;
129
+ // Step 2: Build the complete API body (include key even before upload finishes)
130
+ const body = {
131
+ session_id: params.sessionId,
132
+ partner_id: this.partnerId,
133
+ signal_id: params.signalId,
134
+ user_id: params.userId,
135
+ session_date: params.sessionDate,
136
+ transcript: params.transcript,
137
+ ...(audioS3Key ? { audio_s3_key: audioS3Key } : {}),
138
+ };
139
+ if (params.lotteryCount != null && params.lotteryCount > 0) {
140
+ body['lottery_count'] = params.lotteryCount;
141
+ }
142
+ // Step 3: Run S3 upload + complete API in parallel
143
+ const uploadPromise = s3UrlResp
144
+ ? fetchWithTimeout(s3UrlResp.url, {
145
+ method: 'PUT',
146
+ body: params.audioBlob,
147
+ headers: { 'Content-Type': s3UrlResp.content_type },
148
+ }, 60000).then(r => {
149
+ logger.warn(`[APIClient] S3 upload HTTP ${r.status}`);
150
+ }).catch(err => {
151
+ logger.warn('[APIClient] S3 audio upload failed:', err);
152
+ })
153
+ : Promise.resolve();
154
+ const completePromise = this.post('/voice-survey/complete', body)
155
+ .then(() => { logger.warn('[APIClient] Voice survey completed successfully'); })
156
+ .catch(err => { logger.warn('[APIClient] complete API failed:', err); });
157
+ await Promise.all([uploadPromise, completePromise]);
158
+ }
159
+ catch (err) {
160
+ logger.warn('[APIClient] completeVoiceSurvey failed (ignored):', err);
161
+ }
162
+ }
163
+ async fetchS3UploadUrl(userId, extension) {
164
+ const url = `${this.baseUrl}/S3/upload-url?partner_id=${encodeURIComponent(this.partnerId)}&user_id=${encodeURIComponent(userId)}&extension=${encodeURIComponent(extension)}`;
165
+ const nonce = generateNonce();
166
+ const timestamp = new Date().toISOString();
167
+ const headers = this.buildHeaders(timestamp, nonce);
168
+ try {
169
+ logger.warn(`[APIClient] fetchS3UploadUrl — GET ${this.baseUrl}/S3/upload-url userId=${userId} ext=${extension}`);
170
+ const response = await fetchWithTimeout(url, { method: 'GET', headers }, DEFAULT_FETCH_TIMEOUT_MS);
171
+ if (!response.ok) {
172
+ const text = await response.text();
173
+ logger.warn(`[APIClient] S3 upload URL fetch failed HTTP ${response.status}: ${text}`);
174
+ return null;
175
+ }
176
+ const result = await response.json();
177
+ logger.warn(`[APIClient] fetchS3UploadUrl — got key=${result.key} content_type=${result.content_type} expires_in=${result.expires_in}s`);
178
+ return result;
179
+ }
180
+ catch (err) {
181
+ logger.warn('[APIClient] fetchS3UploadUrl error:', err);
182
+ return null;
183
+ }
184
+ }
185
+ async sendAudioChunk(request) {
186
+ const formData = new FormData();
187
+ formData.append('voice_session_token', request.voice_session_token);
188
+ formData.append('chunk_index', String(request.chunk_index));
189
+ formData.append('is_final', String(request.is_final));
190
+ formData.append('is_turn_end', String(request.is_turn_end ?? false));
191
+ formData.append('audio', request.audioBlob, `chunk_${request.chunk_index}.webm`);
192
+ return this.postFormData('/voice/session/audio', formData);
193
+ }
194
+ queueOffline(item) { this.offlineQueue.enqueue(item); }
195
+ getOfflineQueueSize() { return this.offlineQueue.size; }
196
+ getBaseUrl() { return this.baseUrl; }
197
+ getPartnerId() { return this.partnerId; }
198
+ destroy() {
199
+ this.offlineQueue.destroy();
200
+ }
201
+ // ─── HTTP Internals ─────────────────────────────────────────────────────────
202
+ async post(path, body) {
203
+ const bodyStr = JSON.stringify(body);
204
+ const isOnline = await this.network.isOnline();
205
+ if (!isOnline) {
206
+ const nonce = generateNonce();
207
+ const timestamp = new Date().toISOString();
208
+ this.offlineQueue.enqueue({
209
+ id: nonce,
210
+ endpoint: path,
211
+ method: 'POST',
212
+ body: bodyStr,
213
+ headers: this.buildHeaders(timestamp, nonce),
214
+ timestamp: Date.now(),
215
+ attempts: 0,
216
+ });
217
+ logger.info(`[APIClient] Offline — queued POST ${path}`);
218
+ return { success: true };
219
+ }
220
+ return withRetry(() => this.executePost(path, bodyStr), this.retryConfig, `POST ${path}`);
221
+ }
222
+ async get(path) {
223
+ const nonce = generateNonce();
224
+ const timestamp = new Date().toISOString();
225
+ const headers = this.buildHeaders(timestamp, nonce);
226
+ return withRetry(async () => {
227
+ const url = `${this.baseUrl}${path}`;
228
+ logger.debug(`[APIClient] GET ${url}`);
229
+ const response = await fetchWithTimeout(url, { method: 'GET', headers }, DEFAULT_FETCH_TIMEOUT_MS);
230
+ return this.parseResponse(response, 'GET', path);
231
+ }, this.retryConfig, `GET ${path}`);
232
+ }
233
+ async executePost(path, bodyStr) {
234
+ const nonce = generateNonce();
235
+ const timestamp = new Date().toISOString();
236
+ const headers = this.buildHeaders(timestamp, nonce);
237
+ const url = `${this.baseUrl}${path}`;
238
+ logger.debug(`[APIClient] POST ${url}`);
239
+ const response = await fetchWithTimeout(url, {
240
+ method: 'POST',
241
+ headers,
242
+ body: bodyStr,
243
+ }, DEFAULT_FETCH_TIMEOUT_MS);
244
+ return this.parseResponse(response, 'POST', path);
245
+ }
246
+ async postFormData(path, formData) {
247
+ const nonce = generateNonce();
248
+ const timestamp = new Date().toISOString();
249
+ const url = `${this.baseUrl}${path}`;
250
+ const headers = this.buildHeaders(timestamp, nonce);
251
+ delete headers['Content-Type'];
252
+ return withRetry(async () => {
253
+ const response = await fetchWithTimeout(url, {
254
+ method: 'POST',
255
+ headers,
256
+ body: formData,
257
+ }, DEFAULT_FETCH_TIMEOUT_MS);
258
+ return this.parseResponse(response, 'POST', path);
259
+ }, this.retryConfig, `POST ${path}`);
260
+ }
261
+ async parseResponse(response, method, path) {
262
+ const status = response.status;
263
+ if (response.ok) {
264
+ const raw = await response.json();
265
+ logger.info(`[APIClient] ${method} ${path} → ${status} raw response:`, JSON.stringify(raw));
266
+ // Normalize: if the API already returns { success, data }, use it directly.
267
+ // Otherwise wrap the raw payload so callers always see APIResponse<T>.
268
+ if (raw !== null &&
269
+ typeof raw === 'object' &&
270
+ 'success' in raw) {
271
+ return raw;
272
+ }
273
+ return { success: true, data: raw };
274
+ }
275
+ if (status >= 400 && status < 500) {
276
+ const errorText = await response.text();
277
+ logger.warn(`[APIClient] ${method} ${path} → ${status}: ${errorText}`);
278
+ throw new NonRetryableError(`HTTP ${status}: ${errorText}`, status);
279
+ }
280
+ const errorText = await response.text();
281
+ logger.warn(`[APIClient] ${method} ${path} → ${status}: ${errorText}`);
282
+ throw new RetryableError(`HTTP ${status}: ${errorText}`, status);
283
+ }
284
+ buildHeaders(timestamp, nonce) {
285
+ const headers = {
286
+ 'Content-Type': 'application/json',
287
+ 'X-Pulse-Partner-Id': this.partnerId,
288
+ 'X-Pulse-Nonce': nonce,
289
+ 'X-Pulse-Timestamp': timestamp,
290
+ };
291
+ if (this.apiKey) {
292
+ headers['X-Api-Key'] = this.apiKey;
293
+ }
294
+ return headers;
295
+ }
296
+ // ─── Offline Queue Drain ────────────────────────────────────────────────────
297
+ async drainOfflineQueue() {
298
+ if (this.draining || this.offlineQueue.isEmpty)
299
+ return;
300
+ this.draining = true;
301
+ const items = this.offlineQueue.flush();
302
+ logger.info(`[APIClient] Draining ${items.length} offline event(s)`);
303
+ for (const item of items) {
304
+ try {
305
+ await withRetry(async () => {
306
+ const response = await fetchWithTimeout(`${this.baseUrl}${item.endpoint}`, {
307
+ method: item.method,
308
+ headers: item.headers,
309
+ ...(item.body ? { body: item.body } : {}),
310
+ }, DEFAULT_FETCH_TIMEOUT_MS);
311
+ if (!response.ok) {
312
+ throw new RetryableError(`HTTP ${response.status}`, response.status);
313
+ }
314
+ }, this.retryConfig, `drain ${item.endpoint}`);
315
+ }
316
+ catch (err) {
317
+ logger.error(`[APIClient] Failed to drain event ${item.id}:`, err);
318
+ this.offlineQueue.enqueue({ ...item, attempts: item.attempts + 1 });
319
+ }
320
+ }
321
+ this.draining = false;
322
+ }
323
+ }
324
+ //# sourceMappingURL=APIClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"APIClient.js","sourceRoot":"","sources":["../../src/api/APIClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export declare class CallRecorder {
2
+ private startTime;
3
+ private userChunks;
4
+ private aiChunks;
5
+ private readonly userInputRate;
6
+ private recording;
7
+ private aiChunkCount;
8
+ private userChunkCount;
9
+ private aiCursorMs;
10
+ private aiLastArrivalMs;
11
+ private userCursorMs;
12
+ private userLastArrivalMs;
13
+ /**
14
+ * @param userInputRate - Sample rate of the user's mic audio (default 16000).
15
+ * Web worklet outputs at 16 kHz, RN PulseAudio captures at 16 kHz.
16
+ */
17
+ constructor(userInputRate?: number);
18
+ /** Call when the voice session begins. Resets all state. */
19
+ start(): void;
20
+ /** Whether recording is active. */
21
+ get isRecording(): boolean;
22
+ /**
23
+ * Add a user mic audio chunk (Float32 normalized to [-1, 1]).
24
+ * Timestamp is auto-computed from Date.now() - startTime if not provided.
25
+ */
26
+ addUserChunkF32(samples: Float32Array, timestampMs?: number): void;
27
+ /**
28
+ * Add a user mic audio chunk from base64 PCM (Int16, little-endian).
29
+ * Used by React Native where mic data arrives as base64.
30
+ */
31
+ addUserChunkB64(base64: string, timestampMs?: number): void;
32
+ /**
33
+ * Add an AI audio chunk from Gemini (base64 PCM Int16 @ 24 kHz).
34
+ * Call this from the WebSocket onmessage handler for each inlineData.data chunk.
35
+ */
36
+ addAIChunkB64(base64: string, timestampMs?: number): void;
37
+ /**
38
+ * Add an AI audio chunk as Float32 (already decoded).
39
+ */
40
+ addAIChunkF32(samples: Float32Array, timestampMs?: number): void;
41
+ /** Gap threshold — if wall-clock gap between arrivals exceeds this,
42
+ * treat it as a new turn and re-anchor to wall-clock time. */
43
+ private static readonly GAP_MS;
44
+ private placeAIChunk;
45
+ private placeUserChunk;
46
+ /**
47
+ * Stop recording and build a stereo WAV (24 kHz, 16-bit PCM).
48
+ * Left channel = user mic, Right channel = AI.
49
+ *
50
+ * Returns ArrayBuffer (works on both Web and RN — Web callers can wrap
51
+ * in a Blob if needed).
52
+ *
53
+ * Returns null if no audio was captured.
54
+ */
55
+ buildMergedWAV(): ArrayBuffer | null;
56
+ /**
57
+ * Convenience: buildMergedWAV() wrapped in a Blob for Web callers.
58
+ * Returns null if no audio was captured.
59
+ */
60
+ buildMergedBlob(): Blob | null;
61
+ /** Free chunk memory (call after upload or if session is discarded). */
62
+ reset(): void;
63
+ }
64
+ //# sourceMappingURL=CallRecorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CallRecorder.d.ts","sourceRoot":"","sources":["../../src/audio/CallRecorder.ts"],"names":[],"mappings":"AAgCA,qBAAa,YAAY;IACrB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,cAAc,CAAK;IAO3B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,eAAe,CAAK;IAK5B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,iBAAiB,CAAK;IAE9B;;;OAGG;gBACS,aAAa,GAAE,MAAc;IAIzC,4DAA4D;IAC5D,KAAK,IAAI,IAAI;IAcb,mCAAmC;IACnC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAID;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAOlE;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ3D;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAQzD;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBhE;mEAC+D;IAC/D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAO;IAErC,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;OAQG;IACH,cAAc,IAAI,WAAW,GAAG,IAAI;IA6DpC;;;OAGG;IACH,eAAe,IAAI,IAAI,GAAG,IAAI;IAM9B,wEAAwE;IACxE,KAAK,IAAI,IAAI;CAUhB"}