@doist/comms-sdk 2.0.2 → 3.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.
- package/dist/esm/utils/url-helpers.js +104 -16
- package/dist/types/utils/url-helpers.d.ts +41 -0
- package/package.json +5 -10
- package/dist/cjs/authentication.js +0 -256
- package/dist/cjs/clients/add-comment-helper.js +0 -43
- package/dist/cjs/clients/attachments-client.js +0 -71
- package/dist/cjs/clients/base-client.js +0 -33
- package/dist/cjs/clients/channels-client.js +0 -211
- package/dist/cjs/clients/comments-client.js +0 -175
- package/dist/cjs/clients/conversation-messages-client.js +0 -169
- package/dist/cjs/clients/conversations-client.js +0 -283
- package/dist/cjs/clients/groups-client.js +0 -172
- package/dist/cjs/clients/hooks-client.js +0 -52
- package/dist/cjs/clients/inbox-client.js +0 -184
- package/dist/cjs/clients/notify-audience.js +0 -45
- package/dist/cjs/clients/reactions-client.js +0 -97
- package/dist/cjs/clients/search-client.js +0 -138
- package/dist/cjs/clients/threads-client.js +0 -364
- package/dist/cjs/clients/users-client.js +0 -326
- package/dist/cjs/clients/workspace-users-client.js +0 -252
- package/dist/cjs/clients/workspaces-client.js +0 -174
- package/dist/cjs/comms-api.js +0 -70
- package/dist/cjs/consts/endpoints.js +0 -34
- package/dist/cjs/index.js +0 -53
- package/dist/cjs/migration.js +0 -96
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/testUtils/msw-handlers.js +0 -51
- package/dist/cjs/testUtils/msw-setup.js +0 -35
- package/dist/cjs/testUtils/obsidian-fetch-adapter.js +0 -53
- package/dist/cjs/testUtils/test-defaults.js +0 -106
- package/dist/cjs/transport/fetch-with-retry.js +0 -146
- package/dist/cjs/transport/http-client.js +0 -56
- package/dist/cjs/transport/http-dispatcher.js +0 -190
- package/dist/cjs/types/api-version.js +0 -8
- package/dist/cjs/types/entities.js +0 -496
- package/dist/cjs/types/enums.js +0 -37
- package/dist/cjs/types/errors.js +0 -12
- package/dist/cjs/types/http.js +0 -4
- package/dist/cjs/types/index.js +0 -22
- package/dist/cjs/types/requests.js +0 -183
- package/dist/cjs/utils/case-conversion.js +0 -54
- package/dist/cjs/utils/index.js +0 -19
- package/dist/cjs/utils/multipart-upload.js +0 -202
- package/dist/cjs/utils/timestamp-conversion.js +0 -58
- package/dist/cjs/utils/url-helpers.js +0 -133
- package/dist/cjs/utils/uuidv7.js +0 -182
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.THREAD_ACTIONS = exports.UnsubscribeHookArgsSchema = exports.SubscribeHookArgsSchema = exports.SubscribeMessageHookArgsSchema = exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema = exports.HookTargetUrlSchema = exports.HookEventSchema = exports.MessageHookEventSchema = exports.NonMessageHookEventSchema = exports.HOOK_EVENTS = exports.MESSAGE_HOOK_EVENTS = exports.NON_MESSAGE_HOOK_EVENTS = exports.ARCHIVE_FILTER_VALUES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const entities_1 = require("./entities");
|
|
6
|
-
const enums_1 = require("./enums");
|
|
7
|
-
/**
|
|
8
|
-
* Create-side requests require an `id` on the wire. The SDK clients accept
|
|
9
|
-
* `id` as optional and auto-generate one via
|
|
10
|
-
* {@link import('../utils/uuidv7').generateId} when the caller doesn't pass
|
|
11
|
-
* one — callers can still mint their own ID locally (e.g. for optimistic
|
|
12
|
-
* UI) and have it stick.
|
|
13
|
-
*/
|
|
14
|
-
exports.CreateChannelArgsSchema = zod_1.z.object({
|
|
15
|
-
workspaceId: zod_1.z.number(),
|
|
16
|
-
name: zod_1.z.string(),
|
|
17
|
-
id: zod_1.z.string().optional(),
|
|
18
|
-
userIds: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
19
|
-
color: zod_1.z.number().nullable().optional(),
|
|
20
|
-
public: zod_1.z.boolean().nullable().optional(),
|
|
21
|
-
description: zod_1.z.string().nullable().optional(),
|
|
22
|
-
defaultGroups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
23
|
-
defaultRecipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
24
|
-
isFavorited: zod_1.z.boolean().nullable().optional(),
|
|
25
|
-
icon: zod_1.z.number().nullable().optional(),
|
|
26
|
-
});
|
|
27
|
-
exports.UpdateChannelArgsSchema = zod_1.z.object({
|
|
28
|
-
id: zod_1.z.string(),
|
|
29
|
-
name: zod_1.z.string(),
|
|
30
|
-
color: zod_1.z.number().nullable().optional(),
|
|
31
|
-
public: zod_1.z.boolean().nullable().optional(),
|
|
32
|
-
description: zod_1.z.string().nullable().optional(),
|
|
33
|
-
defaultGroups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
34
|
-
defaultRecipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
35
|
-
isFavorited: zod_1.z.boolean().nullable().optional(),
|
|
36
|
-
icon: zod_1.z.number().nullable().optional(),
|
|
37
|
-
});
|
|
38
|
-
exports.CreateThreadArgsSchema = zod_1.z.object({
|
|
39
|
-
channelId: zod_1.z.string(),
|
|
40
|
-
content: zod_1.z.string(),
|
|
41
|
-
title: zod_1.z.string().nullable().optional(),
|
|
42
|
-
id: zod_1.z.string().optional(),
|
|
43
|
-
recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
44
|
-
groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
45
|
-
notifyAudience: zod_1.z.enum(enums_1.NOTIFY_AUDIENCES).nullable().optional(),
|
|
46
|
-
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
47
|
-
});
|
|
48
|
-
exports.UpdateThreadArgsSchema = zod_1.z.object({
|
|
49
|
-
id: zod_1.z.string(),
|
|
50
|
-
title: zod_1.z.string().nullable().optional(),
|
|
51
|
-
content: zod_1.z.string().nullable().optional(),
|
|
52
|
-
});
|
|
53
|
-
exports.CreateCommentArgsSchema = zod_1.z.object({
|
|
54
|
-
threadId: zod_1.z.string(),
|
|
55
|
-
content: zod_1.z.string(),
|
|
56
|
-
id: zod_1.z.string().optional(),
|
|
57
|
-
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
58
|
-
actions: zod_1.z.unknown().nullable().optional(),
|
|
59
|
-
recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
60
|
-
groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
61
|
-
directMentions: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
62
|
-
directGroupMentions: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
63
|
-
notifyAudience: zod_1.z.enum(enums_1.NOTIFY_AUDIENCES).nullable().optional(),
|
|
64
|
-
});
|
|
65
|
-
exports.UpdateCommentArgsSchema = zod_1.z.object({
|
|
66
|
-
id: zod_1.z.string(),
|
|
67
|
-
content: zod_1.z.string(),
|
|
68
|
-
});
|
|
69
|
-
exports.CreateConversationArgsSchema = zod_1.z.object({
|
|
70
|
-
workspaceId: zod_1.z.number(),
|
|
71
|
-
recipients: zod_1.z.array(zod_1.z.number()),
|
|
72
|
-
id: zod_1.z.string().optional(),
|
|
73
|
-
title: zod_1.z.string().nullable().optional(),
|
|
74
|
-
});
|
|
75
|
-
exports.CreateMessageArgsSchema = zod_1.z
|
|
76
|
-
.object({
|
|
77
|
-
conversationId: zod_1.z.string().nullable().optional(),
|
|
78
|
-
threadId: zod_1.z.string().nullable().optional(),
|
|
79
|
-
content: zod_1.z.string(),
|
|
80
|
-
id: zod_1.z.string().optional(),
|
|
81
|
-
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
82
|
-
})
|
|
83
|
-
.refine((data) => {
|
|
84
|
-
return ((data.conversationId && !data.threadId) || (!data.conversationId && data.threadId));
|
|
85
|
-
}, {
|
|
86
|
-
message: 'Exactly one of conversationId or threadId must be provided',
|
|
87
|
-
});
|
|
88
|
-
exports.GetChannelsArgsSchema = zod_1.z.object({
|
|
89
|
-
workspaceId: zod_1.z.number(),
|
|
90
|
-
archived: zod_1.z.boolean().nullable().optional(),
|
|
91
|
-
});
|
|
92
|
-
exports.GetThreadsArgsSchema = zod_1.z.object({
|
|
93
|
-
workspaceId: zod_1.z.number(),
|
|
94
|
-
channelId: zod_1.z.string().nullable().optional(),
|
|
95
|
-
archived: zod_1.z.boolean().nullable().optional(),
|
|
96
|
-
newerThan: zod_1.z.date().nullable().optional(),
|
|
97
|
-
olderThan: zod_1.z.date().nullable().optional(),
|
|
98
|
-
limit: zod_1.z.number().nullable().optional(),
|
|
99
|
-
});
|
|
100
|
-
exports.GetCommentsArgsSchema = zod_1.z.object({
|
|
101
|
-
threadId: zod_1.z.string(),
|
|
102
|
-
newerThan: zod_1.z.date().nullable().optional(),
|
|
103
|
-
olderThan: zod_1.z.date().nullable().optional(),
|
|
104
|
-
limit: zod_1.z.number().nullable().optional(),
|
|
105
|
-
});
|
|
106
|
-
exports.GetConversationsArgsSchema = zod_1.z.object({
|
|
107
|
-
workspaceId: zod_1.z.number(),
|
|
108
|
-
archived: zod_1.z.boolean().nullable().optional(),
|
|
109
|
-
olderThan: zod_1.z.date().nullable().optional(),
|
|
110
|
-
beforeId: zod_1.z.string().nullable().optional(),
|
|
111
|
-
limit: zod_1.z.number().nullable().optional(),
|
|
112
|
-
});
|
|
113
|
-
exports.GetOrCreateConversationArgsSchema = zod_1.z.object({
|
|
114
|
-
workspaceId: zod_1.z.number(),
|
|
115
|
-
userIds: zod_1.z.array(zod_1.z.number()),
|
|
116
|
-
id: zod_1.z.string().optional(),
|
|
117
|
-
});
|
|
118
|
-
// Inbox
|
|
119
|
-
exports.ARCHIVE_FILTER_VALUES = ['active', 'archived', 'all'];
|
|
120
|
-
// Hooks
|
|
121
|
-
exports.NON_MESSAGE_HOOK_EVENTS = [
|
|
122
|
-
'workspace_added',
|
|
123
|
-
'workspace_updated',
|
|
124
|
-
'workspace_deleted',
|
|
125
|
-
'workspace_user_added',
|
|
126
|
-
'workspace_user_updated',
|
|
127
|
-
'workspace_user_removed',
|
|
128
|
-
'channel_added',
|
|
129
|
-
'channel_updated',
|
|
130
|
-
'channel_deleted',
|
|
131
|
-
'channel_user_added',
|
|
132
|
-
'channel_user_removed',
|
|
133
|
-
'thread_added',
|
|
134
|
-
'thread_updated',
|
|
135
|
-
'thread_deleted',
|
|
136
|
-
'comment_added',
|
|
137
|
-
'comment_updated',
|
|
138
|
-
'comment_deleted',
|
|
139
|
-
'group_added',
|
|
140
|
-
'group_updated',
|
|
141
|
-
'group_deleted',
|
|
142
|
-
'group_user_added',
|
|
143
|
-
'group_user_removed',
|
|
144
|
-
];
|
|
145
|
-
exports.MESSAGE_HOOK_EVENTS = ['message_added', 'message_updated'];
|
|
146
|
-
exports.HOOK_EVENTS = [...exports.NON_MESSAGE_HOOK_EVENTS, ...exports.MESSAGE_HOOK_EVENTS];
|
|
147
|
-
exports.NonMessageHookEventSchema = zod_1.z.enum(exports.NON_MESSAGE_HOOK_EVENTS);
|
|
148
|
-
exports.MessageHookEventSchema = zod_1.z.enum(exports.MESSAGE_HOOK_EVENTS);
|
|
149
|
-
exports.HookEventSchema = zod_1.z.enum(exports.HOOK_EVENTS);
|
|
150
|
-
exports.HookTargetUrlSchema = zod_1.z
|
|
151
|
-
.url()
|
|
152
|
-
.max(150)
|
|
153
|
-
.refine((value) => {
|
|
154
|
-
try {
|
|
155
|
-
return new URL(value).protocol === 'https:';
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
}, { message: 'URL must use HTTPS' });
|
|
161
|
-
exports.SubscribeHookArgsBaseSchema = zod_1.z.object({
|
|
162
|
-
targetUrl: exports.HookTargetUrlSchema,
|
|
163
|
-
workspaceId: zod_1.z.number().nullable().optional(),
|
|
164
|
-
channelId: zod_1.z.string().nullable().optional(),
|
|
165
|
-
threadId: zod_1.z.string().nullable().optional(),
|
|
166
|
-
});
|
|
167
|
-
exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
|
|
168
|
-
event: exports.NonMessageHookEventSchema,
|
|
169
|
-
conversationId: zod_1.z.never().optional(),
|
|
170
|
-
});
|
|
171
|
-
exports.SubscribeMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
|
|
172
|
-
event: exports.MessageHookEventSchema,
|
|
173
|
-
conversationId: zod_1.z.string().nullable().optional(),
|
|
174
|
-
});
|
|
175
|
-
exports.SubscribeHookArgsSchema = zod_1.z.union([
|
|
176
|
-
exports.SubscribeNonMessageHookArgsSchema,
|
|
177
|
-
exports.SubscribeMessageHookArgsSchema,
|
|
178
|
-
]);
|
|
179
|
-
exports.UnsubscribeHookArgsSchema = zod_1.z.object({
|
|
180
|
-
targetUrl: exports.HookTargetUrlSchema,
|
|
181
|
-
});
|
|
182
|
-
// Threads
|
|
183
|
-
exports.THREAD_ACTIONS = ['close', 'reopen'];
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.camelCaseKeys = camelCaseKeys;
|
|
7
|
-
exports.snakeCaseKeys = snakeCaseKeys;
|
|
8
|
-
const camelcase_1 = __importDefault(require("camelcase"));
|
|
9
|
-
function isObject(value) {
|
|
10
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
|
-
function isArray(value) {
|
|
13
|
-
return Array.isArray(value);
|
|
14
|
-
}
|
|
15
|
-
function toSnakeCase(str) {
|
|
16
|
-
return str
|
|
17
|
-
.replace(/([A-Z])/g, '_$1')
|
|
18
|
-
.toLowerCase()
|
|
19
|
-
.replace(/^_/, '');
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* @internal
|
|
23
|
-
*/
|
|
24
|
-
function camelCaseKeys(obj) {
|
|
25
|
-
if (isArray(obj)) {
|
|
26
|
-
return obj.map(camelCaseKeys);
|
|
27
|
-
}
|
|
28
|
-
if (isObject(obj)) {
|
|
29
|
-
const result = {};
|
|
30
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
31
|
-
const camelKey = (0, camelcase_1.default)(key);
|
|
32
|
-
result[camelKey] = camelCaseKeys(value);
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
return obj;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* @internal
|
|
40
|
-
*/
|
|
41
|
-
function snakeCaseKeys(obj) {
|
|
42
|
-
if (isArray(obj)) {
|
|
43
|
-
return obj.map(snakeCaseKeys);
|
|
44
|
-
}
|
|
45
|
-
if (isObject(obj)) {
|
|
46
|
-
const result = {};
|
|
47
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
48
|
-
const snakeKey = toSnakeCase(key);
|
|
49
|
-
result[snakeKey] = snakeCaseKeys(value);
|
|
50
|
-
}
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
return obj;
|
|
54
|
-
}
|
package/dist/cjs/utils/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./case-conversion"), exports);
|
|
18
|
-
__exportStar(require("./url-helpers"), exports);
|
|
19
|
-
__exportStar(require("./uuidv7"), exports);
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getContentTypeFromFileName = getContentTypeFromFileName;
|
|
4
|
-
exports.uploadMultipartFile = uploadMultipartFile;
|
|
5
|
-
const uuid_1 = require("uuid");
|
|
6
|
-
const fetch_with_retry_1 = require("../transport/fetch-with-retry");
|
|
7
|
-
/**
|
|
8
|
-
* Determine a content-type from a filename extension. Falls back to
|
|
9
|
-
* `application/octet-stream` for unknown extensions.
|
|
10
|
-
*/
|
|
11
|
-
function getContentTypeFromFileName(fileName) {
|
|
12
|
-
const extension = fileName.toLowerCase().split('.').pop();
|
|
13
|
-
return extension ? (MIME_BY_EXTENSION[extension] ?? DEFAULT_MIME_TYPE) : DEFAULT_MIME_TYPE;
|
|
14
|
-
}
|
|
15
|
-
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
|
16
|
-
/**
|
|
17
|
-
* Extension → MIME map covering the formats commonly attached to messages.
|
|
18
|
-
* Anything not listed falls back to {@link DEFAULT_MIME_TYPE}; callers can
|
|
19
|
-
* always override via the `contentType` argument.
|
|
20
|
-
*/
|
|
21
|
-
const MIME_BY_EXTENSION = {
|
|
22
|
-
// Images
|
|
23
|
-
png: 'image/png',
|
|
24
|
-
jpg: 'image/jpeg',
|
|
25
|
-
jpeg: 'image/jpeg',
|
|
26
|
-
gif: 'image/gif',
|
|
27
|
-
webp: 'image/webp',
|
|
28
|
-
svg: 'image/svg+xml',
|
|
29
|
-
bmp: 'image/bmp',
|
|
30
|
-
ico: 'image/x-icon',
|
|
31
|
-
tif: 'image/tiff',
|
|
32
|
-
tiff: 'image/tiff',
|
|
33
|
-
heic: 'image/heic',
|
|
34
|
-
heif: 'image/heif',
|
|
35
|
-
avif: 'image/avif',
|
|
36
|
-
// Documents / text
|
|
37
|
-
pdf: 'application/pdf',
|
|
38
|
-
txt: 'text/plain',
|
|
39
|
-
md: 'text/markdown',
|
|
40
|
-
csv: 'text/csv',
|
|
41
|
-
json: 'application/json',
|
|
42
|
-
xml: 'application/xml',
|
|
43
|
-
html: 'text/html',
|
|
44
|
-
htm: 'text/html',
|
|
45
|
-
rtf: 'application/rtf',
|
|
46
|
-
doc: 'application/msword',
|
|
47
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
48
|
-
xls: 'application/vnd.ms-excel',
|
|
49
|
-
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
50
|
-
ppt: 'application/vnd.ms-powerpoint',
|
|
51
|
-
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
52
|
-
// Archives
|
|
53
|
-
zip: 'application/zip',
|
|
54
|
-
gz: 'application/gzip',
|
|
55
|
-
tar: 'application/x-tar',
|
|
56
|
-
'7z': 'application/x-7z-compressed',
|
|
57
|
-
rar: 'application/vnd.rar',
|
|
58
|
-
// Audio
|
|
59
|
-
mp3: 'audio/mpeg',
|
|
60
|
-
wav: 'audio/wav',
|
|
61
|
-
ogg: 'audio/ogg',
|
|
62
|
-
m4a: 'audio/mp4',
|
|
63
|
-
flac: 'audio/flac',
|
|
64
|
-
aac: 'audio/aac',
|
|
65
|
-
// Video
|
|
66
|
-
mp4: 'video/mp4',
|
|
67
|
-
m4v: 'video/mp4',
|
|
68
|
-
mov: 'video/quicktime',
|
|
69
|
-
avi: 'video/x-msvideo',
|
|
70
|
-
webm: 'video/webm',
|
|
71
|
-
mkv: 'video/x-matroska',
|
|
72
|
-
};
|
|
73
|
-
/**
|
|
74
|
-
* Normalise a supported {@link UploadFile} into a `Blob` plus a resolved file name and
|
|
75
|
-
* content type, ready to be encoded into the multipart body.
|
|
76
|
-
*/
|
|
77
|
-
function toBlob(file, fileName, contentType) {
|
|
78
|
-
if (file instanceof Blob) {
|
|
79
|
-
// Duck-type the name rather than `instanceof File`: `File` isn't a global in
|
|
80
|
-
// Node 18, so a `node:buffer` `File` (which carries a real `name`) would
|
|
81
|
-
// otherwise fall back to `'upload'` and lose its inferred file name.
|
|
82
|
-
const blobName = file.name;
|
|
83
|
-
const name = fileName || (typeof blobName === 'string' ? blobName : undefined) || 'upload';
|
|
84
|
-
const type = contentType || file.type || getContentTypeFromFileName(name);
|
|
85
|
-
// Re-wrap only when stamping a type the Blob doesn't already carry.
|
|
86
|
-
const blob = file.type === type ? file : new Blob([file], { type });
|
|
87
|
-
return { blob, fileName: name, contentType: type };
|
|
88
|
-
}
|
|
89
|
-
if (file instanceof Uint8Array) {
|
|
90
|
-
if (!fileName) {
|
|
91
|
-
throw new Error('fileName is required when uploading raw bytes');
|
|
92
|
-
}
|
|
93
|
-
const type = contentType || getContentTypeFromFileName(fileName);
|
|
94
|
-
// `Blob` accepts any `ArrayBufferView`; the cast satisfies the stricter lib
|
|
95
|
-
// `BlobPart` type (which pins the backing buffer to `ArrayBuffer`).
|
|
96
|
-
return { blob: new Blob([file], { type }), fileName, contentType: type };
|
|
97
|
-
}
|
|
98
|
-
throw new Error('Unsupported file type for upload: expected a Blob or Uint8Array');
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Escapes a value for use inside a quoted `Content-Disposition` parameter, where a raw
|
|
102
|
-
* quote would end the value early and a raw CR or LF would end the header. A lone CR
|
|
103
|
-
* needs escaping too, not just CRLF pairs.
|
|
104
|
-
*
|
|
105
|
-
* This is the same escaping the platform's own `FormData` encoder applies, so servers
|
|
106
|
-
* see exactly what they would from any browser.
|
|
107
|
-
*/
|
|
108
|
-
function escapeDispositionValue(value) {
|
|
109
|
-
return value.replace(/"/g, '%22').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Rejects a media type that could not be safely written into a part header.
|
|
113
|
-
*
|
|
114
|
-
* `contentType` is public caller input and reaches the header verbatim. A value
|
|
115
|
-
* carrying a CR or LF would end the header early and let the caller forge one
|
|
116
|
-
* of its own — `Blob` normalises the `type` it stores, but the supplied value
|
|
117
|
-
* is what gets interpolated. Reject rather than silently rewrite, so a caller
|
|
118
|
-
* passing something unusable finds out instead of having it altered.
|
|
119
|
-
*/
|
|
120
|
-
function assertHeaderSafeContentType(contentType) {
|
|
121
|
-
if (!/^[\x20-\x7e]*$/.test(contentType)) {
|
|
122
|
-
throw new Error('contentType must contain only printable ASCII characters, without CR or LF');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Encodes a `multipart/form-data` body as a `Blob`, together with the `Content-Type`
|
|
127
|
-
* that describes it.
|
|
128
|
-
*
|
|
129
|
-
* Deliberately avoids `FormData`. A `FormData` body is only encoded by the `fetch` that
|
|
130
|
-
* owns that `FormData` class — undici brands its own and checks it with a plain
|
|
131
|
-
* `instanceof` — and the SDK dispatches through undici's own `fetch` to keep the request
|
|
132
|
-
* client on the same undici as the dispatcher. A global `FormData` handed to it is
|
|
133
|
-
* stringified to the literal `"[object FormData]"`, so the upload silently carries no
|
|
134
|
-
* file. `Blob` has no such brand and works with whichever `fetch` sends the request,
|
|
135
|
-
* including a caller-supplied `customFetch`.
|
|
136
|
-
*/
|
|
137
|
-
function buildMultipartBody(args) {
|
|
138
|
-
const { blob, fileName, contentType, fields } = args;
|
|
139
|
-
assertHeaderSafeContentType(contentType);
|
|
140
|
-
const boundary = `----comms-sdk-${(0, uuid_1.v4)()}`;
|
|
141
|
-
const parts = [
|
|
142
|
-
`--${boundary}\r\n` +
|
|
143
|
-
`Content-Disposition: form-data; name="file"; filename="${escapeDispositionValue(fileName)}"\r\n` +
|
|
144
|
-
`Content-Type: ${contentType}\r\n\r\n`,
|
|
145
|
-
blob,
|
|
146
|
-
'\r\n',
|
|
147
|
-
];
|
|
148
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
149
|
-
if (value !== undefined && value !== null) {
|
|
150
|
-
parts.push(`--${boundary}\r\n` +
|
|
151
|
-
`Content-Disposition: form-data; name="${escapeDispositionValue(key)}"\r\n\r\n` +
|
|
152
|
-
`${String(value)}\r\n`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
parts.push(`--${boundary}--\r\n`);
|
|
156
|
-
return { body: new Blob(parts), contentType: `multipart/form-data; boundary=${boundary}` };
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Upload a file using `multipart/form-data`.
|
|
160
|
-
*
|
|
161
|
-
* Encodes the request body as a `Blob` so it works unchanged in the browser and in
|
|
162
|
-
* Node.js, whichever `fetch` ends up sending it. The `file` part is sent alongside `file_name`,
|
|
163
|
-
* `file_size`, and `underlying_type` fields (the canonical Comms upload shape); any
|
|
164
|
-
* `additionalFields` are merged in and override the derived values. Authentication uses
|
|
165
|
-
* `Authorization: Bearer`, matching every other Comms SDK client.
|
|
166
|
-
*
|
|
167
|
-
* The response is JSON-parsed and camel-cased by {@link fetchWithRetry}; callers validate
|
|
168
|
-
* the returned shape with the appropriate schema.
|
|
169
|
-
*/
|
|
170
|
-
async function uploadMultipartFile(args) {
|
|
171
|
-
const { baseUrl, authToken, endpoint, file, fileName, contentType, additionalFields, maxRetries = 0, requestId, customFetch, } = args;
|
|
172
|
-
const { blob, fileName: resolvedFileName, contentType: resolvedType, } = toBlob(file, fileName, contentType);
|
|
173
|
-
const fields = {
|
|
174
|
-
file_name: resolvedFileName,
|
|
175
|
-
file_size: blob.size,
|
|
176
|
-
underlying_type: resolvedType,
|
|
177
|
-
...additionalFields,
|
|
178
|
-
};
|
|
179
|
-
const multipart = buildMultipartBody({
|
|
180
|
-
blob,
|
|
181
|
-
fileName: resolvedFileName,
|
|
182
|
-
contentType: resolvedType,
|
|
183
|
-
fields,
|
|
184
|
-
});
|
|
185
|
-
const headers = {
|
|
186
|
-
Authorization: `Bearer ${authToken}`,
|
|
187
|
-
// The boundary is ours, so this has to be declared explicitly — `fetch`
|
|
188
|
-
// only fills it in for a `FormData` body.
|
|
189
|
-
'Content-Type': multipart.contentType,
|
|
190
|
-
};
|
|
191
|
-
if (requestId) {
|
|
192
|
-
headers['X-Request-Id'] = requestId;
|
|
193
|
-
}
|
|
194
|
-
const url = new URL(endpoint, baseUrl).toString();
|
|
195
|
-
const response = await (0, fetch_with_retry_1.fetchWithRetry)(url, {
|
|
196
|
-
method: 'POST',
|
|
197
|
-
headers,
|
|
198
|
-
body: multipart.body,
|
|
199
|
-
timeout: 30000,
|
|
200
|
-
}, maxRetries, customFetch);
|
|
201
|
-
return response.data;
|
|
202
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.timestampToDate = timestampToDate;
|
|
4
|
-
exports.transformTimestamps = transformTimestamps;
|
|
5
|
-
/**
|
|
6
|
-
* Converts a Unix timestamp (in seconds) to a Date object.
|
|
7
|
-
* @param timestamp - Unix timestamp in seconds
|
|
8
|
-
* @returns Date object
|
|
9
|
-
*/
|
|
10
|
-
function timestampToDate(timestamp) {
|
|
11
|
-
return new Date(timestamp * 1000);
|
|
12
|
-
}
|
|
13
|
-
const timestampFieldTargetOverrides = new Map([['pinnedTs', 'pinnedDate']]);
|
|
14
|
-
/**
|
|
15
|
-
* Recursively transforms timestamp fields (ending in 'Ts') in an object to Date objects.
|
|
16
|
-
* Also renames converted fields by removing the 'Ts' suffix.
|
|
17
|
-
* @param obj - The object to transform
|
|
18
|
-
* @returns The transformed object with Date fields
|
|
19
|
-
*/
|
|
20
|
-
function transformTimestamps(obj) {
|
|
21
|
-
if (obj === null || obj === undefined) {
|
|
22
|
-
return obj;
|
|
23
|
-
}
|
|
24
|
-
if (Array.isArray(obj)) {
|
|
25
|
-
return obj.map((item) => transformTimestamps(item));
|
|
26
|
-
}
|
|
27
|
-
if (typeof obj === 'object') {
|
|
28
|
-
const result = {};
|
|
29
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
30
|
-
// Check if the key ends with 'Ts'
|
|
31
|
-
if (key.endsWith('Ts')) {
|
|
32
|
-
const overriddenTargetKey = timestampFieldTargetOverrides.get(key);
|
|
33
|
-
if (typeof value === 'number') {
|
|
34
|
-
// Remove 'Ts' suffix and convert to Date
|
|
35
|
-
const newKey = key.slice(0, -2);
|
|
36
|
-
// If the base key already exists in the original object, use *Date suffix
|
|
37
|
-
// to avoid overwriting it (e.g. posted + postedTs → posted + postedDate)
|
|
38
|
-
const targetKey = overriddenTargetKey ??
|
|
39
|
-
(newKey in obj ? `${newKey}Date` : newKey);
|
|
40
|
-
result[targetKey] = timestampToDate(value);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (overriddenTargetKey) {
|
|
44
|
-
result[overriddenTargetKey] = value;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (typeof value === 'object' && value !== null) {
|
|
49
|
-
// Recursively transform nested objects
|
|
50
|
-
result[key] = transformTimestamps(value);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
result[key] = value;
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
return obj;
|
|
58
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getCommsURL = getCommsURL;
|
|
7
|
-
exports.getFullCommsURL = getFullCommsURL;
|
|
8
|
-
exports.getThreadURL = getThreadURL;
|
|
9
|
-
exports.getChannelURL = getChannelURL;
|
|
10
|
-
exports.getConversationURL = getConversationURL;
|
|
11
|
-
exports.getMessageURL = getMessageURL;
|
|
12
|
-
exports.getCommentURL = getCommentURL;
|
|
13
|
-
exports.getThreadsRootURL = getThreadsRootURL;
|
|
14
|
-
exports.getInboxURL = getInboxURL;
|
|
15
|
-
exports.getMessagesRootURL = getMessagesRootURL;
|
|
16
|
-
exports.getUserProfileURL = getUserProfileURL;
|
|
17
|
-
exports.getSavedThreadsRootURL = getSavedThreadsRootURL;
|
|
18
|
-
exports.getSavedThreadURL = getSavedThreadURL;
|
|
19
|
-
exports.getSearchRootURL = getSearchRootURL;
|
|
20
|
-
exports.getSearchQueryURL = getSearchQueryURL;
|
|
21
|
-
exports.getSettingsURL = getSettingsURL;
|
|
22
|
-
exports.getTeamMembersRootURL = getTeamMembersRootURL;
|
|
23
|
-
const COMMS_BASE_URL = 'https://comms.todoist.com';
|
|
24
|
-
/**
|
|
25
|
-
* Builds a relative Comms URL based on the provided parameters
|
|
26
|
-
* @param params - URL parameters including workspace, channel, conversation, thread, etc.
|
|
27
|
-
* @returns A relative URL path
|
|
28
|
-
* @example
|
|
29
|
-
* getCommsURL({ workspaceId: 1, channelId: '7Yp...', threadId: '7Yq...' })
|
|
30
|
-
* // returns '/1/ch/7Yp.../t/7Yq.../'
|
|
31
|
-
*/
|
|
32
|
-
function getCommsURL(params) {
|
|
33
|
-
const { workspaceId, channelId, conversationId, threadId, commentId, messageId, userId } = params;
|
|
34
|
-
let url = `/${workspaceId}/`;
|
|
35
|
-
if (channelId) {
|
|
36
|
-
url += `ch/${channelId}/`;
|
|
37
|
-
if (threadId) {
|
|
38
|
-
url += `t/${threadId}/`;
|
|
39
|
-
if (commentId) {
|
|
40
|
-
url += `c/${commentId}`;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else if (threadId) {
|
|
45
|
-
url += `inbox/t/${threadId}/`;
|
|
46
|
-
if (commentId) {
|
|
47
|
-
url += `c/${commentId}`;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else if (conversationId) {
|
|
51
|
-
url += `msg/${conversationId}/`;
|
|
52
|
-
if (messageId) {
|
|
53
|
-
url += `m/${messageId}`;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
else if (userId) {
|
|
57
|
-
url += `people/u/${userId}`;
|
|
58
|
-
}
|
|
59
|
-
return url;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Builds a full Comms URL (with protocol and hostname) based on the provided parameters
|
|
63
|
-
* @param params - URL parameters including workspace, channel, conversation, thread, etc.
|
|
64
|
-
* @param baseUrl - Optional base URL (defaults to 'https://comms.todoist.com')
|
|
65
|
-
*/
|
|
66
|
-
function getFullCommsURL(params, baseUrl = COMMS_BASE_URL) {
|
|
67
|
-
// Strip a trailing slash so links don't double up — `getCommsURL` paths start with '/'.
|
|
68
|
-
const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
69
|
-
return `${normalizedBase}${getCommsURL(params)}`;
|
|
70
|
-
}
|
|
71
|
-
/** Returns the URL for a thread in a channel. */
|
|
72
|
-
function getThreadURL(params) {
|
|
73
|
-
return getCommsURL(params);
|
|
74
|
-
}
|
|
75
|
-
/** Returns the URL for a channel. */
|
|
76
|
-
function getChannelURL(params) {
|
|
77
|
-
return getCommsURL(params);
|
|
78
|
-
}
|
|
79
|
-
/** Returns the URL for a conversation. */
|
|
80
|
-
function getConversationURL(params) {
|
|
81
|
-
return getCommsURL(params);
|
|
82
|
-
}
|
|
83
|
-
/** Returns the URL for a specific message in a conversation. */
|
|
84
|
-
function getMessageURL(params) {
|
|
85
|
-
return getCommsURL(params);
|
|
86
|
-
}
|
|
87
|
-
/** Returns the URL for a comment in a thread. */
|
|
88
|
-
function getCommentURL(params) {
|
|
89
|
-
return getCommsURL(params);
|
|
90
|
-
}
|
|
91
|
-
/** Returns the URL for the threads root (channels view). */
|
|
92
|
-
function getThreadsRootURL(workspaceId) {
|
|
93
|
-
return `/${workspaceId}/ch`;
|
|
94
|
-
}
|
|
95
|
-
/** Returns the URL for the inbox. */
|
|
96
|
-
function getInboxURL(workspaceId, tab) {
|
|
97
|
-
const tabParam = tab ? `/${tab}` : '';
|
|
98
|
-
return `/${workspaceId}/inbox${tabParam}`;
|
|
99
|
-
}
|
|
100
|
-
/** Returns the URL for the messages/conversations root. */
|
|
101
|
-
function getMessagesRootURL(workspaceId) {
|
|
102
|
-
return `/${workspaceId}/msg`;
|
|
103
|
-
}
|
|
104
|
-
/** Returns the URL for a user profile. */
|
|
105
|
-
function getUserProfileURL(params) {
|
|
106
|
-
return `/${params.workspaceId}/people/u/${params.userId}`;
|
|
107
|
-
}
|
|
108
|
-
/** Returns the URL for the saved threads view. */
|
|
109
|
-
function getSavedThreadsRootURL(workspaceId) {
|
|
110
|
-
return `/${workspaceId}/saved`;
|
|
111
|
-
}
|
|
112
|
-
/** Returns the URL for a saved thread. */
|
|
113
|
-
function getSavedThreadURL(params) {
|
|
114
|
-
return `/${params.workspaceId}/saved/t/${params.threadId}`;
|
|
115
|
-
}
|
|
116
|
-
/** Returns the URL for the search root. */
|
|
117
|
-
function getSearchRootURL(workspaceId) {
|
|
118
|
-
return `/${workspaceId}/search`;
|
|
119
|
-
}
|
|
120
|
-
/** Returns the URL for a search with a query. */
|
|
121
|
-
function getSearchQueryURL(params) {
|
|
122
|
-
return `/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
|
|
123
|
-
}
|
|
124
|
-
/** Returns the URL for settings. */
|
|
125
|
-
function getSettingsURL(params) {
|
|
126
|
-
return params.initialLocation
|
|
127
|
-
? `/${params.workspaceId}/settings/${params.initialLocation}`
|
|
128
|
-
: `/${params.workspaceId}/settings`;
|
|
129
|
-
}
|
|
130
|
-
/** Returns the URL for the team members root. */
|
|
131
|
-
function getTeamMembersRootURL(workspaceId) {
|
|
132
|
-
return `/${workspaceId}/people/u`;
|
|
133
|
-
}
|