@skyporch/daykeeper-react-native 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/dist/index.js ADDED
@@ -0,0 +1,353 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
8
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
9
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
10
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
11
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
12
+
13
+ // src/errors.ts
14
+ var DaykeeperReactNativeApiError = class extends Error {
15
+ constructor(options) {
16
+ super(options.code);
17
+ __publicField(this, "status");
18
+ __publicField(this, "code");
19
+ __publicField(this, "retryable");
20
+ this.name = "DaykeeperReactNativeApiError";
21
+ this.status = options.status;
22
+ this.code = options.code;
23
+ this.retryable = options.retryable;
24
+ }
25
+ toJSON() {
26
+ return {
27
+ name: this.name,
28
+ status: this.status,
29
+ code: this.code,
30
+ retryable: this.retryable
31
+ };
32
+ }
33
+ };
34
+ var DaykeeperReactNativeTransportError = class extends Error {
35
+ constructor(options) {
36
+ super(options.message);
37
+ __publicField(this, "code");
38
+ __publicField(this, "retryable");
39
+ this.name = "DaykeeperReactNativeTransportError";
40
+ this.code = options.code;
41
+ this.retryable = options.retryable ?? false;
42
+ }
43
+ toJSON() {
44
+ return {
45
+ name: this.name,
46
+ code: this.code,
47
+ message: this.message,
48
+ retryable: this.retryable
49
+ };
50
+ }
51
+ };
52
+
53
+ // src/client.ts
54
+ var DEFAULT_TIMEOUT_MS = 3e4;
55
+ var MAX_RESPONSE_BYTES = 1024 * 1024;
56
+ var MAX_TOKEN_LENGTH = 16384;
57
+ var MAX_MESSAGE_LENGTH = 16e3;
58
+ var _baseUrl, _fetch, _getAccessToken, _timeoutMs, _DaykeeperReactNativeClient_instances, request_fn;
59
+ var DaykeeperReactNativeClient = class {
60
+ constructor(options) {
61
+ __privateAdd(this, _DaykeeperReactNativeClient_instances);
62
+ __privateAdd(this, _baseUrl);
63
+ __privateAdd(this, _fetch);
64
+ __privateAdd(this, _getAccessToken);
65
+ __privateAdd(this, _timeoutMs);
66
+ __privateSet(this, _baseUrl, parseBaseUrl(options.baseUrl));
67
+ __privateSet(this, _fetch, options.fetch ?? globalThis.fetch);
68
+ if (typeof __privateGet(this, _fetch) !== "function") {
69
+ throw configurationError("A Fetch API implementation is required");
70
+ }
71
+ if (typeof options.getAccessToken !== "function") {
72
+ throw configurationError("getAccessToken must be a function");
73
+ }
74
+ __privateSet(this, _getAccessToken, options.getAccessToken);
75
+ __privateSet(this, _timeoutMs, validateTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS));
76
+ }
77
+ getIdentity(options) {
78
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, "/v1/identity", { signal: options?.signal });
79
+ }
80
+ listConversations(options) {
81
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, "/v1/conversations", { signal: options?.signal });
82
+ }
83
+ createConversation(options) {
84
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, "/v1/conversations", {
85
+ method: "POST",
86
+ signal: options?.signal
87
+ });
88
+ }
89
+ getUnread(options) {
90
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, "/v1/unread", { signal: options?.signal });
91
+ }
92
+ markConversationSeen(conversationId, options) {
93
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, `/v1/conversations/${positiveInteger(conversationId, "conversationId")}/seen`, { method: "POST", signal: options?.signal });
94
+ }
95
+ listMessages(conversationId, options = {}) {
96
+ const id = positiveInteger(conversationId, "conversationId");
97
+ const after = options.after === void 0 ? "" : `?after=${positiveInteger(options.after, "after")}`;
98
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, `/v1/conversations/${id}/messages${after}`, {
99
+ signal: options.signal
100
+ });
101
+ }
102
+ sendMessage(conversationId, content, options) {
103
+ const normalizedContent = validateMessage(content);
104
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, `/v1/conversations/${positiveInteger(conversationId, "conversationId")}/messages`, {
105
+ method: "POST",
106
+ body: { content: normalizedContent },
107
+ signal: options?.signal
108
+ });
109
+ }
110
+ claimAnonymousConversation(widgetToken, options) {
111
+ if (typeof widgetToken !== "string" || !widgetToken.trim() || widgetToken.length > MAX_TOKEN_LENGTH) {
112
+ throw configurationError("widgetToken is invalid");
113
+ }
114
+ return __privateMethod(this, _DaykeeperReactNativeClient_instances, request_fn).call(this, "/v1/anonymous-conversations/claim", {
115
+ method: "POST",
116
+ body: { widgetToken: widgetToken.trim() },
117
+ signal: options?.signal
118
+ });
119
+ }
120
+ };
121
+ _baseUrl = new WeakMap();
122
+ _fetch = new WeakMap();
123
+ _getAccessToken = new WeakMap();
124
+ _timeoutMs = new WeakMap();
125
+ _DaykeeperReactNativeClient_instances = new WeakSet();
126
+ request_fn = async function(path, options = {}) {
127
+ const timeoutController = new AbortController();
128
+ let timedOut = false;
129
+ const timeout = setTimeout(() => {
130
+ timedOut = true;
131
+ timeoutController.abort();
132
+ }, __privateGet(this, _timeoutMs));
133
+ const onCallerAbort = () => timeoutController.abort();
134
+ if (options.signal?.aborted) timeoutController.abort();
135
+ else
136
+ options.signal?.addEventListener("abort", onCallerAbort, { once: true });
137
+ try {
138
+ for (let attempt = 0; attempt < 2; attempt += 1) {
139
+ const token = validateToken(
140
+ await __privateGet(this, _getAccessToken).call(this, { forceRefresh: attempt === 1 })
141
+ );
142
+ const headers = new Headers({
143
+ accept: "application/json",
144
+ authorization: `Bearer ${token}`
145
+ });
146
+ if (options.body !== void 0) {
147
+ headers.set("content-type", "application/json");
148
+ }
149
+ let response;
150
+ try {
151
+ response = await __privateGet(this, _fetch).call(this, `${__privateGet(this, _baseUrl)}${path}`, {
152
+ method: options.method ?? "GET",
153
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
154
+ headers,
155
+ signal: timeoutController.signal
156
+ });
157
+ } catch {
158
+ if (timedOut) {
159
+ throw new DaykeeperReactNativeTransportError({
160
+ code: "REQUEST_TIMEOUT",
161
+ message: `The Daykeeper request exceeded ${__privateGet(this, _timeoutMs)}ms`,
162
+ retryable: true
163
+ });
164
+ }
165
+ if (options.signal?.aborted) {
166
+ throw new DaykeeperReactNativeTransportError({
167
+ code: "REQUEST_ABORTED",
168
+ message: "The Daykeeper request was aborted"
169
+ });
170
+ }
171
+ throw new DaykeeperReactNativeTransportError({
172
+ code: "NETWORK_ERROR",
173
+ message: "The Daykeeper customer API could not be reached",
174
+ retryable: true
175
+ });
176
+ }
177
+ const payload = await readJson(response);
178
+ if (response.status === 401 && attempt === 0) continue;
179
+ if (!response.ok) {
180
+ const code = isRecord(payload) && typeof payload.error === "string" ? payload.error : "daykeeper_request_failed";
181
+ throw new DaykeeperReactNativeApiError({
182
+ status: response.status,
183
+ code,
184
+ retryable: response.status === 408 || response.status === 429 || response.status >= 500
185
+ });
186
+ }
187
+ if (!isRecord(payload)) {
188
+ throw new DaykeeperReactNativeTransportError({
189
+ code: "INVALID_RESPONSE",
190
+ message: "The Daykeeper customer API returned an invalid response",
191
+ retryable: true
192
+ });
193
+ }
194
+ return payload;
195
+ }
196
+ throw new Error("Unreachable Daykeeper request state");
197
+ } finally {
198
+ clearTimeout(timeout);
199
+ options.signal?.removeEventListener("abort", onCallerAbort);
200
+ }
201
+ };
202
+ function createDaykeeperReactNativeClient(options) {
203
+ return new DaykeeperReactNativeClient(options);
204
+ }
205
+ function parseBaseUrl(value) {
206
+ let url;
207
+ try {
208
+ url = new URL(value);
209
+ } catch {
210
+ throw configurationError("baseUrl must be a valid absolute URL");
211
+ }
212
+ const local = ["localhost", "127.0.0.1", "::1"].includes(url.hostname);
213
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:")) {
214
+ throw configurationError(
215
+ "baseUrl must use HTTPS except for loopback development"
216
+ );
217
+ }
218
+ if (url.username || url.password || url.search || url.hash) {
219
+ throw configurationError(
220
+ "baseUrl cannot include credentials, a query, or a fragment"
221
+ );
222
+ }
223
+ return url.toString().replace(/\/$/, "");
224
+ }
225
+ function validateTimeout(value) {
226
+ if (!Number.isInteger(value) || value < 1e3 || value > 6e4) {
227
+ throw configurationError(
228
+ "timeoutMs must be an integer from 1000 through 60000"
229
+ );
230
+ }
231
+ return value;
232
+ }
233
+ function validateToken(value) {
234
+ if (typeof value !== "string" || !value || value.length > MAX_TOKEN_LENGTH || /[\r\n]/.test(value)) {
235
+ throw configurationError("The customer access token is invalid");
236
+ }
237
+ return value;
238
+ }
239
+ function positiveInteger(value, name) {
240
+ if (!Number.isInteger(value) || value < 1) {
241
+ throw configurationError(`${name} must be a positive integer`);
242
+ }
243
+ return value;
244
+ }
245
+ function validateMessage(value) {
246
+ if (typeof value !== "string") {
247
+ throw configurationError("Message content must be a string");
248
+ }
249
+ const normalized = value.trim();
250
+ if (!normalized || normalized.length > MAX_MESSAGE_LENGTH) {
251
+ throw configurationError(
252
+ `Message content must be 1 to ${MAX_MESSAGE_LENGTH} characters`
253
+ );
254
+ }
255
+ return normalized;
256
+ }
257
+ async function readJson(response) {
258
+ const declaredLength = Number(response.headers.get("content-length"));
259
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_BYTES) {
260
+ throw responseTooLarge();
261
+ }
262
+ const body = response.body;
263
+ if (body && typeof body.getReader === "function" && typeof TextDecoder === "function") {
264
+ return parseJson(await readStream(body));
265
+ }
266
+ let text;
267
+ try {
268
+ text = await response.text();
269
+ } catch {
270
+ throw invalidResponse();
271
+ }
272
+ if (utf8LengthExceeds(text, MAX_RESPONSE_BYTES)) {
273
+ throw responseTooLarge();
274
+ }
275
+ return parseJson(text);
276
+ }
277
+ async function readStream(body) {
278
+ const reader = body.getReader();
279
+ const chunks = [];
280
+ let total = 0;
281
+ try {
282
+ for (; ; ) {
283
+ const { done, value } = await reader.read();
284
+ if (done) break;
285
+ total += value.byteLength;
286
+ if (total > MAX_RESPONSE_BYTES) {
287
+ await reader.cancel();
288
+ throw responseTooLarge();
289
+ }
290
+ chunks.push(value);
291
+ }
292
+ } finally {
293
+ reader.releaseLock();
294
+ }
295
+ const bytes = new Uint8Array(total);
296
+ let offset = 0;
297
+ for (const chunk of chunks) {
298
+ bytes.set(chunk, offset);
299
+ offset += chunk.byteLength;
300
+ }
301
+ return new TextDecoder().decode(bytes);
302
+ }
303
+ function parseJson(text) {
304
+ if (!text) return void 0;
305
+ try {
306
+ return JSON.parse(text);
307
+ } catch {
308
+ throw invalidResponse();
309
+ }
310
+ }
311
+ function utf8LengthExceeds(value, maximum) {
312
+ let bytes = 0;
313
+ for (let index = 0; index < value.length; index += 1) {
314
+ const codeUnit = value.charCodeAt(index);
315
+ if (codeUnit < 128) bytes += 1;
316
+ else if (codeUnit < 2048) bytes += 2;
317
+ else if (codeUnit >= 55296 && codeUnit <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
318
+ bytes += 4;
319
+ index += 1;
320
+ } else bytes += 3;
321
+ if (bytes > maximum) return true;
322
+ }
323
+ return false;
324
+ }
325
+ function isRecord(value) {
326
+ return typeof value === "object" && value !== null && !Array.isArray(value);
327
+ }
328
+ function responseTooLarge() {
329
+ return new DaykeeperReactNativeTransportError({
330
+ code: "RESPONSE_TOO_LARGE",
331
+ message: "The Daykeeper response exceeded 1 MiB"
332
+ });
333
+ }
334
+ function invalidResponse() {
335
+ return new DaykeeperReactNativeTransportError({
336
+ code: "INVALID_RESPONSE",
337
+ message: "The Daykeeper customer API returned invalid JSON",
338
+ retryable: true
339
+ });
340
+ }
341
+ function configurationError(message) {
342
+ return new DaykeeperReactNativeTransportError({
343
+ code: "INVALID_CONFIGURATION",
344
+ message
345
+ });
346
+ }
347
+ export {
348
+ DaykeeperReactNativeApiError,
349
+ DaykeeperReactNativeClient,
350
+ DaykeeperReactNativeTransportError,
351
+ createDaykeeperReactNativeClient
352
+ };
353
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export type DaykeeperReactNativeTransportErrorCode =\n | \"INVALID_CONFIGURATION\"\n | \"NETWORK_ERROR\"\n | \"REQUEST_ABORTED\"\n | \"REQUEST_TIMEOUT\"\n | \"INVALID_RESPONSE\"\n | \"RESPONSE_TOO_LARGE\";\n\nexport class DaykeeperReactNativeApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryable: boolean;\n\n constructor(options: { status: number; code: string; retryable: boolean }) {\n super(options.code);\n this.name = \"DaykeeperReactNativeApiError\";\n this.status = options.status;\n this.code = options.code;\n this.retryable = options.retryable;\n }\n\n toJSON(): {\n name: string;\n status: number;\n code: string;\n retryable: boolean;\n } {\n return {\n name: this.name,\n status: this.status,\n code: this.code,\n retryable: this.retryable,\n };\n }\n}\n\nexport class DaykeeperReactNativeTransportError extends Error {\n readonly code: DaykeeperReactNativeTransportErrorCode;\n readonly retryable: boolean;\n\n constructor(options: {\n code: DaykeeperReactNativeTransportErrorCode;\n message: string;\n retryable?: boolean;\n }) {\n super(options.message);\n this.name = \"DaykeeperReactNativeTransportError\";\n this.code = options.code;\n this.retryable = options.retryable ?? false;\n }\n\n toJSON(): {\n name: string;\n code: DaykeeperReactNativeTransportErrorCode;\n message: string;\n retryable: boolean;\n } {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n retryable: this.retryable,\n };\n }\n}\n","import {\n DaykeeperReactNativeApiError,\n DaykeeperReactNativeTransportError,\n} from \"./errors.js\";\nimport type {\n DaykeeperClaimConversationResult,\n DaykeeperConversationList,\n DaykeeperConversationResult,\n DaykeeperCustomerIdentity,\n DaykeeperMessageList,\n DaykeeperMessageResult,\n DaykeeperSeenResult,\n DaykeeperUnreadSummary,\n} from \"./types.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_RESPONSE_BYTES = 1024 * 1024;\nconst MAX_TOKEN_LENGTH = 16_384;\nconst MAX_MESSAGE_LENGTH = 16_000;\n\nexport interface DaykeeperReactNativeTokenProviderContext {\n /**\n * True only after Daykeeper rejected the first token with HTTP 401. The\n * provider should bypass any token cache and exchange the app session again.\n */\n forceRefresh: boolean;\n}\n\nexport type DaykeeperReactNativeTokenProvider = (\n context: DaykeeperReactNativeTokenProviderContext,\n) => string | Promise<string>;\n\nexport interface DaykeeperReactNativeClientOptions {\n baseUrl: string;\n getAccessToken: DaykeeperReactNativeTokenProvider;\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n}\n\nexport interface DaykeeperReactNativeRequestOptions {\n signal?: AbortSignal;\n}\n\nexport class DaykeeperReactNativeClient {\n readonly #baseUrl: string;\n readonly #fetch: typeof globalThis.fetch;\n readonly #getAccessToken: DaykeeperReactNativeTokenProvider;\n readonly #timeoutMs: number;\n\n constructor(options: DaykeeperReactNativeClientOptions) {\n this.#baseUrl = parseBaseUrl(options.baseUrl);\n this.#fetch = options.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw configurationError(\"A Fetch API implementation is required\");\n }\n if (typeof options.getAccessToken !== \"function\") {\n throw configurationError(\"getAccessToken must be a function\");\n }\n this.#getAccessToken = options.getAccessToken;\n this.#timeoutMs = validateTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n }\n\n getIdentity(\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperCustomerIdentity> {\n return this.#request(\"/v1/identity\", { signal: options?.signal });\n }\n\n listConversations(\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperConversationList> {\n return this.#request(\"/v1/conversations\", { signal: options?.signal });\n }\n\n createConversation(\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperConversationResult> {\n return this.#request(\"/v1/conversations\", {\n method: \"POST\",\n signal: options?.signal,\n });\n }\n\n getUnread(\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperUnreadSummary> {\n return this.#request(\"/v1/unread\", { signal: options?.signal });\n }\n\n markConversationSeen(\n conversationId: number,\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperSeenResult> {\n return this.#request(\n `/v1/conversations/${positiveInteger(conversationId, \"conversationId\")}/seen`,\n { method: \"POST\", signal: options?.signal },\n );\n }\n\n listMessages(\n conversationId: number,\n options: DaykeeperReactNativeRequestOptions & { after?: number } = {},\n ): Promise<DaykeeperMessageList> {\n const id = positiveInteger(conversationId, \"conversationId\");\n const after =\n options.after === undefined\n ? \"\"\n : `?after=${positiveInteger(options.after, \"after\")}`;\n return this.#request(`/v1/conversations/${id}/messages${after}`, {\n signal: options.signal,\n });\n }\n\n sendMessage(\n conversationId: number,\n content: string,\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperMessageResult> {\n const normalizedContent = validateMessage(content);\n return this.#request(\n `/v1/conversations/${positiveInteger(conversationId, \"conversationId\")}/messages`,\n {\n method: \"POST\",\n body: { content: normalizedContent },\n signal: options?.signal,\n },\n );\n }\n\n claimAnonymousConversation(\n widgetToken: string,\n options?: DaykeeperReactNativeRequestOptions,\n ): Promise<DaykeeperClaimConversationResult> {\n if (\n typeof widgetToken !== \"string\" ||\n !widgetToken.trim() ||\n widgetToken.length > MAX_TOKEN_LENGTH\n ) {\n throw configurationError(\"widgetToken is invalid\");\n }\n return this.#request(\"/v1/anonymous-conversations/claim\", {\n method: \"POST\",\n body: { widgetToken: widgetToken.trim() },\n signal: options?.signal,\n });\n }\n\n async #request<ResponseBody>(\n path: string,\n options: {\n method?: \"GET\" | \"POST\";\n body?: unknown;\n signal?: AbortSignal;\n } = {},\n ): Promise<ResponseBody> {\n const timeoutController = new AbortController();\n let timedOut = false;\n const timeout = setTimeout(() => {\n timedOut = true;\n timeoutController.abort();\n }, this.#timeoutMs);\n const onCallerAbort = () => timeoutController.abort();\n if (options.signal?.aborted) timeoutController.abort();\n else\n options.signal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\n try {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const token = validateToken(\n await this.#getAccessToken({ forceRefresh: attempt === 1 }),\n );\n const headers = new Headers({\n accept: \"application/json\",\n authorization: `Bearer ${token}`,\n });\n if (options.body !== undefined) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n let response: Response;\n try {\n response = await this.#fetch(`${this.#baseUrl}${path}`, {\n method: options.method ?? \"GET\",\n body:\n options.body === undefined\n ? undefined\n : JSON.stringify(options.body),\n headers,\n signal: timeoutController.signal,\n });\n } catch {\n if (timedOut) {\n throw new DaykeeperReactNativeTransportError({\n code: \"REQUEST_TIMEOUT\",\n message: `The Daykeeper request exceeded ${this.#timeoutMs}ms`,\n retryable: true,\n });\n }\n if (options.signal?.aborted) {\n throw new DaykeeperReactNativeTransportError({\n code: \"REQUEST_ABORTED\",\n message: \"The Daykeeper request was aborted\",\n });\n }\n throw new DaykeeperReactNativeTransportError({\n code: \"NETWORK_ERROR\",\n message: \"The Daykeeper customer API could not be reached\",\n retryable: true,\n });\n }\n\n const payload = await readJson(response);\n if (response.status === 401 && attempt === 0) continue;\n if (!response.ok) {\n const code =\n isRecord(payload) && typeof payload.error === \"string\"\n ? payload.error\n : \"daykeeper_request_failed\";\n throw new DaykeeperReactNativeApiError({\n status: response.status,\n code,\n retryable:\n response.status === 408 ||\n response.status === 429 ||\n response.status >= 500,\n });\n }\n if (!isRecord(payload)) {\n throw new DaykeeperReactNativeTransportError({\n code: \"INVALID_RESPONSE\",\n message: \"The Daykeeper customer API returned an invalid response\",\n retryable: true,\n });\n }\n return payload as ResponseBody;\n }\n throw new Error(\"Unreachable Daykeeper request state\");\n } finally {\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", onCallerAbort);\n }\n }\n}\n\nexport function createDaykeeperReactNativeClient(\n options: DaykeeperReactNativeClientOptions,\n): DaykeeperReactNativeClient {\n return new DaykeeperReactNativeClient(options);\n}\n\nfunction parseBaseUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw configurationError(\"baseUrl must be a valid absolute URL\");\n }\n const local = [\"localhost\", \"127.0.0.1\", \"::1\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\")) {\n throw configurationError(\n \"baseUrl must use HTTPS except for loopback development\",\n );\n }\n if (url.username || url.password || url.search || url.hash) {\n throw configurationError(\n \"baseUrl cannot include credentials, a query, or a fragment\",\n );\n }\n return url.toString().replace(/\\/$/, \"\");\n}\n\nfunction validateTimeout(value: number): number {\n if (!Number.isInteger(value) || value < 1_000 || value > 60_000) {\n throw configurationError(\n \"timeoutMs must be an integer from 1000 through 60000\",\n );\n }\n return value;\n}\n\nfunction validateToken(value: string): string {\n if (\n typeof value !== \"string\" ||\n !value ||\n value.length > MAX_TOKEN_LENGTH ||\n /[\\r\\n]/.test(value)\n ) {\n throw configurationError(\"The customer access token is invalid\");\n }\n return value;\n}\n\nfunction positiveInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw configurationError(`${name} must be a positive integer`);\n }\n return value;\n}\n\nfunction validateMessage(value: string): string {\n if (typeof value !== \"string\") {\n throw configurationError(\"Message content must be a string\");\n }\n const normalized = value.trim();\n if (!normalized || normalized.length > MAX_MESSAGE_LENGTH) {\n throw configurationError(\n `Message content must be 1 to ${MAX_MESSAGE_LENGTH} characters`,\n );\n }\n return normalized;\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n const declaredLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_BYTES) {\n throw responseTooLarge();\n }\n\n const body = response.body;\n if (\n body &&\n typeof body.getReader === \"function\" &&\n typeof TextDecoder === \"function\"\n ) {\n return parseJson(await readStream(body));\n }\n\n let text: string;\n try {\n text = await response.text();\n } catch {\n throw invalidResponse();\n }\n if (utf8LengthExceeds(text, MAX_RESPONSE_BYTES)) {\n throw responseTooLarge();\n }\n return parseJson(text);\n}\n\nasync function readStream(body: ReadableStream<Uint8Array>): Promise<string> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n throw responseTooLarge();\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const bytes = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(bytes);\n}\n\nfunction parseJson(text: string): unknown {\n if (!text) return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw invalidResponse();\n }\n}\n\nfunction utf8LengthExceeds(value: string, maximum: number): boolean {\n let bytes = 0;\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit < 0x80) bytes += 1;\n else if (codeUnit < 0x800) bytes += 2;\n else if (\n codeUnit >= 0xd800 &&\n codeUnit <= 0xdbff &&\n index + 1 < value.length &&\n value.charCodeAt(index + 1) >= 0xdc00 &&\n value.charCodeAt(index + 1) <= 0xdfff\n ) {\n bytes += 4;\n index += 1;\n } else bytes += 3;\n if (bytes > maximum) return true;\n }\n return false;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction responseTooLarge(): DaykeeperReactNativeTransportError {\n return new DaykeeperReactNativeTransportError({\n code: \"RESPONSE_TOO_LARGE\",\n message: \"The Daykeeper response exceeded 1 MiB\",\n });\n}\n\nfunction invalidResponse(): DaykeeperReactNativeTransportError {\n return new DaykeeperReactNativeTransportError({\n code: \"INVALID_RESPONSE\",\n message: \"The Daykeeper customer API returned invalid JSON\",\n retryable: true,\n });\n}\n\nfunction configurationError(\n message: string,\n): DaykeeperReactNativeTransportError {\n return new DaykeeperReactNativeTransportError({\n code: \"INVALID_CONFIGURATION\",\n message,\n });\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@skyporch/daykeeper-react-native",
3
+ "version": "0.1.0",
4
+ "description": "Official React Native SDK for Daykeeper customer support experiences",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "react-native": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "react-native": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "CHANGELOG.md",
27
+ "COMPATIBILITY.md",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --target es2020 --clean",
32
+ "check": "pnpm format:check && pnpm check:generated && pnpm typecheck && pnpm test && pnpm build && pnpm verify:exports && pnpm smoke:metro && pnpm pack:check",
33
+ "check:generated": "pnpm generate && git diff --exit-code -- src/generated/schema.ts",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "generate": "openapi-typescript openapi/customer.yaml --output src/generated/schema.ts",
37
+ "pack:check": "npm pack --dry-run --json",
38
+ "smoke:metro": "react-native bundle --platform ios --dev false --entry-file smoke/index.js --bundle-output .smoke/ios.jsbundle && react-native bundle --platform android --dev false --entry-file smoke/index.js --bundle-output .smoke/android.jsbundle",
39
+ "test": "tsx --test \"test/**/*.test.ts\"",
40
+ "typecheck": "tsc -p tsconfig.json --noEmit",
41
+ "verify:exports": "node scripts/verify-exports.mjs",
42
+ "prepublishOnly": "node scripts/verify-release.mjs",
43
+ "prepack": "pnpm run build"
44
+ },
45
+ "packageManager": "pnpm@10.8.0",
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "peerDependencies": {
50
+ "react-native": ">=0.79"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "provenance": true
55
+ },
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/SkyPorch/daykeeper-react-native.git"
59
+ },
60
+ "bugs": {
61
+ "url": "https://github.com/SkyPorch/daykeeper-react-native/issues"
62
+ },
63
+ "homepage": "https://github.com/SkyPorch/daykeeper-react-native#readme",
64
+ "license": "MIT",
65
+ "dependencies": {
66
+ "@babel/runtime": "7.29.2"
67
+ },
68
+ "devDependencies": {
69
+ "@react-native-community/cli": "20.2.0",
70
+ "@react-native/metro-config": "0.86.2",
71
+ "@types/node": "22.20.1",
72
+ "@types/react": "19.2.14",
73
+ "openapi-typescript": "7.13.0",
74
+ "prettier": "3.9.6",
75
+ "react": "19.2.3",
76
+ "react-native": "0.86.2",
77
+ "tsup": "8.5.1",
78
+ "tsx": "4.23.12",
79
+ "typescript": "5.9.3"
80
+ },
81
+ "keywords": [
82
+ "daykeeper",
83
+ "customer-support",
84
+ "react-native",
85
+ "expo",
86
+ "ios",
87
+ "android",
88
+ "sdk"
89
+ ],
90
+ "pnpm": {
91
+ "overrides": {
92
+ "esbuild": "0.28.1"
93
+ }
94
+ }
95
+ }