@attrove/sdk 0.1.15 → 0.1.17
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/cjs/admin-client.js +2 -0
- package/cjs/client.js +14 -12
- package/cjs/constants.js +1 -1
- package/cjs/index.js +4 -1
- package/cjs/resources/entities.js +129 -0
- package/cjs/resources/index.js +5 -1
- package/cjs/resources/messages.js +9 -3
- package/cjs/resources/webhooks.js +171 -0
- package/cjs/utils/fetch.js +1 -0
- package/cjs/utils/index.js +4 -1
- package/cjs/utils/webhooks.js +132 -0
- package/esm/admin-client.d.ts +5 -0
- package/esm/admin-client.d.ts.map +1 -1
- package/esm/admin-client.js +2 -0
- package/esm/admin-client.js.map +1 -1
- package/esm/client.d.ts +15 -10
- package/esm/client.d.ts.map +1 -1
- package/esm/client.js +29 -27
- package/esm/client.js.map +1 -1
- package/esm/constants.d.ts +1 -1
- package/esm/constants.js +1 -1
- package/esm/index.d.ts +22 -18
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +9 -8
- package/esm/index.js.map +1 -1
- package/esm/resources/entities.d.ts +101 -0
- package/esm/resources/entities.d.ts.map +1 -0
- package/esm/resources/entities.js +126 -0
- package/esm/resources/entities.js.map +1 -0
- package/esm/resources/index.d.ts +21 -17
- package/esm/resources/index.d.ts.map +1 -1
- package/esm/resources/index.js +12 -10
- package/esm/resources/index.js.map +1 -1
- package/esm/resources/messages.d.ts +2 -2
- package/esm/resources/messages.d.ts.map +1 -1
- package/esm/resources/messages.js +9 -3
- package/esm/resources/messages.js.map +1 -1
- package/esm/resources/webhooks.d.ts +100 -0
- package/esm/resources/webhooks.d.ts.map +1 -0
- package/esm/resources/webhooks.js +168 -0
- package/esm/resources/webhooks.js.map +1 -0
- package/esm/types/index.d.ts +87 -1
- package/esm/types/index.d.ts.map +1 -1
- package/esm/types/index.js.map +1 -1
- package/esm/utils/fetch.d.ts.map +1 -1
- package/esm/utils/fetch.js +1 -0
- package/esm/utils/fetch.js.map +1 -1
- package/esm/utils/index.d.ts +6 -4
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +3 -2
- package/esm/utils/index.js.map +1 -1
- package/esm/utils/webhooks.d.ts +40 -0
- package/esm/utils/webhooks.d.ts.map +1 -0
- package/esm/utils/webhooks.js +127 -0
- package/esm/utils/webhooks.js.map +1 -0
- package/package.json +1 -1
package/cjs/admin-client.js
CHANGED
|
@@ -13,6 +13,7 @@ const index_js_2 = require("./types/index.js");
|
|
|
13
13
|
const index_js_3 = require("./types/index.js");
|
|
14
14
|
const constants_js_1 = require("./constants.js");
|
|
15
15
|
const settings_js_1 = require("./resources/settings.js");
|
|
16
|
+
const webhooks_js_1 = require("./resources/webhooks.js");
|
|
16
17
|
/**
|
|
17
18
|
* Validate the admin client configuration.
|
|
18
19
|
*
|
|
@@ -197,6 +198,7 @@ class AttroveAdmin {
|
|
|
197
198
|
// Initialize resources
|
|
198
199
|
this.users = new AdminUsersResource(this.http, this.config.clientId);
|
|
199
200
|
this.settings = new settings_js_1.AdminSettingsResource(this.http, this.config.clientId);
|
|
201
|
+
this.webhooks = new webhooks_js_1.AdminWebhooksResource(this.http, this.config.clientId);
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
exports.AttroveAdmin = AttroveAdmin;
|
package/cjs/client.js
CHANGED
|
@@ -13,6 +13,7 @@ const users_js_1 = require("./resources/users.js");
|
|
|
13
13
|
const messages_js_1 = require("./resources/messages.js");
|
|
14
14
|
const conversations_js_1 = require("./resources/conversations.js");
|
|
15
15
|
const integrations_js_1 = require("./resources/integrations.js");
|
|
16
|
+
const entities_js_1 = require("./resources/entities.js");
|
|
16
17
|
const calendars_js_1 = require("./resources/calendars.js");
|
|
17
18
|
const events_js_1 = require("./resources/events.js");
|
|
18
19
|
const meetings_js_1 = require("./resources/meetings.js");
|
|
@@ -29,27 +30,27 @@ const constants_js_1 = require("./constants.js");
|
|
|
29
30
|
*/
|
|
30
31
|
function validateConfig(config) {
|
|
31
32
|
// Validate apiKey
|
|
32
|
-
if (!config.apiKey || typeof config.apiKey !==
|
|
33
|
-
throw new index_js_1.ValidationError(
|
|
33
|
+
if (!config.apiKey || typeof config.apiKey !== 'string') {
|
|
34
|
+
throw new index_js_1.ValidationError('apiKey is required and must be a non-empty string', index_js_2.ErrorCodes.VALIDATION_REQUIRED_FIELD, { field: 'apiKey' });
|
|
34
35
|
}
|
|
35
|
-
if (!config.apiKey.startsWith(
|
|
36
|
-
throw new index_js_1.ValidationError('apiKey must start with "sk_" prefix. Make sure you are using a valid API key.', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field:
|
|
36
|
+
if (!config.apiKey.startsWith('sk_')) {
|
|
37
|
+
throw new index_js_1.ValidationError('apiKey must start with "sk_" prefix. Make sure you are using a valid API key.', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: 'apiKey', expected: 'sk_...' });
|
|
37
38
|
}
|
|
38
39
|
// Validate userId
|
|
39
|
-
if (!config.userId || typeof config.userId !==
|
|
40
|
-
throw new index_js_1.ValidationError(
|
|
40
|
+
if (!config.userId || typeof config.userId !== 'string') {
|
|
41
|
+
throw new index_js_1.ValidationError('userId is required and must be a non-empty string', index_js_2.ErrorCodes.VALIDATION_REQUIRED_FIELD, { field: 'userId' });
|
|
41
42
|
}
|
|
42
43
|
if (!(0, index_js_3.isValidUUID)(config.userId)) {
|
|
43
|
-
throw new index_js_1.ValidationError('userId must be a valid UUID format (e.g., "123e4567-e89b-12d3-a456-426614174000")', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field:
|
|
44
|
+
throw new index_js_1.ValidationError('userId must be a valid UUID format (e.g., "123e4567-e89b-12d3-a456-426614174000")', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: 'userId', expected: 'UUID format' });
|
|
44
45
|
}
|
|
45
46
|
// Validate optional fields
|
|
46
47
|
if (config.timeout !== undefined &&
|
|
47
|
-
(typeof config.timeout !==
|
|
48
|
-
throw new index_js_1.ValidationError(
|
|
48
|
+
(typeof config.timeout !== 'number' || config.timeout <= 0)) {
|
|
49
|
+
throw new index_js_1.ValidationError('timeout must be a positive number in milliseconds', index_js_2.ErrorCodes.VALIDATION_OUT_OF_RANGE, { field: 'timeout', value: config.timeout });
|
|
49
50
|
}
|
|
50
51
|
if (config.maxRetries !== undefined &&
|
|
51
|
-
(typeof config.maxRetries !==
|
|
52
|
-
throw new index_js_1.ValidationError(
|
|
52
|
+
(typeof config.maxRetries !== 'number' || config.maxRetries < 0)) {
|
|
53
|
+
throw new index_js_1.ValidationError('maxRetries must be a non-negative number', index_js_2.ErrorCodes.VALIDATION_OUT_OF_RANGE, { field: 'maxRetries', value: config.maxRetries });
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
@@ -118,6 +119,7 @@ class Attrove {
|
|
|
118
119
|
this.messages = new messages_js_1.MessagesResource(this.http, this.config.userId);
|
|
119
120
|
this.conversations = new conversations_js_1.ConversationsResource(this.http, this.config.userId);
|
|
120
121
|
this.integrations = new integrations_js_1.IntegrationsResource(this.http, this.config.userId);
|
|
122
|
+
this.entities = new entities_js_1.EntitiesResource(this.http, this.config.userId);
|
|
121
123
|
this.calendars = new calendars_js_1.CalendarsResource(this.http, this.config.userId);
|
|
122
124
|
this.events = new events_js_1.EventsResource(this.http, this.config.userId);
|
|
123
125
|
this.meetings = new meetings_js_1.MeetingsResource(this.http, this.config.userId);
|
|
@@ -224,7 +226,7 @@ class Attrove {
|
|
|
224
226
|
// Add the user's prompt to history
|
|
225
227
|
const fullHistory = [
|
|
226
228
|
...history,
|
|
227
|
-
{ role:
|
|
229
|
+
{ role: 'user', content: prompt },
|
|
228
230
|
];
|
|
229
231
|
// Extract query options (separate from stream options)
|
|
230
232
|
const queryOptions = {
|
package/cjs/constants.js
CHANGED
|
@@ -13,7 +13,7 @@ exports.getWsCloseReason = exports.WS_CLOSE_CODES = exports.RETRYABLE_STATUS_SET
|
|
|
13
13
|
* Automatically synchronized with package.json during the release process.
|
|
14
14
|
* For programmatic access, use `getVersion()` from './version'.
|
|
15
15
|
*/
|
|
16
|
-
exports.SDK_VERSION = "0.1.
|
|
16
|
+
exports.SDK_VERSION = "0.1.17"; // auto-synced from package.json during release
|
|
17
17
|
/**
|
|
18
18
|
* Default API base URL for Attrove services.
|
|
19
19
|
*/
|
package/cjs/index.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* @packageDocumentation
|
|
34
34
|
*/
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.getVersion = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_TIMEOUT = exports.DEFAULT_BASE_URL = exports.SDK_VERSION = exports.generateMessageId = exports.isServerError = exports.isTimeoutError = exports.isNetworkError = exports.isRateLimitError = exports.isValidationError = exports.isNotFoundError = exports.isAuthorizationError = exports.isAuthenticationError = exports.isAttroveError = exports.ServerError = exports.TimeoutError = exports.NetworkError = exports.RateLimitError = exports.ValidationError = exports.NotFoundError = exports.AuthorizationError = exports.AuthenticationError = exports.AttroveError = exports.AttroveAdmin = exports.Attrove = void 0;
|
|
36
|
+
exports.getVersion = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_TIMEOUT = exports.DEFAULT_BASE_URL = exports.SDK_VERSION = exports.verifyWebhookSignatureDetailed = exports.verifyWebhookSignature = exports.generateMessageId = exports.isServerError = exports.isTimeoutError = exports.isNetworkError = exports.isRateLimitError = exports.isValidationError = exports.isNotFoundError = exports.isAuthorizationError = exports.isAuthenticationError = exports.isAttroveError = exports.ServerError = exports.TimeoutError = exports.NetworkError = exports.RateLimitError = exports.ValidationError = exports.NotFoundError = exports.AuthorizationError = exports.AuthenticationError = exports.AttroveError = exports.AttroveAdmin = exports.Attrove = void 0;
|
|
37
37
|
const tslib_1 = require("tslib");
|
|
38
38
|
const client_js_1 = require("./client.js");
|
|
39
39
|
const admin_client_js_1 = require("./admin-client.js");
|
|
@@ -86,6 +86,9 @@ Object.defineProperty(exports, "isServerError", { enumerable: true, get: functio
|
|
|
86
86
|
// Export streaming utilities
|
|
87
87
|
var streaming_js_1 = require("./utils/streaming.js");
|
|
88
88
|
Object.defineProperty(exports, "generateMessageId", { enumerable: true, get: function () { return streaming_js_1.generateMessageId; } });
|
|
89
|
+
var webhooks_js_1 = require("./utils/webhooks.js");
|
|
90
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhooks_js_1.verifyWebhookSignature; } });
|
|
91
|
+
Object.defineProperty(exports, "verifyWebhookSignatureDetailed", { enumerable: true, get: function () { return webhooks_js_1.verifyWebhookSignatureDetailed; } });
|
|
89
92
|
// Export constants for advanced usage
|
|
90
93
|
var constants_js_1 = require("./constants.js");
|
|
91
94
|
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return constants_js_1.SDK_VERSION; } });
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Entities Resource
|
|
4
|
+
*
|
|
5
|
+
* Provides methods for listing and retrieving contacts (entities)
|
|
6
|
+
* that a user has communicated with across connected integrations.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.EntitiesResource = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* Entities resource for accessing contact data.
|
|
12
|
+
*
|
|
13
|
+
* Provides methods for listing and retrieving contacts (people and bots)
|
|
14
|
+
* that the user has communicated with across connected integrations
|
|
15
|
+
* (Gmail, Slack, Outlook, etc.).
|
|
16
|
+
*/
|
|
17
|
+
class EntitiesResource {
|
|
18
|
+
constructor(http, userId) {
|
|
19
|
+
this.http = http;
|
|
20
|
+
this.userId = userId;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* List contacts with optional filtering.
|
|
24
|
+
*
|
|
25
|
+
* @param options - Filtering and pagination options
|
|
26
|
+
* @returns Paginated list of contacts
|
|
27
|
+
*
|
|
28
|
+
* @throws {AuthenticationError} If the API key is invalid or expired
|
|
29
|
+
* @throws {ValidationError} If the filter parameters are invalid
|
|
30
|
+
* @throws {NetworkError} If unable to reach the API
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // List all contacts
|
|
35
|
+
* const { data, pagination } = await attrove.entities.list();
|
|
36
|
+
*
|
|
37
|
+
* // Search by name or email
|
|
38
|
+
* const { data } = await attrove.entities.list({ search: 'john' });
|
|
39
|
+
*
|
|
40
|
+
* // Only non-bot contacts
|
|
41
|
+
* const { data } = await attrove.entities.list({ isBot: false });
|
|
42
|
+
*
|
|
43
|
+
* // Filter by type
|
|
44
|
+
* const { data } = await attrove.entities.list({ entityType: 'person' });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
async list(options = {}) {
|
|
48
|
+
const params = {};
|
|
49
|
+
if (options.search) {
|
|
50
|
+
params.search = options.search;
|
|
51
|
+
}
|
|
52
|
+
if (options.entityType) {
|
|
53
|
+
params.entity_type = options.entityType;
|
|
54
|
+
}
|
|
55
|
+
if (options.isBot !== undefined) {
|
|
56
|
+
params.is_bot = String(options.isBot);
|
|
57
|
+
}
|
|
58
|
+
if (options.limit !== undefined) {
|
|
59
|
+
params.limit = String(options.limit);
|
|
60
|
+
}
|
|
61
|
+
if (options.offset !== undefined) {
|
|
62
|
+
params.offset = String(options.offset);
|
|
63
|
+
}
|
|
64
|
+
const response = await this.http.request(`/v1/users/${this.userId}/entities`, { method: 'GET' }, params);
|
|
65
|
+
return {
|
|
66
|
+
data: response.data,
|
|
67
|
+
pagination: response.pagination,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get a single contact by ID.
|
|
72
|
+
*
|
|
73
|
+
* @param id - Entity ID (ent_xxx opaque format or raw UUID)
|
|
74
|
+
* @returns The requested contact
|
|
75
|
+
*
|
|
76
|
+
* @throws {AuthenticationError} If the API key is invalid or expired
|
|
77
|
+
* @throws {NotFoundError} If the contact does not exist
|
|
78
|
+
* @throws {NetworkError} If unable to reach the API
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const contact = await attrove.entities.get('ent_5cyYg0:a9KRt4Y');
|
|
83
|
+
* console.log(contact.name, contact.entity_type);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
async get(id) {
|
|
87
|
+
return this.http.get(`/v1/users/${this.userId}/entities/${id}`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* List entity relationships (co-occurrence on messages, meetings, and calendar events).
|
|
91
|
+
*
|
|
92
|
+
* Returns pairs of entities that appear together, useful for
|
|
93
|
+
* building relationship graphs and network visualizations.
|
|
94
|
+
*
|
|
95
|
+
* @param options - Filtering options
|
|
96
|
+
* @returns Paginated list of entity relationship pairs
|
|
97
|
+
*
|
|
98
|
+
* @throws {AuthenticationError} If the API key is invalid or expired
|
|
99
|
+
* @throws {ValidationError} If the filter parameters are invalid
|
|
100
|
+
* @throws {NotFoundError} If the user does not exist
|
|
101
|
+
* @throws {NetworkError} If unable to reach the API
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const { data } = await attrove.entities.relationships();
|
|
106
|
+
* for (const rel of data) {
|
|
107
|
+
* console.log(`${rel.entity_a.name} ↔ ${rel.entity_b.name}: ${rel.co_occurrence_count}`);
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
async relationships(options = {}) {
|
|
112
|
+
const params = {};
|
|
113
|
+
if (options.limit !== undefined) {
|
|
114
|
+
params.limit = String(options.limit);
|
|
115
|
+
}
|
|
116
|
+
if (options.minInteractions !== undefined) {
|
|
117
|
+
params.min_interactions = String(options.minInteractions);
|
|
118
|
+
}
|
|
119
|
+
if (options.includeBots !== undefined) {
|
|
120
|
+
params.include_bots = String(options.includeBots);
|
|
121
|
+
}
|
|
122
|
+
const response = await this.http.request(`/v1/users/${this.userId}/entities/relationships`, { method: 'GET' }, params);
|
|
123
|
+
return {
|
|
124
|
+
data: response.data,
|
|
125
|
+
pagination: response.pagination,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.EntitiesResource = EntitiesResource;
|
package/cjs/resources/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Resource exports
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AdminSettingsResource = exports.QueryResource = exports.ThreadsResource = exports.MeetingsResource = exports.EventsResource = exports.CalendarsResource = exports.IntegrationsResource = exports.ConversationsResource = exports.MessagesResource = exports.UsersResource = void 0;
|
|
6
|
+
exports.AdminWebhooksResource = exports.AdminSettingsResource = exports.QueryResource = exports.ThreadsResource = exports.MeetingsResource = exports.EventsResource = exports.CalendarsResource = exports.EntitiesResource = exports.IntegrationsResource = exports.ConversationsResource = exports.MessagesResource = exports.UsersResource = void 0;
|
|
7
7
|
var users_js_1 = require("./users.js");
|
|
8
8
|
Object.defineProperty(exports, "UsersResource", { enumerable: true, get: function () { return users_js_1.UsersResource; } });
|
|
9
9
|
var messages_js_1 = require("./messages.js");
|
|
@@ -12,6 +12,8 @@ var conversations_js_1 = require("./conversations.js");
|
|
|
12
12
|
Object.defineProperty(exports, "ConversationsResource", { enumerable: true, get: function () { return conversations_js_1.ConversationsResource; } });
|
|
13
13
|
var integrations_js_1 = require("./integrations.js");
|
|
14
14
|
Object.defineProperty(exports, "IntegrationsResource", { enumerable: true, get: function () { return integrations_js_1.IntegrationsResource; } });
|
|
15
|
+
var entities_js_1 = require("./entities.js");
|
|
16
|
+
Object.defineProperty(exports, "EntitiesResource", { enumerable: true, get: function () { return entities_js_1.EntitiesResource; } });
|
|
15
17
|
var calendars_js_1 = require("./calendars.js");
|
|
16
18
|
Object.defineProperty(exports, "CalendarsResource", { enumerable: true, get: function () { return calendars_js_1.CalendarsResource; } });
|
|
17
19
|
var events_js_1 = require("./events.js");
|
|
@@ -24,3 +26,5 @@ var query_js_1 = require("./query.js");
|
|
|
24
26
|
Object.defineProperty(exports, "QueryResource", { enumerable: true, get: function () { return query_js_1.QueryResource; } });
|
|
25
27
|
var settings_js_1 = require("./settings.js");
|
|
26
28
|
Object.defineProperty(exports, "AdminSettingsResource", { enumerable: true, get: function () { return settings_js_1.AdminSettingsResource; } });
|
|
29
|
+
var webhooks_js_1 = require("./webhooks.js");
|
|
30
|
+
Object.defineProperty(exports, "AdminWebhooksResource", { enumerable: true, get: function () { return webhooks_js_1.AdminWebhooksResource; } });
|
|
@@ -45,7 +45,7 @@ class MessagesResource {
|
|
|
45
45
|
async list(options = {}) {
|
|
46
46
|
const params = {};
|
|
47
47
|
if (options.ids?.length) {
|
|
48
|
-
params.ids = options.ids.join(
|
|
48
|
+
params.ids = options.ids.join(',');
|
|
49
49
|
}
|
|
50
50
|
if (options.integrationId) {
|
|
51
51
|
params.integration_id = options.integrationId;
|
|
@@ -65,13 +65,19 @@ class MessagesResource {
|
|
|
65
65
|
if (options.offset !== undefined) {
|
|
66
66
|
params.offset = String(options.offset);
|
|
67
67
|
}
|
|
68
|
+
if (options.sentBy) {
|
|
69
|
+
params.sent_by = options.sentBy;
|
|
70
|
+
}
|
|
71
|
+
if (options.entityId) {
|
|
72
|
+
params.entity_id = options.entityId;
|
|
73
|
+
}
|
|
68
74
|
if (options.excludeBots !== undefined) {
|
|
69
75
|
params.exclude_bots = String(options.excludeBots);
|
|
70
76
|
}
|
|
71
77
|
if (options.expand?.length) {
|
|
72
|
-
params.expand = options.expand.join(
|
|
78
|
+
params.expand = options.expand.join(',');
|
|
73
79
|
}
|
|
74
|
-
const response = await this.http.request(`/v1/users/${this.userId}/messages`, { method:
|
|
80
|
+
const response = await this.http.request(`/v1/users/${this.userId}/messages`, { method: 'GET' }, params);
|
|
75
81
|
return {
|
|
76
82
|
data: response.data,
|
|
77
83
|
pagination: response.pagination,
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AdminWebhooksResource = void 0;
|
|
4
|
+
const index_js_1 = require("../errors/index.js");
|
|
5
|
+
const index_js_2 = require("../types/index.js");
|
|
6
|
+
function validateWebhookId(id, fieldName = 'id') {
|
|
7
|
+
if (!(0, index_js_2.isValidUUID)(id)) {
|
|
8
|
+
throw new index_js_1.ValidationError(`${fieldName} must be a valid UUID format`, index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: fieldName, expected: 'UUID format' });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function validateWebhookCreateOptions(options) {
|
|
12
|
+
if (!options.url || typeof options.url !== 'string') {
|
|
13
|
+
throw new index_js_1.ValidationError('url is required and must be a non-empty string', index_js_2.ErrorCodes.VALIDATION_REQUIRED_FIELD, { field: 'url' });
|
|
14
|
+
}
|
|
15
|
+
if (!options.url.startsWith('https://')) {
|
|
16
|
+
throw new index_js_1.ValidationError('url must use HTTPS', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: 'url' });
|
|
17
|
+
}
|
|
18
|
+
if (!Array.isArray(options.eventTypes) || options.eventTypes.length === 0) {
|
|
19
|
+
throw new index_js_1.ValidationError('eventTypes must contain at least one event type', index_js_2.ErrorCodes.VALIDATION_REQUIRED_FIELD, { field: 'eventTypes' });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function validateWebhookUpdateOptions(options) {
|
|
23
|
+
if (options.url === undefined &&
|
|
24
|
+
options.eventTypes === undefined &&
|
|
25
|
+
options.isActive === undefined) {
|
|
26
|
+
throw new index_js_1.ValidationError('At least one field must be provided for update', index_js_2.ErrorCodes.VALIDATION_REQUIRED_FIELD, { field: 'options' });
|
|
27
|
+
}
|
|
28
|
+
if (typeof options.url === 'string' && !options.url.startsWith('https://')) {
|
|
29
|
+
throw new index_js_1.ValidationError('url must use HTTPS', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: 'url' });
|
|
30
|
+
}
|
|
31
|
+
if (options.eventTypes !== undefined &&
|
|
32
|
+
(!Array.isArray(options.eventTypes) || options.eventTypes.length === 0)) {
|
|
33
|
+
throw new index_js_1.ValidationError('eventTypes must contain at least one event type', index_js_2.ErrorCodes.VALIDATION_INVALID_FORMAT, { field: 'eventTypes' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function mapEndpoint(raw) {
|
|
37
|
+
return {
|
|
38
|
+
id: raw.id,
|
|
39
|
+
partnerId: raw.partner_id,
|
|
40
|
+
url: raw.url,
|
|
41
|
+
eventTypes: raw.event_types,
|
|
42
|
+
isActive: raw.is_active,
|
|
43
|
+
autoPausedAt: raw.auto_paused_at,
|
|
44
|
+
consecutiveFailures: raw.consecutive_failures,
|
|
45
|
+
secretPrefix: raw.secret_prefix,
|
|
46
|
+
secretSuffix: raw.secret_suffix,
|
|
47
|
+
createdByUserId: raw.created_by_user_id,
|
|
48
|
+
createdAt: raw.created_at,
|
|
49
|
+
updatedAt: raw.updated_at,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function mapDelivery(raw) {
|
|
53
|
+
return {
|
|
54
|
+
id: raw.id,
|
|
55
|
+
eventId: raw.event_id,
|
|
56
|
+
endpointId: raw.endpoint_id,
|
|
57
|
+
attempt: raw.attempt,
|
|
58
|
+
status: raw.status,
|
|
59
|
+
nextAttemptAt: raw.next_attempt_at,
|
|
60
|
+
responseStatus: raw.response_status,
|
|
61
|
+
responseBodyTruncated: raw.response_body_truncated,
|
|
62
|
+
durationMs: raw.duration_ms,
|
|
63
|
+
errorCode: raw.error_code,
|
|
64
|
+
webhookId: raw.webhook_id,
|
|
65
|
+
deliveredAt: raw.delivered_at,
|
|
66
|
+
createdAt: raw.created_at,
|
|
67
|
+
updatedAt: raw.updated_at,
|
|
68
|
+
eventType: raw.event_type,
|
|
69
|
+
eventVersion: raw.event_version,
|
|
70
|
+
occurredAt: raw.occurred_at,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class AdminWebhooksResource {
|
|
74
|
+
constructor(http, clientId) {
|
|
75
|
+
this.http = http;
|
|
76
|
+
this.clientId = clientId;
|
|
77
|
+
}
|
|
78
|
+
/** Creates a new webhook endpoint. The response includes the plaintext secret (shown only once). */
|
|
79
|
+
async create(options) {
|
|
80
|
+
validateWebhookCreateOptions(options);
|
|
81
|
+
const raw = await this.http.post('/v1/webhooks', {
|
|
82
|
+
url: options.url,
|
|
83
|
+
event_types: options.eventTypes,
|
|
84
|
+
is_active: options.isActive,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
...mapEndpoint(raw),
|
|
88
|
+
secret: raw.secret,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Lists all webhook endpoints for this partner. */
|
|
92
|
+
async list() {
|
|
93
|
+
const raw = await this.http.get('/v1/webhooks');
|
|
94
|
+
return raw.map(mapEndpoint);
|
|
95
|
+
}
|
|
96
|
+
/** Retrieves a single webhook endpoint by ID. */
|
|
97
|
+
async get(id) {
|
|
98
|
+
validateWebhookId(id);
|
|
99
|
+
const raw = await this.http.get(`/v1/webhooks/${id}`);
|
|
100
|
+
return mapEndpoint(raw);
|
|
101
|
+
}
|
|
102
|
+
/** Updates a webhook endpoint's URL, subscribed event types, or active status. */
|
|
103
|
+
async update(id, options) {
|
|
104
|
+
validateWebhookId(id);
|
|
105
|
+
validateWebhookUpdateOptions(options);
|
|
106
|
+
const body = {};
|
|
107
|
+
if (options.url !== undefined) {
|
|
108
|
+
body['url'] = options.url;
|
|
109
|
+
}
|
|
110
|
+
if (options.eventTypes !== undefined) {
|
|
111
|
+
body['event_types'] = options.eventTypes;
|
|
112
|
+
}
|
|
113
|
+
if (options.isActive !== undefined) {
|
|
114
|
+
body['is_active'] = options.isActive;
|
|
115
|
+
}
|
|
116
|
+
const raw = await this.http.patch(`/v1/webhooks/${id}`, body);
|
|
117
|
+
return mapEndpoint(raw);
|
|
118
|
+
}
|
|
119
|
+
/** Deletes a webhook endpoint and all associated deliveries. */
|
|
120
|
+
async delete(id) {
|
|
121
|
+
validateWebhookId(id);
|
|
122
|
+
return this.http.delete(`/v1/webhooks/${id}`);
|
|
123
|
+
}
|
|
124
|
+
/** Sends a synthetic test event to the specified endpoint to verify connectivity. */
|
|
125
|
+
async test(id) {
|
|
126
|
+
validateWebhookId(id);
|
|
127
|
+
const raw = await this.http.post(`/v1/webhooks/${id}/test`, {});
|
|
128
|
+
return {
|
|
129
|
+
endpointId: raw.endpoint_id,
|
|
130
|
+
eventId: raw.event_id,
|
|
131
|
+
fanoutCount: raw.fanout_count,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Rotates the webhook signing secret. The previous secret remains valid for 24 hours. */
|
|
135
|
+
async rotateSecret(id) {
|
|
136
|
+
validateWebhookId(id);
|
|
137
|
+
const raw = await this.http.post(`/v1/webhooks/${id}/rotate-secret`, {});
|
|
138
|
+
return {
|
|
139
|
+
id: raw.id,
|
|
140
|
+
secret: raw.secret,
|
|
141
|
+
secretPrefix: raw.secret_prefix,
|
|
142
|
+
secretSuffix: raw.secret_suffix,
|
|
143
|
+
rotatedAt: raw.rotated_at,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Lists delivery attempts for a webhook endpoint, optionally filtered by status. */
|
|
147
|
+
async listDeliveries(id, options = {}) {
|
|
148
|
+
validateWebhookId(id);
|
|
149
|
+
const query = {};
|
|
150
|
+
if (typeof options.limit === 'number') {
|
|
151
|
+
if (!Number.isInteger(options.limit) ||
|
|
152
|
+
options.limit < 1 ||
|
|
153
|
+
options.limit > 100) {
|
|
154
|
+
throw new index_js_1.ValidationError('limit must be an integer between 1 and 100', index_js_2.ErrorCodes.VALIDATION_OUT_OF_RANGE, { field: 'limit', value: options.limit });
|
|
155
|
+
}
|
|
156
|
+
query.limit = String(options.limit);
|
|
157
|
+
}
|
|
158
|
+
if (typeof options.offset === 'number') {
|
|
159
|
+
if (!Number.isInteger(options.offset) || options.offset < 0) {
|
|
160
|
+
throw new index_js_1.ValidationError('offset must be a non-negative integer', index_js_2.ErrorCodes.VALIDATION_OUT_OF_RANGE, { field: 'offset', value: options.offset });
|
|
161
|
+
}
|
|
162
|
+
query.offset = String(options.offset);
|
|
163
|
+
}
|
|
164
|
+
if (options.status) {
|
|
165
|
+
query.status = options.status;
|
|
166
|
+
}
|
|
167
|
+
const raw = await this.http.get(`/v1/webhooks/${id}/deliveries`, query);
|
|
168
|
+
return raw.map(mapDelivery);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.AdminWebhooksResource = AdminWebhooksResource;
|
package/cjs/utils/fetch.js
CHANGED
package/cjs/utils/index.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
* Utility exports
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateMessageId = exports.StreamingClient = exports.HttpClient = void 0;
|
|
6
|
+
exports.verifyWebhookSignatureDetailed = exports.verifyWebhookSignature = exports.generateMessageId = exports.StreamingClient = exports.HttpClient = void 0;
|
|
7
7
|
var fetch_js_1 = require("./fetch.js");
|
|
8
8
|
Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return fetch_js_1.HttpClient; } });
|
|
9
9
|
var streaming_js_1 = require("./streaming.js");
|
|
10
10
|
Object.defineProperty(exports, "StreamingClient", { enumerable: true, get: function () { return streaming_js_1.StreamingClient; } });
|
|
11
11
|
Object.defineProperty(exports, "generateMessageId", { enumerable: true, get: function () { return streaming_js_1.generateMessageId; } });
|
|
12
|
+
var webhooks_js_1 = require("./webhooks.js");
|
|
13
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhooks_js_1.verifyWebhookSignature; } });
|
|
14
|
+
Object.defineProperty(exports, "verifyWebhookSignatureDetailed", { enumerable: true, get: function () { return webhooks_js_1.verifyWebhookSignatureDetailed; } });
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyWebhookSignature = exports.verifyWebhookSignatureDetailed = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/// <reference types="node" />
|
|
6
|
+
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
7
|
+
const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
8
|
+
function getHeader(headers, name) {
|
|
9
|
+
const lowerName = name.toLowerCase();
|
|
10
|
+
if (typeof Headers !== 'undefined' && headers instanceof Headers) {
|
|
11
|
+
return headers.get(lowerName);
|
|
12
|
+
}
|
|
13
|
+
const record = headers;
|
|
14
|
+
for (const [key, value] of Object.entries(record)) {
|
|
15
|
+
if (key.toLowerCase() !== lowerName) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
return value ?? null;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function normalizeBody(body) {
|
|
26
|
+
if (typeof body === 'string') {
|
|
27
|
+
return Buffer.from(body, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
if (body instanceof Uint8Array) {
|
|
30
|
+
return Buffer.from(body);
|
|
31
|
+
}
|
|
32
|
+
return Buffer.from(body);
|
|
33
|
+
}
|
|
34
|
+
function extractSignatures(signatureHeader) {
|
|
35
|
+
const candidates = signatureHeader
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.map((segment) => segment.trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
const signatures = [];
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (candidate.startsWith('v1,')) {
|
|
42
|
+
signatures.push(candidate.slice(3));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (candidate.startsWith('v1=')) {
|
|
46
|
+
signatures.push(candidate.slice(3));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Allow bare values to be tolerant of intermediary header transformations.
|
|
50
|
+
signatures.push(candidate);
|
|
51
|
+
}
|
|
52
|
+
return signatures.filter((signature) => signature.length > 0);
|
|
53
|
+
}
|
|
54
|
+
function expectedSignature(params) {
|
|
55
|
+
const hmac = crypto_1.default.createHmac('sha256', params.secret);
|
|
56
|
+
hmac.update(`${params.webhookId}.${params.timestamp}.`, 'utf8');
|
|
57
|
+
hmac.update(params.body);
|
|
58
|
+
return hmac.digest();
|
|
59
|
+
}
|
|
60
|
+
function safeCompare(left, right) {
|
|
61
|
+
if (left.length !== right.length) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return crypto_1.default.timingSafeEqual(left, right);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Verifies Attrove webhook signatures using HMAC-SHA256 (`v1`).
|
|
68
|
+
* Returns a structured result with a reason on failure for easier debugging.
|
|
69
|
+
*
|
|
70
|
+
* Uses the raw secret string (including `whsec_` prefix) as the HMAC key.
|
|
71
|
+
* This matches the Attrove server's signing behaviour but differs from the
|
|
72
|
+
* Standard Webhooks reference implementation which base64-decodes the secret.
|
|
73
|
+
* Always use this SDK function (not a generic Standard Webhooks library) to verify.
|
|
74
|
+
*
|
|
75
|
+
* Signing input: `${webhook-id}.${webhook-timestamp}.${rawBody}`
|
|
76
|
+
*
|
|
77
|
+
* @remarks Node.js only — requires `crypto` and `Buffer` from Node.js built-ins.
|
|
78
|
+
*/
|
|
79
|
+
function verifyWebhookSignatureDetailed(headers, rawBody, secret, options = {}) {
|
|
80
|
+
const webhookId = getHeader(headers, 'webhook-id');
|
|
81
|
+
const timestamp = getHeader(headers, 'webhook-timestamp');
|
|
82
|
+
const signatureHeader = getHeader(headers, 'webhook-signature');
|
|
83
|
+
if (!webhookId || !timestamp || !signatureHeader) {
|
|
84
|
+
return { valid: false, reason: 'missing_headers' };
|
|
85
|
+
}
|
|
86
|
+
if (!secret) {
|
|
87
|
+
return { valid: false, reason: 'missing_secret' };
|
|
88
|
+
}
|
|
89
|
+
const timestampSeconds = Number(timestamp);
|
|
90
|
+
if (!Number.isFinite(timestampSeconds)) {
|
|
91
|
+
return { valid: false, reason: 'invalid_timestamp' };
|
|
92
|
+
}
|
|
93
|
+
const now = options.currentTimestamp ?? Math.floor(Date.now() / 1000);
|
|
94
|
+
const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
95
|
+
if (Math.abs(now - timestampSeconds) > tolerance) {
|
|
96
|
+
return { valid: false, reason: 'timestamp_expired' };
|
|
97
|
+
}
|
|
98
|
+
const bodyBuffer = normalizeBody(rawBody);
|
|
99
|
+
const expected = expectedSignature({
|
|
100
|
+
secret,
|
|
101
|
+
webhookId,
|
|
102
|
+
timestamp,
|
|
103
|
+
body: bodyBuffer,
|
|
104
|
+
});
|
|
105
|
+
const providedSignatures = extractSignatures(signatureHeader);
|
|
106
|
+
const matched = providedSignatures.some((provided) => {
|
|
107
|
+
let decoded;
|
|
108
|
+
try {
|
|
109
|
+
decoded = Buffer.from(provided, 'base64');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return safeCompare(decoded, expected);
|
|
115
|
+
});
|
|
116
|
+
if (matched) {
|
|
117
|
+
return { valid: true };
|
|
118
|
+
}
|
|
119
|
+
return { valid: false, reason: 'no_matching_signature' };
|
|
120
|
+
}
|
|
121
|
+
exports.verifyWebhookSignatureDetailed = verifyWebhookSignatureDetailed;
|
|
122
|
+
/**
|
|
123
|
+
* Verifies Attrove webhook signatures using HMAC-SHA256 (`v1`).
|
|
124
|
+
* Convenience wrapper that returns a boolean instead of a detailed result.
|
|
125
|
+
*
|
|
126
|
+
* @remarks Node.js only — requires `crypto` and `Buffer` from Node.js built-ins.
|
|
127
|
+
*/
|
|
128
|
+
function verifyWebhookSignature(headers, rawBody, secret, options = {}) {
|
|
129
|
+
return verifyWebhookSignatureDetailed(headers, rawBody, secret, options)
|
|
130
|
+
.valid;
|
|
131
|
+
}
|
|
132
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
package/esm/admin-client.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { HttpClient } from "./utils/fetch.js";
|
|
8
8
|
import { AttroveAdminConfig, CreateUserOptions, CreateUserResponse, CreateTokenResponse } from "./types/index.js";
|
|
9
9
|
import { AdminSettingsResource } from "./resources/settings.js";
|
|
10
|
+
import { AdminWebhooksResource } from "./resources/webhooks.js";
|
|
10
11
|
/**
|
|
11
12
|
* Users admin resource for managing users.
|
|
12
13
|
*
|
|
@@ -110,6 +111,10 @@ export declare class AttroveAdmin {
|
|
|
110
111
|
* Settings resource for managing organization sync configuration.
|
|
111
112
|
*/
|
|
112
113
|
readonly settings: AdminSettingsResource;
|
|
114
|
+
/**
|
|
115
|
+
* Webhooks resource for managing outbound webhook endpoints.
|
|
116
|
+
*/
|
|
117
|
+
readonly webhooks: AdminWebhooksResource;
|
|
113
118
|
/**
|
|
114
119
|
* Create a new admin client.
|
|
115
120
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-client.d.ts","sourceRoot":"","sources":["../../../../packages/sdk/src/admin-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAkDhE;;;;;GAKG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM;IAGnC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsBrE;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAqBvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CACe;IAEtC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IAEnC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IAEzC;;;;;;;;;;;;;;OAcG;gBACS,MAAM,EAAE,kBAAkB;
|
|
1
|
+
{"version":3,"file":"admin-client.d.ts","sourceRoot":"","sources":["../../../../packages/sdk/src/admin-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAkDhE;;;;;GAKG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM;IAGnC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsBrE;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAqBvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CACe;IAEtC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IAEnC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IAEzC;;;;;;;;;;;;;;OAcG;gBACS,MAAM,EAAE,kBAAkB;CA2BvC"}
|