@ikatec/digisac-api-sdk 4.8.0 → 4.10.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/README.md +36 -12
- package/dist/apis/absence/AbsenceApi.cjs +12 -0
- package/dist/apis/absence/AbsenceApi.cjs.map +1 -1
- package/dist/apis/absence/AbsenceApi.d.ts +9 -0
- package/dist/apis/absence/AbsenceApi.d.ts.map +1 -1
- package/dist/apis/absence/AbsenceApi.mjs +12 -0
- package/dist/apis/absence/AbsenceApi.mjs.map +1 -1
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.cjs +8 -0
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.cjs.map +1 -1
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.d.ts +8 -1
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.d.ts.map +1 -1
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.mjs +8 -0
- package/dist/apis/acceptanceTerms/AcceptanceTermsApi.mjs.map +1 -1
- package/dist/apis/accounts/AccountsApi.cjs +5 -0
- package/dist/apis/accounts/AccountsApi.cjs.map +1 -1
- package/dist/apis/accounts/AccountsApi.d.ts +5 -0
- package/dist/apis/accounts/AccountsApi.d.ts.map +1 -1
- package/dist/apis/accounts/AccountsApi.mjs +5 -0
- package/dist/apis/accounts/AccountsApi.mjs.map +1 -1
- package/dist/apis/accounts/types.d.ts +2 -0
- package/dist/apis/accounts/types.d.ts.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.cjs +12 -0
- package/dist/apis/activityLog/ActivityLogApi.cjs.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.d.ts +8 -0
- package/dist/apis/activityLog/ActivityLogApi.d.ts.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.mjs +12 -0
- package/dist/apis/activityLog/ActivityLogApi.mjs.map +1 -1
- package/dist/apis/categories/CategoriesApi.cjs +7 -0
- package/dist/apis/categories/CategoriesApi.cjs.map +1 -1
- package/dist/apis/categories/CategoriesApi.d.ts +7 -1
- package/dist/apis/categories/CategoriesApi.d.ts.map +1 -1
- package/dist/apis/categories/CategoriesApi.mjs +7 -0
- package/dist/apis/categories/CategoriesApi.mjs.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs +5 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts +5 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs +5 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs +8 -0
- package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts +8 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs +8 -0
- package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs +7 -0
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts +7 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs +7 -0
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.cjs +16 -3
- package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.d.ts +16 -5
- package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
- package/dist/apis/contacts/ContactsApi.mjs +16 -3
- package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
- package/dist/apis/contacts/types.d.ts +6 -1
- package/dist/apis/contacts/types.d.ts.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.cjs +17 -0
- package/dist/apis/creditMovements/CreditMovementsApi.cjs.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.d.ts +11 -0
- package/dist/apis/creditMovements/CreditMovementsApi.d.ts.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.mjs +17 -0
- package/dist/apis/creditMovements/CreditMovementsApi.mjs.map +1 -1
- package/dist/apis/customFields/CustomFieldsApi.cjs +7 -0
- package/dist/apis/customFields/CustomFieldsApi.cjs.map +1 -1
- package/dist/apis/customFields/CustomFieldsApi.d.ts +7 -1
- package/dist/apis/customFields/CustomFieldsApi.d.ts.map +1 -1
- package/dist/apis/customFields/CustomFieldsApi.mjs +7 -0
- package/dist/apis/customFields/CustomFieldsApi.mjs.map +1 -1
- package/dist/apis/departments/DepartmentsApi.cjs +21 -0
- package/dist/apis/departments/DepartmentsApi.cjs.map +1 -1
- package/dist/apis/departments/DepartmentsApi.d.ts +20 -2
- package/dist/apis/departments/DepartmentsApi.d.ts.map +1 -1
- package/dist/apis/departments/DepartmentsApi.mjs +21 -0
- package/dist/apis/departments/DepartmentsApi.mjs.map +1 -1
- package/dist/apis/departments/types.d.ts +5 -0
- package/dist/apis/departments/types.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.cjs +7 -0
- package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
- package/dist/apis/distribution/DistributionApi.d.ts +7 -1
- package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.mjs +7 -0
- package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
- package/dist/apis/files/FilesApi.cjs +49 -0
- package/dist/apis/files/FilesApi.cjs.map +1 -1
- package/dist/apis/files/FilesApi.d.ts +45 -2
- package/dist/apis/files/FilesApi.d.ts.map +1 -1
- package/dist/apis/files/FilesApi.mjs +49 -0
- package/dist/apis/files/FilesApi.mjs.map +1 -1
- package/dist/apis/files/types.d.ts +45 -7
- package/dist/apis/files/types.d.ts.map +1 -1
- package/dist/apis/holiday/HolidayApi.cjs +7 -0
- package/dist/apis/holiday/HolidayApi.cjs.map +1 -1
- package/dist/apis/holiday/HolidayApi.d.ts +7 -1
- package/dist/apis/holiday/HolidayApi.d.ts.map +1 -1
- package/dist/apis/holiday/HolidayApi.mjs +7 -0
- package/dist/apis/holiday/HolidayApi.mjs.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.cjs +7 -0
- package/dist/apis/integrations/IntegrationsApi.cjs.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.d.ts +7 -1
- package/dist/apis/integrations/IntegrationsApi.d.ts.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.mjs +7 -0
- package/dist/apis/integrations/IntegrationsApi.mjs.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs +7 -0
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts +7 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs +7 -0
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs.map +1 -1
- package/dist/apis/interactiveMessages/types.d.ts +61 -3
- package/dist/apis/interactiveMessages/types.d.ts.map +1 -1
- package/dist/apis/kanbanBoards/KanbanBoardsApi.cjs +7 -0
- package/dist/apis/kanbanBoards/KanbanBoardsApi.cjs.map +1 -1
- package/dist/apis/kanbanBoards/KanbanBoardsApi.d.ts +7 -1
- package/dist/apis/kanbanBoards/KanbanBoardsApi.d.ts.map +1 -1
- package/dist/apis/kanbanBoards/KanbanBoardsApi.mjs +7 -0
- package/dist/apis/kanbanBoards/KanbanBoardsApi.mjs.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.cjs +7 -0
- package/dist/apis/kanbanCards/KanbanCardsApi.cjs.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.d.ts +7 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.d.ts.map +1 -1
- package/dist/apis/kanbanCards/KanbanCardsApi.mjs +7 -0
- package/dist/apis/kanbanCards/KanbanCardsApi.mjs.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs +8 -0
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts +8 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs +8 -0
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs +8 -0
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts +8 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs +8 -0
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs.map +1 -1
- package/dist/apis/messages/MessagesApi.cjs +23 -3
- package/dist/apis/messages/MessagesApi.cjs.map +1 -1
- package/dist/apis/messages/MessagesApi.d.ts +19 -3
- package/dist/apis/messages/MessagesApi.d.ts.map +1 -1
- package/dist/apis/messages/MessagesApi.mjs +23 -3
- package/dist/apis/messages/MessagesApi.mjs.map +1 -1
- package/dist/apis/messages/types.d.ts +3 -0
- package/dist/apis/messages/types.d.ts.map +1 -1
- package/dist/apis/notifications/NotificationsApi.cjs +7 -0
- package/dist/apis/notifications/NotificationsApi.cjs.map +1 -1
- package/dist/apis/notifications/NotificationsApi.d.ts +7 -1
- package/dist/apis/notifications/NotificationsApi.d.ts.map +1 -1
- package/dist/apis/notifications/NotificationsApi.mjs +7 -0
- package/dist/apis/notifications/NotificationsApi.mjs.map +1 -1
- package/dist/apis/organizations/OrganizationsApi.cjs +7 -0
- package/dist/apis/organizations/OrganizationsApi.cjs.map +1 -1
- package/dist/apis/organizations/OrganizationsApi.d.ts +7 -1
- package/dist/apis/organizations/OrganizationsApi.d.ts.map +1 -1
- package/dist/apis/organizations/OrganizationsApi.mjs +7 -0
- package/dist/apis/organizations/OrganizationsApi.mjs.map +1 -1
- package/dist/apis/people/PeopleApi.cjs +7 -0
- package/dist/apis/people/PeopleApi.cjs.map +1 -1
- package/dist/apis/people/PeopleApi.d.ts +7 -1
- package/dist/apis/people/PeopleApi.d.ts.map +1 -1
- package/dist/apis/people/PeopleApi.mjs +7 -0
- package/dist/apis/people/PeopleApi.mjs.map +1 -1
- package/dist/apis/planAiHistory/PlanAiHistoryApi.cjs +16 -0
- package/dist/apis/planAiHistory/PlanAiHistoryApi.cjs.map +1 -1
- package/dist/apis/planAiHistory/PlanAiHistoryApi.d.ts +10 -0
- package/dist/apis/planAiHistory/PlanAiHistoryApi.d.ts.map +1 -1
- package/dist/apis/planAiHistory/PlanAiHistoryApi.mjs +16 -0
- package/dist/apis/planAiHistory/PlanAiHistoryApi.mjs.map +1 -1
- package/dist/apis/roles/RolesApi.cjs +7 -0
- package/dist/apis/roles/RolesApi.cjs.map +1 -1
- package/dist/apis/roles/RolesApi.d.ts +6 -1
- package/dist/apis/roles/RolesApi.d.ts.map +1 -1
- package/dist/apis/roles/RolesApi.mjs +7 -0
- package/dist/apis/roles/RolesApi.mjs.map +1 -1
- package/dist/apis/services/ServicesApi.cjs +68 -0
- package/dist/apis/services/ServicesApi.cjs.map +1 -1
- package/dist/apis/services/ServicesApi.d.ts +53 -1
- package/dist/apis/services/ServicesApi.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.mjs +68 -0
- package/dist/apis/services/ServicesApi.mjs.map +1 -1
- package/dist/apis/services/types.d.ts +68 -1
- package/dist/apis/services/types.d.ts.map +1 -1
- package/dist/apis/stickers/StickersApi.cjs +20 -0
- package/dist/apis/stickers/StickersApi.cjs.map +1 -1
- package/dist/apis/stickers/StickersApi.d.ts +15 -0
- package/dist/apis/stickers/StickersApi.d.ts.map +1 -1
- package/dist/apis/stickers/StickersApi.mjs +20 -0
- package/dist/apis/stickers/StickersApi.mjs.map +1 -1
- package/dist/apis/tags/TagsApi.cjs +7 -0
- package/dist/apis/tags/TagsApi.cjs.map +1 -1
- package/dist/apis/tags/TagsApi.d.ts +7 -1
- package/dist/apis/tags/TagsApi.d.ts.map +1 -1
- package/dist/apis/tags/TagsApi.mjs +7 -0
- package/dist/apis/tags/TagsApi.mjs.map +1 -1
- package/dist/apis/timetable/TimetableApi.cjs +7 -0
- package/dist/apis/timetable/TimetableApi.cjs.map +1 -1
- package/dist/apis/timetable/TimetableApi.d.ts +7 -1
- package/dist/apis/timetable/TimetableApi.d.ts.map +1 -1
- package/dist/apis/timetable/TimetableApi.mjs +7 -0
- package/dist/apis/timetable/TimetableApi.mjs.map +1 -1
- package/dist/apis/users/UsersApi.cjs +8 -0
- package/dist/apis/users/UsersApi.cjs.map +1 -1
- package/dist/apis/users/UsersApi.d.ts +8 -1
- package/dist/apis/users/UsersApi.d.ts.map +1 -1
- package/dist/apis/users/UsersApi.mjs +8 -0
- package/dist/apis/users/UsersApi.mjs.map +1 -1
- package/dist/apis/users/types.d.ts +15 -0
- package/dist/apis/users/types.d.ts.map +1 -1
- package/dist/apis/webhooks/WebhooksApi.cjs +7 -0
- package/dist/apis/webhooks/WebhooksApi.cjs.map +1 -1
- package/dist/apis/webhooks/WebhooksApi.d.ts +7 -1
- package/dist/apis/webhooks/WebhooksApi.d.ts.map +1 -1
- package/dist/apis/webhooks/WebhooksApi.mjs +7 -0
- package/dist/apis/webhooks/WebhooksApi.mjs.map +1 -1
- package/dist/core/ApiClient.d.ts +14 -0
- package/dist/core/ApiClient.d.ts.map +1 -1
- package/dist/core/ArchivableCrudApi.cjs.map +1 -1
- package/dist/core/ArchivableCrudApi.d.ts +1 -1
- package/dist/core/ArchivableCrudApi.d.ts.map +1 -1
- package/dist/core/ArchivableCrudApi.mjs.map +1 -1
- package/dist/core/BaseApiClient.cjs +24 -3
- package/dist/core/BaseApiClient.cjs.map +1 -1
- package/dist/core/BaseApiClient.d.ts +14 -0
- package/dist/core/BaseApiClient.d.ts.map +1 -1
- package/dist/core/BaseApiClient.mjs +24 -3
- package/dist/core/BaseApiClient.mjs.map +1 -1
- package/dist/core/requestExternal.cjs +33 -0
- package/dist/core/requestExternal.cjs.map +1 -0
- package/dist/core/requestExternal.d.ts +9 -0
- package/dist/core/requestExternal.d.ts.map +1 -0
- package/dist/core/requestExternal.mjs +32 -0
- package/dist/core/requestExternal.mjs.map +1 -0
- package/package.json +1 -1
- package/src/apis/absence/AbsenceApi.ts +19 -0
- package/src/apis/acceptanceTerms/AcceptanceTermsApi.ts +13 -1
- package/src/apis/accounts/AccountsApi.ts +5 -0
- package/src/apis/accounts/types.ts +2 -0
- package/src/apis/activityLog/ActivityLogApi.ts +18 -0
- package/src/apis/categories/CategoriesApi.ts +12 -1
- package/src/apis/clientFeedback/ClientFeedbackApi.ts +5 -1
- package/src/apis/contactBlockLists/ContactBlockListsApi.ts +13 -1
- package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.ts +12 -1
- package/src/apis/contacts/ContactsApi.test.ts +20 -4
- package/src/apis/contacts/ContactsApi.ts +24 -5
- package/src/apis/contacts/types.ts +7 -1
- package/src/apis/creditMovements/CreditMovementsApi.ts +27 -0
- package/src/apis/customFields/CustomFieldsApi.ts +12 -1
- package/src/apis/departments/DepartmentsApi.test.ts +88 -0
- package/src/apis/departments/DepartmentsApi.ts +41 -2
- package/src/apis/departments/types.ts +6 -0
- package/src/apis/distribution/DistributionApi.ts +12 -1
- package/src/apis/files/FilesApi.test.ts +122 -0
- package/src/apis/files/FilesApi.ts +107 -2
- package/src/apis/files/types.ts +48 -7
- package/src/apis/holiday/HolidayApi.ts +16 -1
- package/src/apis/integrations/IntegrationsApi.ts +12 -1
- package/src/apis/interactiveMessages/InteractiveMessagesApi.ts +12 -1
- package/src/apis/interactiveMessages/types.ts +67 -1
- package/src/apis/kanbanBoards/KanbanBoardsApi.ts +12 -1
- package/src/apis/kanbanCards/KanbanCardsApi.ts +12 -1
- package/src/apis/knowledgeBase/KnowledgeBaseApi.ts +13 -1
- package/src/apis/knowledgeBaseItem/KnowledgeBaseItemApi.ts +13 -1
- package/src/apis/messages/MessagesApi.test.ts +13 -7
- package/src/apis/messages/MessagesApi.ts +29 -3
- package/src/apis/messages/types.ts +3 -0
- package/src/apis/notifications/NotificationsApi.ts +12 -1
- package/src/apis/organizations/OrganizationsApi.ts +12 -1
- package/src/apis/payloads.test-d.ts +275 -0
- package/src/apis/payloads.test.ts +41 -0
- package/src/apis/people/PeopleApi.ts +16 -1
- package/src/apis/planAiHistory/PlanAiHistoryApi.ts +26 -0
- package/src/apis/roles/RolesApi.ts +10 -1
- package/src/apis/services/ServicesApi.test.ts +45 -0
- package/src/apis/services/ServicesApi.ts +91 -1
- package/src/apis/services/types.ts +63 -1
- package/src/apis/stickers/StickersApi.ts +32 -0
- package/src/apis/tags/TagsApi.ts +16 -1
- package/src/apis/timetable/TimetableApi.ts +12 -1
- package/src/apis/users/UsersApi.ts +17 -1
- package/src/apis/users/types.ts +11 -0
- package/src/apis/webhooks/WebhooksApi.ts +16 -1
- package/src/core/ApiClient.ts +24 -0
- package/src/core/ArchivableCrudApi.ts +2 -1
- package/src/core/BaseApiClient.test.ts +98 -0
- package/src/core/BaseApiClient.ts +49 -4
- package/src/core/requestExternal.ts +43 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Service,
|
|
5
|
+
CreateServicePayload,
|
|
6
|
+
ServiceLimits,
|
|
7
|
+
UpdateServicePayload,
|
|
8
|
+
ArchiveServicePayload,
|
|
9
|
+
} from './types'
|
|
4
10
|
|
|
5
11
|
export class ServicesApi extends ArchivableCrudApi<
|
|
6
12
|
Service,
|
|
@@ -19,4 +25,88 @@ export class ServicesApi extends ArchivableCrudApi<
|
|
|
19
25
|
getLimits(headers?: Record<string, string>): Promise<ServiceLimits> {
|
|
20
26
|
return this.client.get<ServiceLimits>(`${this.basePath}/limits`, headers)
|
|
21
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated The backend does not archive a connection by `PUT { archivedAt }`: the PUT
|
|
31
|
+
* requires `type` (400 without it) and ignores `archivedAt`. Use {@link archiveWithValidation}.
|
|
32
|
+
*/
|
|
33
|
+
override archive(id: string, headers?: Record<string, string>): Promise<Service> {
|
|
34
|
+
return super.archive(id, headers)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated The backend does not restore a connection by `PUT { archivedAt: null }`: the PUT
|
|
39
|
+
* requires `type` (400 without it) and ignores `archivedAt`. Use {@link archiveWithValidation}.
|
|
40
|
+
*/
|
|
41
|
+
override unarchive(id: string, headers?: Record<string, string>): Promise<Service> {
|
|
42
|
+
return super.unarchive(id, headers)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Archives (`archive: true`) or restores (`archive: false`) a connection through the route used
|
|
47
|
+
* by the UI. Archiving shuts the connection down, closes its open tickets, marks its schedules
|
|
48
|
+
* as done and cancels its campaigns; restoring checks the plan limit of the `type` (400 when it
|
|
49
|
+
* is full).
|
|
50
|
+
*
|
|
51
|
+
* Resolves `{}` before the work is done (the route does not await it): poll `getById` until
|
|
52
|
+
* `archivedAt` changes. Send the `type` of the connection: without it the request is never
|
|
53
|
+
* answered.
|
|
54
|
+
* @permissions services.disabled
|
|
55
|
+
*/
|
|
56
|
+
archiveWithValidation(
|
|
57
|
+
id: string,
|
|
58
|
+
body: ArchiveServicePayload,
|
|
59
|
+
headers?: Record<string, string>,
|
|
60
|
+
): Promise<Record<string, never>> {
|
|
61
|
+
return this.client.post<Record<string, never>, ArchiveServicePayload>(
|
|
62
|
+
`${this.basePath}/${id}/archive`,
|
|
63
|
+
body,
|
|
64
|
+
headers,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Takes the session of the connection over to this server. Resolves the text `'OK'`; an
|
|
70
|
+
* unknown id answers 404 `Service not found.`.
|
|
71
|
+
* @permissions services.operate
|
|
72
|
+
*/
|
|
73
|
+
takeover(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
74
|
+
return this.client.post<'OK'>(`${this.basePath}/${id}/takeover`, undefined, headers)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Logs the connection out of the messaging service. Resolves the text `'OK'`; an unknown id
|
|
79
|
+
* answers 404 `Service not found.`.
|
|
80
|
+
* @permissions services.operate
|
|
81
|
+
*/
|
|
82
|
+
logout(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
83
|
+
return this.client.post<'OK'>(`${this.basePath}/${id}/logout`, undefined, headers)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Shuts the connection down. Resolves the text `'OK'`; an unknown id answers 404
|
|
88
|
+
* `Service not found.`.
|
|
89
|
+
* @permissions services.operate
|
|
90
|
+
*/
|
|
91
|
+
shutdown(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
92
|
+
return this.client.post<'OK'>(`${this.basePath}/${id}/shutdown`, undefined, headers)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Starts the connection. Resolves the text `'OK'`; an unknown id answers 404
|
|
97
|
+
* `Service not found.`.
|
|
98
|
+
* @permissions services.operate
|
|
99
|
+
*/
|
|
100
|
+
start(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
101
|
+
return this.client.post<'OK'>(`${this.basePath}/${id}/start`, undefined, headers)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Restarts the connection. Resolves the text `'OK'`; an unknown id answers 404
|
|
106
|
+
* `Service not found.`.
|
|
107
|
+
* @permissions services.operate
|
|
108
|
+
*/
|
|
109
|
+
restart(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
110
|
+
return this.client.post<'OK'>(`${this.basePath}/${id}/restart`, undefined, headers)
|
|
111
|
+
}
|
|
22
112
|
}
|
|
@@ -47,6 +47,52 @@ export type ServiceConnectionStatus = {
|
|
|
47
47
|
timestamp?: string
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* `data.webchat` of a Webchat service. Every key is optional: the object only fills up once the
|
|
52
|
+
* service is set up, and older services lack the keys added later (e.g. `instagram`). The backend
|
|
53
|
+
* stores what the forms send without normalizing it, so `null` and `{}` show up where the web
|
|
54
|
+
* forms write them.
|
|
55
|
+
*/
|
|
56
|
+
export type WebchatServiceData = {
|
|
57
|
+
/** Widget id in the webchat API; the page link is `${webchatUrl}/${id}`. Absent before setup. */
|
|
58
|
+
id?: string
|
|
59
|
+
name?: string | null
|
|
60
|
+
/** WhatsApp channel of the widget: DDI + number, without mask. `null` once cleared. */
|
|
61
|
+
phone?: string | null
|
|
62
|
+
/** Telegram channel of the widget, without `@`. `null` once cleared. */
|
|
63
|
+
telegram?: string | null
|
|
64
|
+
/** Instagram channel of the widget, without `@`. `null` once cleared. */
|
|
65
|
+
instagram?: string | null
|
|
66
|
+
isOpenForm?: boolean
|
|
67
|
+
isOpenChat?: boolean
|
|
68
|
+
/** `{}` when no picture was picked in the legacy form. */
|
|
69
|
+
file?: { base64Url?: string; mimetype?: string; fileName?: string }
|
|
70
|
+
googleId?: string | null
|
|
71
|
+
desktopChatOpen?: boolean
|
|
72
|
+
mobileChatOpen?: boolean
|
|
73
|
+
getClientData?: boolean
|
|
74
|
+
closeTicketByClient?: boolean
|
|
75
|
+
phoneClient?: boolean
|
|
76
|
+
phoneClientRequired?: boolean
|
|
77
|
+
clientName?: boolean
|
|
78
|
+
clientNameRequired?: boolean
|
|
79
|
+
/** `null` or `{}` when no custom field is asked for. */
|
|
80
|
+
customField?: { id?: string; name?: string } | null
|
|
81
|
+
customFieldRequired?: boolean
|
|
82
|
+
custom?: {
|
|
83
|
+
webchatBalloon?: boolean
|
|
84
|
+
webchatBalloonText?: string | null
|
|
85
|
+
webchatColor?: string
|
|
86
|
+
/** File id. */
|
|
87
|
+
logo?: string
|
|
88
|
+
/** File id. */
|
|
89
|
+
profilePhoto?: string
|
|
90
|
+
/** File id. */
|
|
91
|
+
webchatIcon?: string
|
|
92
|
+
typeIcon?: string
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
50
96
|
export type ServiceData = {
|
|
51
97
|
// ─── Common ───────────────────────────────────────────────────────────────
|
|
52
98
|
token?: string
|
|
@@ -106,7 +152,7 @@ export type ServiceData = {
|
|
|
106
152
|
mimetype: string
|
|
107
153
|
fileName: string
|
|
108
154
|
}
|
|
109
|
-
webchat?:
|
|
155
|
+
webchat?: WebchatServiceData
|
|
110
156
|
|
|
111
157
|
// ─── Other ────────────────────────────────────────────────────────────────
|
|
112
158
|
driverId?: string
|
|
@@ -207,6 +253,8 @@ export type ServiceRelationships = 'bot' | 'defaultDepartment'
|
|
|
207
253
|
export type CreateServicePayload = {
|
|
208
254
|
name: string
|
|
209
255
|
type: ServiceType
|
|
256
|
+
/** Send `null`: without it the duplicate-name check never answers (SUS-3784). */
|
|
257
|
+
archivedAt?: null
|
|
210
258
|
data?: ServiceData
|
|
211
259
|
settings?: ServiceSettings
|
|
212
260
|
defaultDepartmentId?: string
|
|
@@ -214,14 +262,28 @@ export type CreateServicePayload = {
|
|
|
214
262
|
}
|
|
215
263
|
|
|
216
264
|
export type UpdateServicePayload = {
|
|
265
|
+
/** Required by the backend (400 without it), although optional here for compatibility. */
|
|
266
|
+
type?: ServiceType
|
|
217
267
|
name?: string
|
|
218
268
|
data?: ServiceData
|
|
219
269
|
settings?: ServiceSettings
|
|
220
270
|
defaultDepartmentId?: string | null
|
|
221
271
|
botId?: string | null
|
|
272
|
+
/**
|
|
273
|
+
* Ignored by the backend (archive through `ServicesApi.archiveWithValidation`). Send `null`
|
|
274
|
+
* with a `name`: without `archivedAt` the duplicate-name check never answers (SUS-3784).
|
|
275
|
+
*/
|
|
222
276
|
archivedAt?: string | null
|
|
223
277
|
}
|
|
224
278
|
|
|
279
|
+
/** Body of `ServicesApi.archiveWithValidation`. */
|
|
280
|
+
export type ArchiveServicePayload = {
|
|
281
|
+
/** `true` archives, `false` restores. */
|
|
282
|
+
archive: boolean
|
|
283
|
+
/** The type of the connection, used by the plan limit check (the request hangs without it). */
|
|
284
|
+
type: ServiceType
|
|
285
|
+
}
|
|
286
|
+
|
|
225
287
|
export type ServiceLimit = {
|
|
226
288
|
used: number
|
|
227
289
|
total: number
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
+
import type { GetByIdQuery } from '../../core/types'
|
|
3
4
|
import type { Sticker, CreateStickerPayload, UpdateStickerPayload } from './types'
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Stickers of the account. The backend serves `GET /stickers` and `POST /stickers` (plus
|
|
8
|
+
* `GET /stickers/exist`, `POST /stickers/add` and `DELETE /stickers/remove/:stickerId`); there is
|
|
9
|
+
* no read by id, update nor `DELETE /stickers/:id`.
|
|
10
|
+
*/
|
|
5
11
|
export class StickersApi extends BaseCrudApi<Sticker, CreateStickerPayload, UpdateStickerPayload> {
|
|
6
12
|
constructor(client: ApiClient) {
|
|
7
13
|
super(client, '/stickers')
|
|
8
14
|
}
|
|
15
|
+
|
|
16
|
+
/** @deprecated Not served: the backend has no GET /stickers/:id, so this route answers 404. */
|
|
17
|
+
override getById(
|
|
18
|
+
id: string,
|
|
19
|
+
query?: GetByIdQuery<Sticker>,
|
|
20
|
+
headers?: Record<string, string>,
|
|
21
|
+
): Promise<Sticker> {
|
|
22
|
+
return super.getById(id, query, headers)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @deprecated Not served: the backend has no PUT /stickers/:id, so this route answers 404. */
|
|
26
|
+
override updateById(
|
|
27
|
+
id: string,
|
|
28
|
+
body: UpdateStickerPayload,
|
|
29
|
+
headers?: Record<string, string>,
|
|
30
|
+
): Promise<Sticker> {
|
|
31
|
+
return super.updateById(id, body, headers)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Not served: the backend has no DELETE /stickers/:id (it removes a sticker by
|
|
36
|
+
* `DELETE /stickers/remove/:stickerId`), so this route answers 404.
|
|
37
|
+
*/
|
|
38
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<Sticker> {
|
|
39
|
+
return super.deleteById(id, headers)
|
|
40
|
+
}
|
|
9
41
|
}
|
package/src/apis/tags/TagsApi.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
+
import type { ListQuery } from '../../core/types'
|
|
2
3
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
4
|
import type { ContactsSelection } from '../contacts/types'
|
|
4
5
|
import type {
|
|
@@ -9,11 +10,25 @@ import type {
|
|
|
9
10
|
UpdateTagPayload,
|
|
10
11
|
} from './types'
|
|
11
12
|
|
|
12
|
-
export class TagsApi extends BaseCrudApi<
|
|
13
|
+
export class TagsApi extends BaseCrudApi<
|
|
14
|
+
Tag,
|
|
15
|
+
CreateTagPayload,
|
|
16
|
+
UpdateTagPayload,
|
|
17
|
+
ListQuery<Tag>,
|
|
18
|
+
'OK'
|
|
19
|
+
> {
|
|
13
20
|
constructor(client: ApiClient) {
|
|
14
21
|
super(client, '/tags')
|
|
15
22
|
}
|
|
16
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Deletes a tag. Resolves the text `'OK'`; an unknown id answers 500.
|
|
26
|
+
* @permissions tags.view OR tags.destroy
|
|
27
|
+
*/
|
|
28
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
29
|
+
return super.deleteById(id, headers)
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
/**
|
|
18
33
|
* Adds the tag to every contact of the account that matches the selection. Resolves to `'OK'`,
|
|
19
34
|
* or `false` when every contact already had the tag.
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
+
import type { ListQuery } from '../../core/types'
|
|
2
3
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
4
|
import type { Timetable, CreateTimetablePayload, UpdateTimetablePayload } from './types'
|
|
4
5
|
|
|
5
6
|
export class TimetableApi extends BaseCrudApi<
|
|
6
7
|
Timetable,
|
|
7
8
|
CreateTimetablePayload,
|
|
8
|
-
UpdateTimetablePayload
|
|
9
|
+
UpdateTimetablePayload,
|
|
10
|
+
ListQuery<Timetable>,
|
|
11
|
+
'OK'
|
|
9
12
|
> {
|
|
10
13
|
constructor(client: ApiClient) {
|
|
11
14
|
super(client, '/timetable')
|
|
12
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Deletes a timetable. Resolves the text `'OK'`.
|
|
19
|
+
* @permissions timetables.view OR timetables.destroy
|
|
20
|
+
*/
|
|
21
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
22
|
+
return super.deleteById(id, headers)
|
|
23
|
+
}
|
|
13
24
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
+
import type { ListQuery } from '../../core/types'
|
|
2
3
|
import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
|
|
3
4
|
import type { Contact } from '../contacts/types'
|
|
4
5
|
import type {
|
|
@@ -19,11 +20,26 @@ import type {
|
|
|
19
20
|
ValidateUserImportResult,
|
|
20
21
|
} from './types'
|
|
21
22
|
|
|
22
|
-
export class UsersApi extends ArchivableCrudApi<
|
|
23
|
+
export class UsersApi extends ArchivableCrudApi<
|
|
24
|
+
User,
|
|
25
|
+
CreateUserPayload,
|
|
26
|
+
UpdateUserPayload,
|
|
27
|
+
ListQuery<User>,
|
|
28
|
+
'OK'
|
|
29
|
+
> {
|
|
23
30
|
constructor(client: ApiClient) {
|
|
24
31
|
super(client, '/users')
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Deletes a user (removes it as the default user of the contacts and deletes its webhooks first).
|
|
36
|
+
* Resolves the text `'OK'`.
|
|
37
|
+
* @permissions users.destroy
|
|
38
|
+
*/
|
|
39
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
40
|
+
return super.deleteById(id, headers)
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
/**
|
|
28
44
|
* Archives or restores a user through the dedicated route used by the UI, which also
|
|
29
45
|
* sets the user offline and ends its sessions. The route enforces the business rules that
|
package/src/apis/users/types.ts
CHANGED
|
@@ -71,7 +71,10 @@ export type CreateUserPayload = {
|
|
|
71
71
|
isClientUser?: boolean
|
|
72
72
|
roles?: string[]
|
|
73
73
|
departments?: string[]
|
|
74
|
+
/** @deprecated Not read by the backend: restrict the connections with `restrictedServices`. */
|
|
74
75
|
services?: string[]
|
|
76
|
+
/** Connections the user cannot access, as `{ value: serviceId }`. */
|
|
77
|
+
restrictedServices?: { value: string }[]
|
|
75
78
|
scheduleId?: string
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -83,7 +86,15 @@ export type UpdateUserPayload = {
|
|
|
83
86
|
isClientUser?: boolean
|
|
84
87
|
roles?: string[]
|
|
85
88
|
departments?: string[]
|
|
89
|
+
/** @deprecated Not read by the backend: restrict the connections with `restrictedServices`. */
|
|
86
90
|
services?: string[]
|
|
91
|
+
/**
|
|
92
|
+
* Connections the user cannot access, as `{ value: serviceId }`. Every update without it (and
|
|
93
|
+
* every update of an admin) clears the restrictions of the user.
|
|
94
|
+
*/
|
|
95
|
+
restrictedServices?: { value: string }[]
|
|
96
|
+
/** New password of the user. */
|
|
97
|
+
password?: string
|
|
87
98
|
timetableId?: string | null
|
|
88
99
|
preferences?: UserPreferences
|
|
89
100
|
language?: string
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
+
import type { ListQuery } from '../../core/types'
|
|
2
3
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
4
|
import type { Webhook, CreateWebhookPayload, UpdateWebhookPayload } from './types'
|
|
4
5
|
|
|
5
|
-
export class WebhooksApi extends BaseCrudApi<
|
|
6
|
+
export class WebhooksApi extends BaseCrudApi<
|
|
7
|
+
Webhook,
|
|
8
|
+
CreateWebhookPayload,
|
|
9
|
+
UpdateWebhookPayload,
|
|
10
|
+
ListQuery<Webhook>,
|
|
11
|
+
'OK'
|
|
12
|
+
> {
|
|
6
13
|
constructor(client: ApiClient) {
|
|
7
14
|
super(client, '/me/webhooks')
|
|
8
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Deletes a webhook. Resolves the text `'OK'`; an unknown id answers 500.
|
|
19
|
+
* @permissions settingsApi.update
|
|
20
|
+
*/
|
|
21
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
22
|
+
return super.deleteById(id, headers)
|
|
23
|
+
}
|
|
9
24
|
}
|
package/src/core/ApiClient.ts
CHANGED
|
@@ -32,4 +32,28 @@ export interface ApiClient {
|
|
|
32
32
|
endpoint: string,
|
|
33
33
|
headers?: Record<string, string>,
|
|
34
34
|
): Promise<TResponse>
|
|
35
|
+
/**
|
|
36
|
+
* Like {@link request}, but resolves the raw `Response` of a successful (2xx) call instead of
|
|
37
|
+
* the parsed body, for the routes whose headers matter (`Content-Type`, `Content-Disposition`)
|
|
38
|
+
* or whose body is binary (xlsx, pdf, files). Non-2xx still throws the `ApiError` subclass.
|
|
39
|
+
* Optional: a custom client without it cannot call the API methods that need the raw response.
|
|
40
|
+
*/
|
|
41
|
+
requestRaw?<TBody = unknown>(
|
|
42
|
+
method: HttpMethod,
|
|
43
|
+
endpoint: string,
|
|
44
|
+
body?: TBody,
|
|
45
|
+
headers?: Record<string, string>,
|
|
46
|
+
): Promise<Response>
|
|
47
|
+
/**
|
|
48
|
+
* Sends a request to an absolute URL outside the API (e.g. the signed storage URL of
|
|
49
|
+
* `FilesApi.upload`): no base URL, no `Authorization` header and no JSON serialization, `body`
|
|
50
|
+
* goes as is. Resolves the raw `Response` of a 2xx answer; non-2xx throws the `ApiError`
|
|
51
|
+
* subclass. Optional: without it, the API methods that need it send through the global `fetch`.
|
|
52
|
+
*/
|
|
53
|
+
requestExternal?(
|
|
54
|
+
method: HttpMethod,
|
|
55
|
+
url: string,
|
|
56
|
+
body?: BodyInit,
|
|
57
|
+
headers?: Record<string, string>,
|
|
58
|
+
): Promise<Response>
|
|
35
59
|
}
|
|
@@ -17,7 +17,8 @@ export class ArchivableCrudApi<
|
|
|
17
17
|
TCreate,
|
|
18
18
|
TUpdate extends { archivedAt?: string | null } = TCreate & { archivedAt?: string | null },
|
|
19
19
|
TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
|
|
20
|
-
|
|
20
|
+
TDeleteResponse = TResponse,
|
|
21
|
+
> extends BaseCrudApi<TResponse, TCreate, TUpdate, TQuery, TDeleteResponse> {
|
|
21
22
|
constructor(client: ApiClient, basePath: string) {
|
|
22
23
|
super(client, basePath)
|
|
23
24
|
}
|
|
@@ -498,4 +498,102 @@ describe('BaseApiClient', () => {
|
|
|
498
498
|
errorClass: 'AccessDenied',
|
|
499
499
|
})
|
|
500
500
|
})
|
|
501
|
+
|
|
502
|
+
// ── requestRaw ───────────────────────────────────────────────────
|
|
503
|
+
|
|
504
|
+
describe('requestRaw', () => {
|
|
505
|
+
it('resolves the raw Response of a 2xx answer, with the headers and body untouched', async () => {
|
|
506
|
+
const spy = mockFetch({
|
|
507
|
+
ok: true,
|
|
508
|
+
status: 200,
|
|
509
|
+
statusText: 'OK',
|
|
510
|
+
headers: { 'Content-Type': 'application/pdf', 'Content-Disposition': 'attachment' },
|
|
511
|
+
body: '%PDF-1.4',
|
|
512
|
+
})
|
|
513
|
+
client.setAccessToken('token-1')
|
|
514
|
+
const response = await client.requestRaw('GET', '/terms/t-1/export-pdf')
|
|
515
|
+
expect(spy).toHaveBeenCalledWith(
|
|
516
|
+
'https://api.example.com/terms/t-1/export-pdf',
|
|
517
|
+
expect.objectContaining({
|
|
518
|
+
method: 'GET',
|
|
519
|
+
headers: expect.objectContaining({ Authorization: 'Bearer token-1' }),
|
|
520
|
+
}),
|
|
521
|
+
)
|
|
522
|
+
expect(response.status).toBe(200)
|
|
523
|
+
expect(response.headers.get('Content-Type')).toBe('application/pdf')
|
|
524
|
+
expect(await response.text()).toBe('%PDF-1.4')
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it('sends the JSON body and the custom headers', async () => {
|
|
528
|
+
const spy = mockFetch({ ok: true, status: 200, statusText: 'OK', body: 'a;b' })
|
|
529
|
+
await client.requestRaw('POST', '/export', { where: { id: '1' } }, { 'X-Custom': 'v' })
|
|
530
|
+
expect(spy).toHaveBeenCalledWith(
|
|
531
|
+
'https://api.example.com/export',
|
|
532
|
+
expect.objectContaining({
|
|
533
|
+
method: 'POST',
|
|
534
|
+
body: JSON.stringify({ where: { id: '1' } }),
|
|
535
|
+
headers: expect.objectContaining({ 'X-Custom': 'v' }),
|
|
536
|
+
}),
|
|
537
|
+
)
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it('throws the ApiError subclass of a non-2xx answer', async () => {
|
|
541
|
+
mockFetch({
|
|
542
|
+
ok: false,
|
|
543
|
+
status: 404,
|
|
544
|
+
statusText: 'Not Found',
|
|
545
|
+
headers: { 'Content-Type': 'application/json' },
|
|
546
|
+
body: JSON.stringify({ error: 'NotFoundHttpError', message: 'Route not found.' }),
|
|
547
|
+
})
|
|
548
|
+
await expect(client.requestRaw('GET', '/missing')).rejects.toBeInstanceOf(NotFoundError)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('throws ApiConnectionError when the request fails', async () => {
|
|
552
|
+
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('fetch failed'))
|
|
553
|
+
await expect(client.requestRaw('GET', '/down')).rejects.toBeInstanceOf(ApiConnectionError)
|
|
554
|
+
})
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
// ── requestExternal ──────────────────────────────────────────────
|
|
558
|
+
|
|
559
|
+
describe('requestExternal', () => {
|
|
560
|
+
const SIGNED_URL = 'https://storage.example.com/acc-1/file-1.csv?X-Amz-Signature=secret'
|
|
561
|
+
|
|
562
|
+
it('sends the body as is to the absolute URL, without the token nor the JSON Content-Type', async () => {
|
|
563
|
+
const spy = vi
|
|
564
|
+
.spyOn(globalThis, 'fetch')
|
|
565
|
+
.mockResolvedValue(new Response(null, { status: 200 }))
|
|
566
|
+
client.setAccessToken('token-1')
|
|
567
|
+
const response = await client.requestExternal('PUT', SIGNED_URL, 'a;b', {
|
|
568
|
+
'Content-Type': 'text/csv',
|
|
569
|
+
})
|
|
570
|
+
expect(spy).toHaveBeenCalledWith(SIGNED_URL, {
|
|
571
|
+
method: 'PUT',
|
|
572
|
+
headers: { 'Content-Type': 'text/csv' },
|
|
573
|
+
body: 'a;b',
|
|
574
|
+
})
|
|
575
|
+
expect(response.status).toBe(200)
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
it('throws the ApiError subclass of a non-2xx answer, with the body as text', async () => {
|
|
579
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
580
|
+
new Response('<Error><Code>AccessDenied</Code></Error>', {
|
|
581
|
+
status: 403,
|
|
582
|
+
statusText: 'Forbidden',
|
|
583
|
+
}),
|
|
584
|
+
)
|
|
585
|
+
const captured = await client.requestExternal('PUT', SIGNED_URL).catch((e) => e)
|
|
586
|
+
expect(captured).toBeInstanceOf(PermissionDeniedError)
|
|
587
|
+
expect((captured as ApiError).body).toBe('<Error><Code>AccessDenied</Code></Error>')
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
it('throws ApiConnectionError without the query string (the signature) in the message', async () => {
|
|
591
|
+
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('fetch failed'))
|
|
592
|
+
const captured = await client.requestExternal('PUT', SIGNED_URL).catch((e) => e)
|
|
593
|
+
expect(captured).toBeInstanceOf(ApiConnectionError)
|
|
594
|
+
expect((captured as ApiConnectionError).message).toBe(
|
|
595
|
+
'Request to PUT https://storage.example.com/acc-1/file-1.csv failed: fetch failed',
|
|
596
|
+
)
|
|
597
|
+
})
|
|
598
|
+
})
|
|
501
599
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ApiClient, HttpMethod } from './ApiClient'
|
|
2
2
|
import { ApiConnectionError, ApiError } from './errors'
|
|
3
3
|
import { DEFAULT_QUERY_FORMAT, type QueryFormat } from './queryString'
|
|
4
|
+
import { requestExternal } from './requestExternal'
|
|
4
5
|
|
|
5
6
|
export interface BaseApiClientOptions {
|
|
6
7
|
/** How list/get queries are serialized in the URL. Defaults to `'json'`. */
|
|
@@ -29,6 +30,53 @@ export class BaseApiClient implements ApiClient {
|
|
|
29
30
|
body?: TBody,
|
|
30
31
|
headers?: Record<string, string>,
|
|
31
32
|
): Promise<TResponse> {
|
|
33
|
+
return this.handleResponse<TResponse>(await this.send(method, endpoint, body, headers))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sends the request and resolves the raw `Response` of a 2xx answer (headers and body
|
|
38
|
+
* untouched, so a binary body can be read with `arrayBuffer()`). Non-2xx throws the
|
|
39
|
+
* `ApiError` subclass, as {@link request} does.
|
|
40
|
+
*/
|
|
41
|
+
async requestRaw<TBody = unknown>(
|
|
42
|
+
method: HttpMethod,
|
|
43
|
+
endpoint: string,
|
|
44
|
+
body?: TBody,
|
|
45
|
+
headers?: Record<string, string>,
|
|
46
|
+
): Promise<Response> {
|
|
47
|
+
const response = await this.send(method, endpoint, body, headers)
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw ApiError.fromResponse(
|
|
50
|
+
response.status,
|
|
51
|
+
response.statusText,
|
|
52
|
+
response.headers,
|
|
53
|
+
await this.readErrorBody(response),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return response
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sends a request to an absolute URL outside the API, without the base URL, the access token
|
|
61
|
+
* and the JSON serialization. Resolves the raw `Response` of a 2xx answer; non-2xx throws the
|
|
62
|
+
* `ApiError` subclass. Override it to intercept those requests as well.
|
|
63
|
+
*/
|
|
64
|
+
requestExternal(
|
|
65
|
+
method: HttpMethod,
|
|
66
|
+
url: string,
|
|
67
|
+
body?: BodyInit,
|
|
68
|
+
headers?: Record<string, string>,
|
|
69
|
+
): Promise<Response> {
|
|
70
|
+
return requestExternal(method, url, body, headers)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Builds and sends the request; a network failure is thrown as `ApiConnectionError`. */
|
|
74
|
+
protected async send<TBody = unknown>(
|
|
75
|
+
method: HttpMethod,
|
|
76
|
+
endpoint: string,
|
|
77
|
+
body?: TBody,
|
|
78
|
+
headers?: Record<string, string>,
|
|
79
|
+
): Promise<Response> {
|
|
32
80
|
const url = `${this.baseUrl}/${endpoint.replace(/^\//, '')}`
|
|
33
81
|
const init: RequestInit = {
|
|
34
82
|
method,
|
|
@@ -40,17 +88,14 @@ export class BaseApiClient implements ApiClient {
|
|
|
40
88
|
...(body !== undefined && { body: JSON.stringify(body) }),
|
|
41
89
|
}
|
|
42
90
|
|
|
43
|
-
let response: Response
|
|
44
91
|
try {
|
|
45
|
-
|
|
92
|
+
return await fetch(url, init)
|
|
46
93
|
} catch (error) {
|
|
47
94
|
const reason = error instanceof Error ? error.message : String(error)
|
|
48
95
|
throw new ApiConnectionError(`Request to ${method} ${url} failed: ${reason}`, {
|
|
49
96
|
cause: error,
|
|
50
97
|
})
|
|
51
98
|
}
|
|
52
|
-
|
|
53
|
-
return this.handleResponse<TResponse>(response)
|
|
54
99
|
}
|
|
55
100
|
|
|
56
101
|
protected async handleResponse<TResponse>(response: Response): Promise<TResponse> {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HttpMethod } from './ApiClient'
|
|
2
|
+
import { ApiConnectionError, ApiError } from './errors'
|
|
3
|
+
|
|
4
|
+
/** The URL without its query string, which may carry a signature that must not reach the logs. */
|
|
5
|
+
function withoutQuery(url: string): string {
|
|
6
|
+
return url.replace(/[?#].*$/s, '')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sends a request to an absolute URL outside the API (e.g. a signed storage URL): no base URL, no
|
|
11
|
+
* `Authorization` header and no JSON serialization, `body` goes as is. Resolves the raw `Response`
|
|
12
|
+
* of a 2xx answer; non-2xx throws the `ApiError` subclass with the body as text, and a network
|
|
13
|
+
* failure throws `ApiConnectionError`.
|
|
14
|
+
*/
|
|
15
|
+
export async function requestExternal(
|
|
16
|
+
method: HttpMethod,
|
|
17
|
+
url: string,
|
|
18
|
+
body?: BodyInit,
|
|
19
|
+
headers?: Record<string, string>,
|
|
20
|
+
): Promise<Response> {
|
|
21
|
+
let response: Response
|
|
22
|
+
try {
|
|
23
|
+
response = await fetch(url, {
|
|
24
|
+
method,
|
|
25
|
+
...(headers && { headers }),
|
|
26
|
+
...(body !== undefined && { body }),
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
30
|
+
throw new ApiConnectionError(`Request to ${method} ${withoutQuery(url)} failed: ${reason}`, {
|
|
31
|
+
cause: error,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw ApiError.fromResponse(
|
|
36
|
+
response.status,
|
|
37
|
+
response.statusText,
|
|
38
|
+
response.headers,
|
|
39
|
+
(await response.text().catch(() => undefined)) || undefined,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
return response
|
|
43
|
+
}
|