@nurama/sdk 0.0.0-stage → 1.4.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 (220) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1080 -2
  4. package/dist/BotClient.d.ts +66 -0
  5. package/dist/BotClient.d.ts.map +1 -0
  6. package/dist/BotClient.js +68 -0
  7. package/dist/BotClient.js.map +1 -0
  8. package/dist/NuramaClient.d.ts +448 -0
  9. package/dist/NuramaClient.d.ts.map +1 -0
  10. package/dist/NuramaClient.js +864 -0
  11. package/dist/NuramaClient.js.map +1 -0
  12. package/dist/browser/nurama-bot-sdk.js +11780 -0
  13. package/dist/browser/nurama-bot-sdk.min.js +1 -0
  14. package/dist/browser/nurama-sdk.js +11732 -0
  15. package/dist/browser/nurama-sdk.min.js +1 -0
  16. package/dist/routes/ai.d.ts +280 -0
  17. package/dist/routes/ai.d.ts.map +1 -0
  18. package/dist/routes/ai.js +173 -0
  19. package/dist/routes/ai.js.map +1 -0
  20. package/dist/routes/asset.d.ts +493 -0
  21. package/dist/routes/asset.d.ts.map +1 -0
  22. package/dist/routes/asset.js +848 -0
  23. package/dist/routes/asset.js.map +1 -0
  24. package/dist/routes/auth.d.ts +218 -0
  25. package/dist/routes/auth.d.ts.map +1 -0
  26. package/dist/routes/auth.js +454 -0
  27. package/dist/routes/auth.js.map +1 -0
  28. package/dist/routes/blogPosts.d.ts +17 -0
  29. package/dist/routes/blogPosts.d.ts.map +1 -0
  30. package/dist/routes/blogPosts.js +29 -0
  31. package/dist/routes/blogPosts.js.map +1 -0
  32. package/dist/routes/board.d.ts +187 -0
  33. package/dist/routes/board.d.ts.map +1 -0
  34. package/dist/routes/board.js +270 -0
  35. package/dist/routes/board.js.map +1 -0
  36. package/dist/routes/bot.d.ts +147 -0
  37. package/dist/routes/bot.d.ts.map +1 -0
  38. package/dist/routes/bot.js +157 -0
  39. package/dist/routes/bot.js.map +1 -0
  40. package/dist/routes/chat.d.ts +842 -0
  41. package/dist/routes/chat.d.ts.map +1 -0
  42. package/dist/routes/chat.js +863 -0
  43. package/dist/routes/chat.js.map +1 -0
  44. package/dist/routes/chatAi.d.ts +51 -0
  45. package/dist/routes/chatAi.d.ts.map +1 -0
  46. package/dist/routes/chatAi.js +109 -0
  47. package/dist/routes/chatAi.js.map +1 -0
  48. package/dist/routes/config.d.ts +11 -0
  49. package/dist/routes/config.d.ts.map +1 -0
  50. package/dist/routes/config.js +24 -0
  51. package/dist/routes/config.js.map +1 -0
  52. package/dist/routes/convo.d.ts +169 -0
  53. package/dist/routes/convo.d.ts.map +1 -0
  54. package/dist/routes/convo.js +284 -0
  55. package/dist/routes/convo.js.map +1 -0
  56. package/dist/routes/credits.d.ts +82 -0
  57. package/dist/routes/credits.d.ts.map +1 -0
  58. package/dist/routes/credits.js +49 -0
  59. package/dist/routes/credits.js.map +1 -0
  60. package/dist/routes/device.d.ts +74 -0
  61. package/dist/routes/device.d.ts.map +1 -0
  62. package/dist/routes/device.js +122 -0
  63. package/dist/routes/device.js.map +1 -0
  64. package/dist/routes/folder.d.ts +75 -0
  65. package/dist/routes/folder.d.ts.map +1 -0
  66. package/dist/routes/folder.js +99 -0
  67. package/dist/routes/folder.js.map +1 -0
  68. package/dist/routes/invite.d.ts +61 -0
  69. package/dist/routes/invite.d.ts.map +1 -0
  70. package/dist/routes/invite.js +86 -0
  71. package/dist/routes/invite.js.map +1 -0
  72. package/dist/routes/joinLink.d.ts +38 -0
  73. package/dist/routes/joinLink.d.ts.map +1 -0
  74. package/dist/routes/joinLink.js +81 -0
  75. package/dist/routes/joinLink.js.map +1 -0
  76. package/dist/routes/membership.d.ts +116 -0
  77. package/dist/routes/membership.d.ts.map +1 -0
  78. package/dist/routes/membership.js +183 -0
  79. package/dist/routes/membership.js.map +1 -0
  80. package/dist/routes/notification.d.ts +103 -0
  81. package/dist/routes/notification.d.ts.map +1 -0
  82. package/dist/routes/notification.js +89 -0
  83. package/dist/routes/notification.js.map +1 -0
  84. package/dist/routes/payment.d.ts +56 -0
  85. package/dist/routes/payment.d.ts.map +1 -0
  86. package/dist/routes/payment.js +78 -0
  87. package/dist/routes/payment.js.map +1 -0
  88. package/dist/routes/product.d.ts +43 -0
  89. package/dist/routes/product.d.ts.map +1 -0
  90. package/dist/routes/product.js +53 -0
  91. package/dist/routes/product.js.map +1 -0
  92. package/dist/routes/project.d.ts +821 -0
  93. package/dist/routes/project.d.ts.map +1 -0
  94. package/dist/routes/project.js +1153 -0
  95. package/dist/routes/project.js.map +1 -0
  96. package/dist/routes/public.d.ts +269 -0
  97. package/dist/routes/public.d.ts.map +1 -0
  98. package/dist/routes/public.js +412 -0
  99. package/dist/routes/public.js.map +1 -0
  100. package/dist/routes/scratch.d.ts +70 -0
  101. package/dist/routes/scratch.d.ts.map +1 -0
  102. package/dist/routes/scratch.js +67 -0
  103. package/dist/routes/scratch.js.map +1 -0
  104. package/dist/routes/settings.d.ts +102 -0
  105. package/dist/routes/settings.d.ts.map +1 -0
  106. package/dist/routes/settings.js +94 -0
  107. package/dist/routes/settings.js.map +1 -0
  108. package/dist/routes/shortlink.d.ts +79 -0
  109. package/dist/routes/shortlink.d.ts.map +1 -0
  110. package/dist/routes/shortlink.js +25 -0
  111. package/dist/routes/shortlink.js.map +1 -0
  112. package/dist/routes/socket.d.ts +108 -0
  113. package/dist/routes/socket.d.ts.map +1 -0
  114. package/dist/routes/socket.js +555 -0
  115. package/dist/routes/socket.js.map +1 -0
  116. package/dist/routes/storage.d.ts +44 -0
  117. package/dist/routes/storage.d.ts.map +1 -0
  118. package/dist/routes/storage.js +49 -0
  119. package/dist/routes/storage.js.map +1 -0
  120. package/dist/routes/subscription.d.ts +184 -0
  121. package/dist/routes/subscription.d.ts.map +1 -0
  122. package/dist/routes/subscription.js +219 -0
  123. package/dist/routes/subscription.js.map +1 -0
  124. package/dist/routes/supportChat.d.ts +40 -0
  125. package/dist/routes/supportChat.d.ts.map +1 -0
  126. package/dist/routes/supportChat.js +53 -0
  127. package/dist/routes/supportChat.js.map +1 -0
  128. package/dist/routes/supportTicket.d.ts +89 -0
  129. package/dist/routes/supportTicket.d.ts.map +1 -0
  130. package/dist/routes/supportTicket.js +54 -0
  131. package/dist/routes/supportTicket.js.map +1 -0
  132. package/dist/routes/tag.d.ts +72 -0
  133. package/dist/routes/tag.d.ts.map +1 -0
  134. package/dist/routes/tag.js +81 -0
  135. package/dist/routes/tag.js.map +1 -0
  136. package/dist/routes/task.d.ts +252 -0
  137. package/dist/routes/task.d.ts.map +1 -0
  138. package/dist/routes/task.js +284 -0
  139. package/dist/routes/task.js.map +1 -0
  140. package/dist/routes/taskRelation.d.ts +80 -0
  141. package/dist/routes/taskRelation.d.ts.map +1 -0
  142. package/dist/routes/taskRelation.js +71 -0
  143. package/dist/routes/taskRelation.js.map +1 -0
  144. package/dist/routes/token.d.ts +75 -0
  145. package/dist/routes/token.d.ts.map +1 -0
  146. package/dist/routes/token.js +51 -0
  147. package/dist/routes/token.js.map +1 -0
  148. package/dist/routes/user.d.ts +112 -0
  149. package/dist/routes/user.d.ts.map +1 -0
  150. package/dist/routes/user.js +151 -0
  151. package/dist/routes/user.js.map +1 -0
  152. package/dist/routes/version.d.ts +42 -0
  153. package/dist/routes/version.d.ts.map +1 -0
  154. package/dist/routes/version.js +38 -0
  155. package/dist/routes/version.js.map +1 -0
  156. package/dist/routes/webhook.d.ts +170 -0
  157. package/dist/routes/webhook.d.ts.map +1 -0
  158. package/dist/routes/webhook.js +173 -0
  159. package/dist/routes/webhook.js.map +1 -0
  160. package/dist/routes/workspace.d.ts +120 -0
  161. package/dist/routes/workspace.d.ts.map +1 -0
  162. package/dist/routes/workspace.js +199 -0
  163. package/dist/routes/workspace.js.map +1 -0
  164. package/dist/utils/uploadSessionManager.d.ts +133 -0
  165. package/dist/utils/uploadSessionManager.d.ts.map +1 -0
  166. package/dist/utils/uploadSessionManager.js +321 -0
  167. package/dist/utils/uploadSessionManager.js.map +1 -0
  168. package/dist/utils/urlParams.d.ts +35 -0
  169. package/dist/utils/urlParams.d.ts.map +1 -0
  170. package/dist/utils/urlParams.js +146 -0
  171. package/dist/utils/urlParams.js.map +1 -0
  172. package/dist/version.d.ts +15 -0
  173. package/dist/version.d.ts.map +1 -0
  174. package/dist/version.js +12 -0
  175. package/dist/version.js.map +1 -0
  176. package/package.json +87 -3
  177. package/src/BotClient.ts +113 -0
  178. package/src/NuramaClient.ts +1193 -0
  179. package/src/bot-browser-entry.js +15 -0
  180. package/src/browser-entry.js +20 -0
  181. package/src/routes/ai.ts +378 -0
  182. package/src/routes/asset.ts +1104 -0
  183. package/src/routes/auth.ts +587 -0
  184. package/src/routes/blogPosts.ts +29 -0
  185. package/src/routes/board.ts +403 -0
  186. package/src/routes/bot.ts +257 -0
  187. package/src/routes/chat.ts +1292 -0
  188. package/src/routes/chatAi.ts +125 -0
  189. package/src/routes/config.ts +31 -0
  190. package/src/routes/convo.ts +321 -0
  191. package/src/routes/credits.ts +112 -0
  192. package/src/routes/device.ts +133 -0
  193. package/src/routes/folder.ts +154 -0
  194. package/src/routes/invite.ts +133 -0
  195. package/src/routes/joinLink.ts +100 -0
  196. package/src/routes/membership.ts +237 -0
  197. package/src/routes/notification.ts +166 -0
  198. package/src/routes/payment.ts +104 -0
  199. package/src/routes/product.ts +67 -0
  200. package/src/routes/project.ts +1528 -0
  201. package/src/routes/public.ts +496 -0
  202. package/src/routes/scratch.ts +94 -0
  203. package/src/routes/settings.ts +152 -0
  204. package/src/routes/shortlink.ts +90 -0
  205. package/src/routes/socket.ts +739 -0
  206. package/src/routes/storage.ts +83 -0
  207. package/src/routes/subscription.ts +307 -0
  208. package/src/routes/supportChat.ts +62 -0
  209. package/src/routes/supportTicket.ts +114 -0
  210. package/src/routes/tag.ts +131 -0
  211. package/src/routes/task.ts +431 -0
  212. package/src/routes/taskRelation.ts +125 -0
  213. package/src/routes/token.ts +113 -0
  214. package/src/routes/user.ts +214 -0
  215. package/src/routes/version.ts +62 -0
  216. package/src/routes/webhook.ts +295 -0
  217. package/src/routes/workspace.ts +223 -0
  218. package/src/utils/uploadSessionManager.ts +407 -0
  219. package/src/utils/urlParams.ts +181 -0
  220. package/src/version.ts +22 -0
@@ -0,0 +1,848 @@
1
+ import { uploadSessionManager } from '../utils/uploadSessionManager.js';
2
+ /**
3
+ * Defines asset-related methods for the NuramaClient.
4
+ * @param {NuramaClient} client - The NuramaClient instance.
5
+ * @returns {object} An object containing the asset-related methods.
6
+ */
7
+ export default function createAssetMethods(client) {
8
+ /**
9
+ * Uploads a single part of a file
10
+ * @private
11
+ * @param {string} signedUrl - The signed URL for uploading this part
12
+ * @param {number} partNumber - The part number (1-based index)
13
+ * @param {number} totalParts - Total number of parts
14
+ * @param {Blob|Buffer|ArrayBuffer} data - The data chunk to upload
15
+ * @param {PartUploadOptions} options - Upload options
16
+ * @param {number} attempt - Current attempt number
17
+ * @returns {Promise<{ETag: string, PartNumber: number}>} The completed part information
18
+ */
19
+ async function uploadPart(signedUrl, partNumber, totalParts, data, options = {}, attempt = 1) {
20
+ const { onPartProgress, maxRetries = 3, abortSignal } = options;
21
+ try {
22
+ // Report start of part upload
23
+ if (onPartProgress) {
24
+ onPartProgress({
25
+ partNumber,
26
+ totalParts,
27
+ partPercent: 0
28
+ });
29
+ }
30
+ // Check for abort signal
31
+ if (abortSignal?.aborted) {
32
+ throw new Error('Upload aborted by user');
33
+ }
34
+ // Create headers
35
+ const headers = {
36
+ 'Content-Type': 'application/octet-stream',
37
+ };
38
+ // Set Content-Length only in Node.js (Buffer). In browsers, Content-Length
39
+ // is a forbidden header that gets silently stripped — the browser calculates
40
+ // it automatically from the body.
41
+ if (typeof Buffer !== 'undefined' && data instanceof Buffer) {
42
+ headers['Content-Length'] = data.length.toString();
43
+ }
44
+ // Upload the part
45
+ // Note: Buffer extends Uint8Array, which is a valid BodyInit type
46
+ const response = await fetch(signedUrl, {
47
+ method: 'PUT',
48
+ headers,
49
+ body: data,
50
+ signal: abortSignal
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
54
+ }
55
+ // Get ETag from response headers
56
+ const eTag = response.headers.get('ETag')?.replace(/['"]/g, '') || '';
57
+ if (!eTag) {
58
+ throw new Error('Server did not return an ETag');
59
+ }
60
+ // Report completion of part upload
61
+ if (onPartProgress) {
62
+ onPartProgress({
63
+ partNumber,
64
+ totalParts,
65
+ partPercent: 100
66
+ });
67
+ }
68
+ return {
69
+ ETag: eTag,
70
+ PartNumber: partNumber
71
+ };
72
+ }
73
+ catch (error) {
74
+ // Handle retries
75
+ if (attempt < maxRetries) {
76
+ console.warn(`Part ${partNumber} upload failed (attempt ${attempt}/${maxRetries}). Retrying...`);
77
+ // Exponential backoff with jitter
78
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1) * (0.9 + Math.random() * 0.2), 10000);
79
+ await new Promise(resolve => setTimeout(resolve, delay));
80
+ return uploadPart(signedUrl, partNumber, totalParts, data, options, attempt + 1);
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+ return {
86
+ /**
87
+ * Retrieves a specific asset by its ID with optional chat data.
88
+ * @param {string} assetId - The ID of the asset.
89
+ * @param {GetAssetParams} [params] - Optional parameters for chat data inclusion.
90
+ * @returns {Promise<AssetResponse>} The asset object with optional chat data.
91
+ */
92
+ async getAsset(assetId, params) {
93
+ if (!assetId)
94
+ throw new Error('assetId is required.');
95
+ return client._request({
96
+ method: 'GET',
97
+ endpoint: `/v1/assets/${assetId}`,
98
+ params,
99
+ sendJWT: true,
100
+ });
101
+ },
102
+ /**
103
+ * Lists every location an asset is referenced — its primary file system and
104
+ * each secondary reference (reviewer, submission, public), grouped and counted.
105
+ *
106
+ * Renaming an asset retitles it at every one of these locations, and deleting
107
+ * its last primary reference removes them all — so this is what the rename and
108
+ * delete confirmations show the user before either happens.
109
+ *
110
+ * @param {string} assetId - The ID of the asset.
111
+ * @returns {Promise<AssetReferences>} The asset's references.
112
+ */
113
+ async getAssetReferences(assetId) {
114
+ if (!assetId)
115
+ throw new Error('assetId is required.');
116
+ return client._request({
117
+ method: 'GET',
118
+ endpoint: `/v1/assets/${assetId}/references`,
119
+ sendJWT: true,
120
+ });
121
+ },
122
+ /**
123
+ * Updates an asset.
124
+ * @param {string} assetId - The ID of the asset to update.
125
+ * @param {UpdateAssetData} updateData - Data to update (e.g., name, meta, tags, folderId).
126
+ * @returns {Promise<AssetResponse>} The updated asset object.
127
+ */
128
+ async updateAsset(assetId, updateData) {
129
+ if (!assetId)
130
+ throw new Error('assetId is required.');
131
+ return client._request({
132
+ method: 'PUT',
133
+ endpoint: `/v1/assets/${assetId}`,
134
+ body: updateData,
135
+ sendJWT: true,
136
+ });
137
+ },
138
+ /**
139
+ * Deletes an asset (marks for deletion).
140
+ * @param {string} assetId - The ID of the asset to delete.
141
+ * @returns {Promise<void>}
142
+ */
143
+ async deleteAsset(assetId) {
144
+ if (!assetId)
145
+ throw new Error('assetId is required.');
146
+ return client._request({
147
+ method: 'DELETE',
148
+ endpoint: `/v1/assets/${assetId}`,
149
+ sendJWT: true,
150
+ });
151
+ },
152
+ /**
153
+ * Retrieves a specific file from an asset.
154
+ * @param {string} assetId - The ID of the asset.
155
+ * @param {string} fileId - The ID of the file.
156
+ * @returns {Promise<FileResponse>} The file object.
157
+ */
158
+ async getFile(assetId, fileId) {
159
+ if (!assetId)
160
+ throw new Error('assetId is required.');
161
+ if (!fileId)
162
+ throw new Error('fileId is required.');
163
+ return client._request({
164
+ method: 'GET',
165
+ endpoint: `/v1/assets/${assetId}/file/${fileId}`,
166
+ sendJWT: true,
167
+ });
168
+ },
169
+ /**
170
+ * Retrieves files of a specific function type from an asset.
171
+ * @param {string} assetId - The ID of the asset.
172
+ * @param {string} functionType - The function type of the files (e.g., 'thumbnail', 'original').
173
+ * @returns {Promise<FileResponse[]>} An array of file objects.
174
+ */
175
+ async getFilesByFunctionType(assetId, functionType) {
176
+ if (!assetId)
177
+ throw new Error('assetId is required.');
178
+ if (!functionType)
179
+ throw new Error('functionType is required.');
180
+ return client._request({
181
+ method: 'GET',
182
+ endpoint: `/v1/assets/${assetId}/function-type/${functionType}`,
183
+ sendJWT: true,
184
+ });
185
+ },
186
+ /**
187
+ * Complete a multipart upload initiated by `createAssets`.
188
+ *
189
+ * Matches the route (`POST /v1/assets/complete-upload`) and mirrors
190
+ * `nuramaClient.scratch.completeUpload`, so moving between the asset
191
+ * and scratch namespaces uses the same verb.
192
+ *
193
+ * @param {CompleteMultipartUploadData} uploadData - Data including key, uploadId, parts, and assetId.
194
+ * @returns {Promise<any>} S3 completion response.
195
+ */
196
+ async completeUpload(uploadData) {
197
+ if (!uploadData || !uploadData.key || !uploadData.uploadId || !uploadData.parts || !uploadData.assetId) {
198
+ throw new Error('key, uploadId, parts, and assetId are required for completeUpload.');
199
+ }
200
+ return client._request({
201
+ method: 'POST',
202
+ endpoint: '/v1/assets/complete-upload',
203
+ body: uploadData,
204
+ sendJWT: true,
205
+ });
206
+ },
207
+ /**
208
+ * Uploads a file using multipart upload with the provided signed URLs
209
+ * @param {File|Blob|Buffer|string} file - The file to upload (File/Blob in browser, Buffer/string path in Node.js)
210
+ * @param {string[]} signedUrls - Array of signed URLs for each part
211
+ * @param {string} key - The S3 key for the upload
212
+ * @param {string} uploadId - The S3 uploadId for the multipart upload
213
+ * @param {MultipartUploadOptions} options - Upload options
214
+ * @returns {Promise<MultipartUploadResult>} The completed upload data
215
+ */
216
+ async multipartUpload(file, signedUrls, key, uploadId, options = {}) {
217
+ if (!file)
218
+ throw new Error('file is required');
219
+ if (!signedUrls || !signedUrls.length)
220
+ throw new Error('signedUrls array is required and cannot be empty');
221
+ if (!key)
222
+ throw new Error('key is required');
223
+ if (!uploadId)
224
+ throw new Error('uploadId is required');
225
+ const { onProgress, abortSignal, enableProgressPersistence, projectId, sessionId: providedSessionId, fileName, fileSize: providedFileSize, assetId } = options;
226
+ const totalParts = signedUrls.length;
227
+ const completedParts = [];
228
+ // Track progress of each part
229
+ const partProgress = new Array(totalParts).fill(0);
230
+ const calculateTotalProgress = () => {
231
+ const totalPercent = partProgress.reduce((sum, percent) => sum + percent, 0) / totalParts;
232
+ return Math.floor(totalPercent);
233
+ };
234
+ // Initialize upload session if enabled
235
+ let sessionData = null;
236
+ if (enableProgressPersistence && projectId) {
237
+ const actualSessionId = providedSessionId || `upload_${uploadId}_${Date.now()}`;
238
+ const actualFileName = fileName || (typeof file === 'string' ? file.split('/').pop() || 'unknown' : 'unknown');
239
+ let actualFileSize = providedFileSize;
240
+ // Get file size if not provided
241
+ if (!actualFileSize) {
242
+ if ((typeof File !== 'undefined' && file instanceof File) ||
243
+ (typeof Blob !== 'undefined' && file instanceof Blob)) {
244
+ actualFileSize = file.size;
245
+ }
246
+ else if (typeof Buffer !== 'undefined' && file instanceof Buffer) {
247
+ actualFileSize = file.length;
248
+ }
249
+ }
250
+ try {
251
+ sessionData = uploadSessionManager.createSession(projectId, actualSessionId, {
252
+ uploadId,
253
+ key,
254
+ fileName: actualFileName,
255
+ fileSize: actualFileSize || 0,
256
+ totalParts,
257
+ assetId
258
+ });
259
+ }
260
+ catch (error) {
261
+ console.warn('[SDK] Failed to create upload session:', error);
262
+ }
263
+ }
264
+ try {
265
+ // Get file data according to environment
266
+ let fileSize;
267
+ let getChunk;
268
+ // Browser environment (File/Blob)
269
+ if ((typeof File !== 'undefined' && file instanceof File) ||
270
+ (typeof Blob !== 'undefined' && file instanceof Blob)) {
271
+ const blob = file;
272
+ fileSize = blob.size;
273
+ getChunk = async (start, end) => {
274
+ // Read slice into ArrayBuffer to materialize the data before upload.
275
+ // Passing a Blob reference directly to fetch() can silently send
276
+ // empty bodies for large files when the browser loses the file handle.
277
+ const slice = blob.slice(start, end);
278
+ let buffer;
279
+ if (typeof slice.arrayBuffer === 'function') {
280
+ buffer = await slice.arrayBuffer();
281
+ }
282
+ else {
283
+ // React Native Blob polyfill lacks arrayBuffer() — use FileReader fallback
284
+ buffer = await new Promise((resolve, reject) => {
285
+ const reader = new FileReader();
286
+ reader.onload = () => resolve(reader.result);
287
+ reader.onerror = () => reject(new Error('Failed to read blob chunk'));
288
+ reader.readAsArrayBuffer(slice);
289
+ });
290
+ }
291
+ if (buffer.byteLength === 0 && end > start) {
292
+ throw new Error(`Failed to read file bytes ${start}-${end}: got 0 bytes (file may have been modified or removed)`);
293
+ }
294
+ return buffer;
295
+ };
296
+ }
297
+ // Node.js environment with Buffer
298
+ else if (typeof Buffer !== 'undefined' && file instanceof Buffer) {
299
+ const buffer = file;
300
+ fileSize = buffer.length;
301
+ getChunk = async (start, end) => {
302
+ return buffer.slice(start, end);
303
+ };
304
+ }
305
+ // Node.js environment with file path
306
+ else if (typeof file === 'string') {
307
+ // Handle Node.js file reading
308
+ if (typeof process === 'undefined' || typeof require !== 'function') {
309
+ throw new Error('File path provided but environment does not support Node.js file system');
310
+ }
311
+ try {
312
+ // Dynamic import of fs module for Node.js
313
+ const fs = await import('fs/promises');
314
+ const { stat, open } = fs;
315
+ // Get file size
316
+ const stats = await stat(file);
317
+ fileSize = stats.size;
318
+ getChunk = async (start, end) => {
319
+ const fileHandle = await open(file, 'r');
320
+ try {
321
+ const length = end - start;
322
+ const buffer = Buffer.alloc(length);
323
+ await fileHandle.read(buffer, 0, length, start);
324
+ return buffer;
325
+ }
326
+ finally {
327
+ await fileHandle.close();
328
+ }
329
+ };
330
+ }
331
+ catch (error) {
332
+ const errorMessage = error instanceof Error ? error.message : String(error);
333
+ throw new Error(`Failed to read file: ${errorMessage}`);
334
+ }
335
+ }
336
+ else {
337
+ throw new Error('Unsupported file type. Must be File, Blob, Buffer, or string path in Node.js');
338
+ }
339
+ const chunkSize = Math.ceil(fileSize / totalParts);
340
+ // Upload each part
341
+ for (let i = 0; i < totalParts; i++) {
342
+ if (abortSignal?.aborted) {
343
+ throw new Error('Upload aborted by user');
344
+ }
345
+ const partNumber = i + 1;
346
+ const start = i * chunkSize;
347
+ const end = Math.min((i + 1) * chunkSize, fileSize);
348
+ // Calculate current total progress (based on completed parts so far)
349
+ const currentTotalProgress = calculateTotalProgress();
350
+ client._log(`[DEBUG] Uploading part ${partNumber}/${totalParts}, bytes ${start}-${end - 1} of ${fileSize}, totalPercentComplete: ${currentTotalProgress}%`);
351
+ // Get the chunk data
352
+ const chunkData = await getChunk(start, end);
353
+ // Create part-specific options with progress callback
354
+ const partOptions = {
355
+ maxRetries: options.maxRetries,
356
+ abortSignal: abortSignal,
357
+ onPartProgress: onProgress ? (progress) => {
358
+ // Update this part's progress
359
+ partProgress[i] = progress.partPercent;
360
+ const totalPercentComplete = calculateTotalProgress();
361
+ // Update upload session if enabled
362
+ if (sessionData) {
363
+ try {
364
+ const completedPartsArray = partProgress
365
+ .map((percent, index) => percent === 100 ? index + 1 : null)
366
+ .filter(part => part !== null);
367
+ uploadSessionManager.updateProgress(sessionData.projectId, sessionData.sessionId, {
368
+ progress: totalPercentComplete,
369
+ partNumber: progress.partNumber,
370
+ completedParts: completedPartsArray
371
+ });
372
+ }
373
+ catch (error) {
374
+ console.warn('[SDK] Failed to update upload session progress:', error);
375
+ }
376
+ }
377
+ // Call user's progress callback with complete information
378
+ onProgress({
379
+ partNumber: progress.partNumber,
380
+ totalParts: progress.totalParts,
381
+ percentComplete: progress.partPercent,
382
+ totalPercentComplete
383
+ });
384
+ } : undefined
385
+ };
386
+ // Upload the part
387
+ const part = await uploadPart(signedUrls[i], partNumber, totalParts, chunkData, partOptions);
388
+ completedParts.push(part);
389
+ }
390
+ // Sort parts by part number to ensure correct order
391
+ completedParts.sort((a, b) => a.PartNumber - b.PartNumber);
392
+ // Mark upload session as completing if enabled
393
+ if (sessionData) {
394
+ try {
395
+ uploadSessionManager.updateStatus(sessionData.projectId, sessionData.sessionId, 'completing');
396
+ }
397
+ catch (error) {
398
+ console.warn('[SDK] Failed to update upload session status to completing:', error);
399
+ }
400
+ }
401
+ return {
402
+ key,
403
+ uploadId,
404
+ parts: completedParts
405
+ };
406
+ }
407
+ catch (error) {
408
+ // Mark upload session as failed if enabled
409
+ if (sessionData) {
410
+ try {
411
+ const errorMessage = error instanceof Error ? error.message : String(error);
412
+ uploadSessionManager.updateStatus(sessionData.projectId, sessionData.sessionId, 'failed', errorMessage);
413
+ }
414
+ catch (sessionError) {
415
+ console.warn('[SDK] Failed to update upload session status to failed:', sessionError);
416
+ }
417
+ }
418
+ // Re-throw the original error
419
+ throw error;
420
+ }
421
+ },
422
+ /**
423
+ * Gets the page number an asset appears on based on specified filters and sorting.
424
+ * @param {string} assetId - The ID of the asset to find the page for.
425
+ * @param {GetAssetPageParams} [params] - Query parameters for sorting, filtering, and pagination limit.
426
+ * @returns {Promise<AssetPageResponse>} Object containing the page number.
427
+ */
428
+ async getAssetPage(assetId, params) {
429
+ if (!assetId)
430
+ throw new Error('assetId is required.');
431
+ return client._request({
432
+ method: 'GET',
433
+ endpoint: `/v1/assets/page/${assetId}`,
434
+ params: params,
435
+ sendJWT: true,
436
+ });
437
+ },
438
+ /**
439
+ * Attempts to repair assets (e.g., regenerate signed URLs for pending uploads).
440
+ * @param {string[]} assetIds - An array of asset IDs to repair.
441
+ * @returns {Promise<RepairAssetsResponse>} Array of repair results.
442
+ */
443
+ async repairAssets(assetIds) {
444
+ if (!assetIds || assetIds.length === 0)
445
+ throw new Error('assetIds array is required and cannot be empty.');
446
+ return client._request({
447
+ method: 'POST',
448
+ endpoint: '/v1/assets/repair',
449
+ body: { assetIds },
450
+ sendJWT: true,
451
+ });
452
+ },
453
+ /**
454
+ * Generates signed download URLs for the original files of specified assets.
455
+ * @param {string[]} assetIds - An array of asset IDs.
456
+ * @returns {Promise<DownloadAssetsResponse>} Array of download URL results.
457
+ */
458
+ async downloadAssets(assetIds) {
459
+ if (!assetIds || assetIds.length === 0)
460
+ throw new Error('assetIds array is required and cannot be empty.');
461
+ return client._request({
462
+ method: 'POST',
463
+ endpoint: '/v1/assets/download',
464
+ body: { assetIds },
465
+ sendJWT: true,
466
+ });
467
+ },
468
+ /**
469
+ * Mints a short-lived signed URL for rendering a document inline.
470
+ *
471
+ * Documents keep their `media` PDF in the private bucket, so unlike images
472
+ * and video it cannot be addressed by keyPath through the file CDN. Fetch
473
+ * this per document open; do not cache it past `expires`.
474
+ *
475
+ * @param {string} assetId - The document asset's ID.
476
+ * @returns {Promise<DocumentViewUrlResponse>} Signed URL, expiry and page count.
477
+ */
478
+ async getDocumentViewUrl(assetId) {
479
+ if (!assetId)
480
+ throw new Error('assetId is required.');
481
+ return client._request({
482
+ method: 'GET',
483
+ endpoint: `/v1/assets/${assetId}/document-url`,
484
+ sendJWT: true,
485
+ });
486
+ },
487
+ /**
488
+ * Tags an asset with a specific tag.
489
+ * @param {string} assetId - The ID of the asset to tag.
490
+ * @param {TagAssetData} tagData - Data containing the tag ID.
491
+ * @returns {Promise<AssetResponse>} The updated asset object.
492
+ */
493
+ async tagAsset(assetId, tagData) {
494
+ if (!assetId)
495
+ throw new Error('assetId is required.');
496
+ if (!tagData.tagId)
497
+ throw new Error('tagId is required.');
498
+ return client._request({
499
+ method: 'PUT',
500
+ endpoint: `/v1/assets/${assetId}/tag`,
501
+ body: tagData,
502
+ sendJWT: true,
503
+ });
504
+ },
505
+ /**
506
+ * Untags an asset by removing a specific tag.
507
+ * @param {string} assetId - The ID of the asset to untag.
508
+ * @param {UntagAssetData} untagData - Data containing the tag ID to remove.
509
+ * @returns {Promise<AssetResponse>} The updated asset object.
510
+ */
511
+ async untagAsset(assetId, untagData) {
512
+ if (!assetId)
513
+ throw new Error('assetId is required.');
514
+ if (!untagData.tagId)
515
+ throw new Error('tagId is required.');
516
+ return client._request({
517
+ method: 'PUT',
518
+ endpoint: `/v1/assets/${assetId}/untag`,
519
+ body: untagData,
520
+ sendJWT: true,
521
+ });
522
+ },
523
+ /**
524
+ * Creates a short link for an asset.
525
+ * If a short link already exists for the asset with the same visibility, returns the existing one.
526
+ * @param {string} assetId - The ID of the asset to create a short link for.
527
+ * @param {CreateAssetShortLinkData} [data] - Optional data including visibility context.
528
+ * @returns {Promise<CreateAssetShortLinkResponse>} The short link object and short URL.
529
+ */
530
+ async createShortLink(assetId, data) {
531
+ if (!assetId)
532
+ throw new Error('assetId is required.');
533
+ return client._request({
534
+ method: 'POST',
535
+ endpoint: `/v1/assets/${assetId}/shortlink`,
536
+ params: data,
537
+ sendJWT: true,
538
+ });
539
+ },
540
+ // ========================================================================
541
+ // Public Asset Link Methods
542
+ // ========================================================================
543
+ /**
544
+ * Create a public download link for an asset.
545
+ * @param {string} assetId - The asset ID.
546
+ * @param {Object} data - Link creation data.
547
+ * @param {string} data.projectId - The project ID.
548
+ * @param {number} [data.validity] - Link validity in milliseconds.
549
+ * @returns {Promise<PublicAssetLink>} The created public link.
550
+ */
551
+ async createPublicLink(assetId, data) {
552
+ if (!assetId)
553
+ throw new Error('assetId is required.');
554
+ return client._request({
555
+ method: 'POST',
556
+ endpoint: `/v1/assets/${assetId}/public-links`,
557
+ body: data,
558
+ sendJWT: true,
559
+ });
560
+ },
561
+ /**
562
+ * Get all public download links for an asset.
563
+ * @param {string} assetId - The asset ID.
564
+ * @returns {Promise<{ results: PublicAssetLink[] }>} The public links.
565
+ */
566
+ async getPublicLinks(assetId, options) {
567
+ if (!assetId)
568
+ throw new Error('assetId is required.');
569
+ return client._request({
570
+ method: 'GET',
571
+ endpoint: `/v1/assets/${assetId}/public-links`,
572
+ sendJWT: true,
573
+ bypassCache: options?.bypassCache,
574
+ });
575
+ },
576
+ /**
577
+ * Update a public download link (extend expiration or change status).
578
+ * @param {string} assetId - The asset ID.
579
+ * @param {string} linkId - The link ID.
580
+ * @param {Object} data - Update data.
581
+ * @returns {Promise<PublicAssetLink>} The updated public link.
582
+ */
583
+ async updatePublicLink(assetId, linkId, data) {
584
+ if (!assetId)
585
+ throw new Error('assetId is required.');
586
+ if (!linkId)
587
+ throw new Error('linkId is required.');
588
+ return client._request({
589
+ method: 'PUT',
590
+ endpoint: `/v1/assets/${assetId}/public-links/${linkId}`,
591
+ body: data,
592
+ sendJWT: true,
593
+ });
594
+ },
595
+ /**
596
+ * Disable a public download link.
597
+ * @param {string} assetId - The asset ID.
598
+ * @param {string} linkId - The link ID.
599
+ * @returns {Promise<PublicAssetLink>} The disabled public link.
600
+ */
601
+ async disablePublicLink(assetId, linkId) {
602
+ if (!assetId)
603
+ throw new Error('assetId is required.');
604
+ if (!linkId)
605
+ throw new Error('linkId is required.');
606
+ return client._request({
607
+ method: 'PUT',
608
+ endpoint: `/v1/assets/${assetId}/public-links/${linkId}/disable`,
609
+ sendJWT: true,
610
+ });
611
+ },
612
+ /**
613
+ * Reactivate a disabled/expired public download link.
614
+ * @param {string} assetId - The asset ID.
615
+ * @param {string} linkId - The link ID.
616
+ * @param {Object} [data] - Reactivation data.
617
+ * @param {number} [data.validity] - New validity in milliseconds.
618
+ * @returns {Promise<PublicAssetLink>} The reactivated public link.
619
+ */
620
+ async reactivatePublicLink(assetId, linkId, data) {
621
+ if (!assetId)
622
+ throw new Error('assetId is required.');
623
+ if (!linkId)
624
+ throw new Error('linkId is required.');
625
+ return client._request({
626
+ method: 'PUT',
627
+ endpoint: `/v1/assets/${assetId}/public-links/${linkId}/reactivate`,
628
+ body: data,
629
+ sendJWT: true,
630
+ });
631
+ },
632
+ /**
633
+ * Mint a signed multipart upload URL for a user-supplied custom thumbnail
634
+ * image. The upload lands in the originals bucket tagged so the
635
+ * post-processing Lambda generates the customThumbnail outputs and
636
+ * registers them on the asset via the file-update callback.
637
+ *
638
+ * Caller flow:
639
+ * 1. multipartUpload(file, response.urls, response.key, response.uploadId)
640
+ * 2. completeCustomThumbnailUpload({ assetId, key, uploadId, parts })
641
+ * 3. wait for the assetFileUpdate websocket event
642
+ */
643
+ async getCustomThumbnailUploadUrl(assetId, data) {
644
+ if (!assetId)
645
+ throw new Error('assetId is required.');
646
+ if (!data?.fileName || !data?.mimeType || !data?.sizeInMB) {
647
+ throw new Error('fileName, mimeType, and sizeInMB are required.');
648
+ }
649
+ return client._request({
650
+ method: 'POST',
651
+ endpoint: `/v1/assets/${assetId}/custom-thumbnail/upload-url`,
652
+ body: data,
653
+ sendJWT: true,
654
+ });
655
+ },
656
+ /**
657
+ * Finalize the multipart S3 upload for a custom thumbnail. Triggers the
658
+ * post-processing Lambda by committing the S3 object.
659
+ */
660
+ async completeCustomThumbnailUpload(assetId, data) {
661
+ if (!assetId)
662
+ throw new Error('assetId is required.');
663
+ if (!data?.key || !data?.uploadId || !data?.parts?.length) {
664
+ throw new Error('key, uploadId, and parts are required.');
665
+ }
666
+ return client._request({
667
+ method: 'POST',
668
+ endpoint: `/v1/assets/${assetId}/custom-thumbnail/complete-upload`,
669
+ body: data,
670
+ sendJWT: true,
671
+ });
672
+ },
673
+ /**
674
+ * Remove the custom thumbnail from an asset. Soft-deletes all custom
675
+ * thumb files; the asset falls back to the auto-generated thumbnail.
676
+ */
677
+ async removeCustomThumbnail(assetId) {
678
+ if (!assetId)
679
+ throw new Error('assetId is required.');
680
+ return client._request({
681
+ method: 'DELETE',
682
+ endpoint: `/v1/assets/${assetId}/custom-thumbnail`,
683
+ sendJWT: true,
684
+ });
685
+ },
686
+ /**
687
+ * Promote a chat-message attachment into a project as a fresh,
688
+ * independent project asset. The source attachment is left untouched;
689
+ * the new project asset has its own lifecycle, post-processing
690
+ * pipeline, and storage footprint.
691
+ *
692
+ * Idempotent: a second promote of the same source into the same
693
+ * project returns the existing promoted asset with `deduped: true`.
694
+ *
695
+ * Requires `canCreateAsset` on the destination project — reviewers
696
+ * are blocked. The server additionally rejects when the source
697
+ * attachment's workspace doesn't match the destination project's.
698
+ */
699
+ async promoteAttachmentToProject(assetId, payload) {
700
+ if (!assetId)
701
+ throw new Error('assetId is required.');
702
+ if (!payload?.projectId)
703
+ throw new Error('projectId is required.');
704
+ return client._request({
705
+ method: 'POST',
706
+ endpoint: `/v1/assets/${assetId}/promote-to-project`,
707
+ body: payload,
708
+ sendJWT: true,
709
+ });
710
+ },
711
+ /**
712
+ * Upload Session Management Methods
713
+ */
714
+ /**
715
+ * Get all active upload sessions for a project
716
+ * @param {string} projectId - The project ID to get sessions for
717
+ * @returns {UploadSessionData[]} Array of upload session data
718
+ */
719
+ getUploadSessions(projectId) {
720
+ if (!projectId)
721
+ throw new Error('projectId is required');
722
+ return uploadSessionManager.getProjectSessions(projectId);
723
+ },
724
+ /**
725
+ * True when any upload is genuinely in flight anywhere in the app (across
726
+ * all projects and tabs). Intended for app-level guards — e.g. suppressing
727
+ * an automatic version-update page refresh while bytes are still uploading.
728
+ * Stale (crashed-tab) sessions are ignored via the freshness window.
729
+ * @param {number} [staleMs] - Max age of the last progress update that still
730
+ * counts as active (default: 2 minutes)
731
+ * @returns {boolean}
732
+ */
733
+ hasActiveUploads(staleMs) {
734
+ return uploadSessionManager.hasActiveUploads(staleMs);
735
+ },
736
+ /**
737
+ * Get a specific upload session
738
+ * @param {string} projectId - The project ID
739
+ * @param {string} sessionId - The session ID
740
+ * @returns {UploadSessionData | null} Upload session data or null if not found
741
+ */
742
+ getUploadSession(projectId, sessionId) {
743
+ if (!projectId)
744
+ throw new Error('projectId is required');
745
+ if (!sessionId)
746
+ throw new Error('sessionId is required');
747
+ return uploadSessionManager.getSession(projectId, sessionId);
748
+ },
749
+ /**
750
+ * Remove an upload session
751
+ * @param {string} projectId - The project ID
752
+ * @param {string} sessionId - The session ID
753
+ */
754
+ removeUploadSession(projectId, sessionId) {
755
+ if (!projectId)
756
+ throw new Error('projectId is required');
757
+ if (!sessionId)
758
+ throw new Error('sessionId is required');
759
+ uploadSessionManager.removeSession(projectId, sessionId);
760
+ },
761
+ /**
762
+ * Clean up old upload sessions for a project
763
+ * @param {string} projectId - The project ID
764
+ * @param {number} [olderThanMs] - Remove sessions older than this (default: 24 hours)
765
+ */
766
+ cleanupUploadSessions(projectId, olderThanMs) {
767
+ if (!projectId)
768
+ throw new Error('projectId is required');
769
+ uploadSessionManager.cleanupSessions(projectId, olderThanMs);
770
+ },
771
+ /**
772
+ * Register a listener for cross-tab upload session messages
773
+ * @param {string} listenerId - Unique listener ID
774
+ * @param {function} callback - Callback function to handle messages
775
+ */
776
+ onUploadSessionMessage(listenerId, callback) {
777
+ if (!listenerId)
778
+ throw new Error('listenerId is required');
779
+ if (typeof callback !== 'function')
780
+ throw new Error('callback must be a function');
781
+ uploadSessionManager.onMessage(listenerId, callback);
782
+ },
783
+ /**
784
+ * Unregister a cross-tab upload session message listener
785
+ * @param {string} listenerId - Unique listener ID
786
+ */
787
+ offUploadSessionMessage(listenerId) {
788
+ if (!listenerId)
789
+ throw new Error('listenerId is required');
790
+ uploadSessionManager.offMessage(listenerId);
791
+ },
792
+ /**
793
+ * Mark an upload session as completed
794
+ * @param {string} projectId - The project ID
795
+ * @param {string} sessionId - The session ID
796
+ */
797
+ completeUploadSession(projectId, sessionId) {
798
+ if (!projectId)
799
+ throw new Error('projectId is required');
800
+ if (!sessionId)
801
+ throw new Error('sessionId is required');
802
+ uploadSessionManager.updateStatus(projectId, sessionId, 'completed');
803
+ },
804
+ // ========================================================================
805
+ // Access Activity Methods (play / download / embed metrics)
806
+ // ========================================================================
807
+ /**
808
+ * Record an authenticated play event from the in-app player. Fire-and-forget;
809
+ * server returns 204. Throw-on-failure is fine because the caller already
810
+ * de-dupes per session.
811
+ */
812
+ async recordAccessActivity(assetId, body) {
813
+ if (!assetId)
814
+ throw new Error('assetId is required.');
815
+ await client._request({
816
+ method: 'POST',
817
+ endpoint: `/v1/assets/${assetId}/access-activity`,
818
+ body,
819
+ sendJWT: true,
820
+ });
821
+ },
822
+ /**
823
+ * Get aggregated access-activity for a single asset. Returns totals per
824
+ * eventType, a breakdown for the requested dimension, and a zero-filled
825
+ * daily series.
826
+ */
827
+ async getAssetAccessActivity(assetId, params) {
828
+ if (!assetId)
829
+ throw new Error('assetId is required.');
830
+ const query = new URLSearchParams();
831
+ if (params?.range)
832
+ query.set('range', params.range);
833
+ if (params?.groupBy)
834
+ query.set('groupBy', params.groupBy);
835
+ if (params?.eventType)
836
+ query.set('eventType', params.eventType);
837
+ if (params?.limit != null)
838
+ query.set('limit', String(params.limit));
839
+ const qs = query.toString();
840
+ return client._request({
841
+ method: 'GET',
842
+ endpoint: `/v1/assets/${assetId}/access-activity${qs ? `?${qs}` : ''}`,
843
+ sendJWT: true,
844
+ });
845
+ },
846
+ };
847
+ }
848
+ //# sourceMappingURL=asset.js.map