@abyss-project/link 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.eslintrc.js +32 -0
  2. package/.prettierrc +5 -0
  3. package/README.md +21 -2
  4. package/dist/api/abyss.admin.api.d.ts +4 -0
  5. package/dist/api/abyss.admin.api.js +17 -0
  6. package/dist/api/abyss.admin.api.js.map +1 -0
  7. package/dist/api/index.d.ts +4 -0
  8. package/dist/api/index.js +21 -0
  9. package/dist/api/index.js.map +1 -0
  10. package/dist/api/link.admin.api.d.ts +14 -0
  11. package/dist/api/link.admin.api.js +64 -0
  12. package/dist/api/link.admin.api.js.map +1 -0
  13. package/dist/api/link.api.d.ts +15 -0
  14. package/dist/api/link.api.js +69 -0
  15. package/dist/api/link.api.js.map +1 -0
  16. package/dist/api/link.public.api.d.ts +2 -0
  17. package/dist/api/link.public.api.js +9 -0
  18. package/dist/api/link.public.api.js.map +1 -0
  19. package/dist/constants/index.d.ts +1 -0
  20. package/dist/constants/index.js +18 -0
  21. package/dist/constants/index.js.map +1 -0
  22. package/dist/constants/link.constants.d.ts +25 -0
  23. package/dist/constants/link.constants.js +48 -0
  24. package/dist/constants/link.constants.js.map +1 -0
  25. package/dist/index.d.ts +70 -0
  26. package/dist/index.js +157 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/types/enum/api-error.enum.d.ts +24 -0
  29. package/dist/types/enum/api-error.enum.js +30 -0
  30. package/dist/types/enum/api-error.enum.js.map +1 -0
  31. package/dist/types/enum/index.d.ts +4 -0
  32. package/dist/types/enum/index.js +21 -0
  33. package/dist/types/enum/index.js.map +1 -0
  34. package/dist/types/enum/link-share.enum.d.ts +11 -0
  35. package/dist/types/enum/link-share.enum.js +17 -0
  36. package/dist/types/enum/link-share.enum.js.map +1 -0
  37. package/dist/types/enum/link-stats.enum.d.ts +5 -0
  38. package/dist/types/enum/link-stats.enum.js +10 -0
  39. package/dist/types/enum/link-stats.enum.js.map +1 -0
  40. package/dist/types/enum/link.enum.d.ts +20 -0
  41. package/dist/types/enum/link.enum.js +27 -0
  42. package/dist/types/enum/link.enum.js.map +1 -0
  43. package/dist/types/index.d.ts +2 -0
  44. package/dist/types/index.js +19 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/types/interface/api/index.d.ts +5 -0
  47. package/dist/types/interface/api/index.js +22 -0
  48. package/dist/types/interface/api/index.js.map +1 -0
  49. package/dist/types/interface/api/requests/abyss.admin.request.d.ts +14 -0
  50. package/dist/types/interface/api/requests/abyss.admin.request.js +3 -0
  51. package/dist/types/interface/api/requests/abyss.admin.request.js.map +1 -0
  52. package/dist/types/interface/api/requests/index.d.ts +4 -0
  53. package/dist/types/interface/api/requests/index.js +21 -0
  54. package/dist/types/interface/api/requests/index.js.map +1 -0
  55. package/dist/types/interface/api/requests/link.admin.request.d.ts +69 -0
  56. package/dist/types/interface/api/requests/link.admin.request.js +3 -0
  57. package/dist/types/interface/api/requests/link.admin.request.js.map +1 -0
  58. package/dist/types/interface/api/requests/link.public.request.d.ts +13 -0
  59. package/dist/types/interface/api/requests/link.public.request.js +3 -0
  60. package/dist/types/interface/api/requests/link.public.request.js.map +1 -0
  61. package/dist/types/interface/api/requests/link.request.d.ts +74 -0
  62. package/dist/types/interface/api/requests/link.request.js +3 -0
  63. package/dist/types/interface/api/requests/link.request.js.map +1 -0
  64. package/dist/types/interface/api/responses/abyss.admin.response.d.ts +14 -0
  65. package/dist/types/interface/api/responses/abyss.admin.response.js +3 -0
  66. package/dist/types/interface/api/responses/abyss.admin.response.js.map +1 -0
  67. package/dist/types/interface/api/responses/index.d.ts +4 -0
  68. package/dist/types/interface/api/responses/index.js +21 -0
  69. package/dist/types/interface/api/responses/index.js.map +1 -0
  70. package/dist/types/interface/api/responses/link.admin.response.d.ts +43 -0
  71. package/dist/types/interface/api/responses/link.admin.response.js +3 -0
  72. package/dist/types/interface/api/responses/link.admin.response.js.map +1 -0
  73. package/dist/types/interface/api/responses/link.public.response.d.ts +5 -0
  74. package/dist/types/interface/api/responses/link.public.response.js +3 -0
  75. package/dist/types/interface/api/responses/link.public.response.js.map +1 -0
  76. package/dist/types/interface/api/responses/link.response.d.ts +43 -0
  77. package/dist/types/interface/api/responses/link.response.js +3 -0
  78. package/dist/types/interface/api/responses/link.response.js.map +1 -0
  79. package/dist/types/interface/api/type-message/api-error.d.ts +6 -0
  80. package/dist/types/interface/api/type-message/api-error.js +3 -0
  81. package/dist/types/interface/api/type-message/api-error.js.map +1 -0
  82. package/dist/types/interface/api/type-message/base-paginate.d.ts +10 -0
  83. package/dist/types/interface/api/type-message/base-paginate.js +3 -0
  84. package/dist/types/interface/api/type-message/base-paginate.js.map +1 -0
  85. package/dist/types/interface/api/type-message/response.d.ts +5 -0
  86. package/dist/types/interface/api/type-message/response.js +3 -0
  87. package/dist/types/interface/api/type-message/response.js.map +1 -0
  88. package/dist/types/interface/index.d.ts +2 -0
  89. package/dist/types/interface/index.js +19 -0
  90. package/dist/types/interface/index.js.map +1 -0
  91. package/dist/types/interface/models/index.d.ts +5 -0
  92. package/dist/types/interface/models/index.js +22 -0
  93. package/dist/types/interface/models/index.js.map +1 -0
  94. package/dist/types/interface/models/link-list.dto.d.ts +20 -0
  95. package/dist/types/interface/models/link-list.dto.js +3 -0
  96. package/dist/types/interface/models/link-list.dto.js.map +1 -0
  97. package/dist/types/interface/models/link.dto.d.ts +91 -0
  98. package/dist/types/interface/models/link.dto.js +3 -0
  99. package/dist/types/interface/models/link.dto.js.map +1 -0
  100. package/dist/types/interface/models/organization.dto.d.ts +11 -0
  101. package/dist/types/interface/models/organization.dto.js +3 -0
  102. package/dist/types/interface/models/organization.dto.js.map +1 -0
  103. package/dist/types/interface/models/project.dto.d.ts +12 -0
  104. package/dist/types/interface/models/project.dto.js +3 -0
  105. package/dist/types/interface/models/project.dto.js.map +1 -0
  106. package/dist/types/interface/models/user.dto.d.ts +11 -0
  107. package/dist/types/interface/models/user.dto.js +3 -0
  108. package/dist/types/interface/models/user.dto.js.map +1 -0
  109. package/dist/utils/enum.utils.d.ts +1 -0
  110. package/dist/utils/enum.utils.js +3 -0
  111. package/dist/utils/enum.utils.js.map +1 -0
  112. package/dist/utils/error.utils.d.ts +15 -0
  113. package/dist/utils/error.utils.js +25 -0
  114. package/dist/utils/error.utils.js.map +1 -0
  115. package/dist/utils/index.d.ts +3 -0
  116. package/dist/utils/index.js +20 -0
  117. package/dist/utils/index.js.map +1 -0
  118. package/dist/utils/link-slug.utils.d.ts +1 -0
  119. package/dist/utils/link-slug.utils.js +11 -0
  120. package/dist/utils/link-slug.utils.js.map +1 -0
  121. package/jest.config.cjs +9 -0
  122. package/package.json +62 -4
  123. package/src/api/abyss.admin.api.ts +39 -0
  124. package/src/api/index.ts +4 -0
  125. package/src/api/link.admin.api.ts +168 -0
  126. package/src/api/link.api.ts +158 -0
  127. package/src/api/link.public.api.ts +19 -0
  128. package/src/constants/index.ts +1 -0
  129. package/src/constants/link.constants.ts +66 -0
  130. package/src/constants/tests/link-forbidden-slugs.spec.ts +103 -0
  131. package/src/index.ts +180 -0
  132. package/src/tests/link-public-api.spec.ts +37 -0
  133. package/src/tests/link-sdk-surface.spec.ts +191 -0
  134. package/src/tests/sdk-surface.spec.ts +63 -0
  135. package/src/types/enum/api-error.enum.ts +31 -0
  136. package/src/types/enum/index.ts +4 -0
  137. package/src/types/enum/link-share.enum.ts +17 -0
  138. package/src/types/enum/link-stats.enum.ts +6 -0
  139. package/src/types/enum/link.enum.ts +34 -0
  140. package/src/types/index.ts +2 -0
  141. package/src/types/interface/api/index.ts +5 -0
  142. package/src/types/interface/api/requests/abyss.admin.request.ts +17 -0
  143. package/src/types/interface/api/requests/index.ts +4 -0
  144. package/src/types/interface/api/requests/link.admin.request.ts +104 -0
  145. package/src/types/interface/api/requests/link.public.request.ts +18 -0
  146. package/src/types/interface/api/requests/link.request.ts +100 -0
  147. package/src/types/interface/api/responses/abyss.admin.response.ts +17 -0
  148. package/src/types/interface/api/responses/index.ts +4 -0
  149. package/src/types/interface/api/responses/link.admin.response.ts +56 -0
  150. package/src/types/interface/api/responses/link.public.response.ts +7 -0
  151. package/src/types/interface/api/responses/link.response.ts +55 -0
  152. package/src/types/interface/api/type-message/api-error.ts +7 -0
  153. package/src/types/interface/api/type-message/base-paginate.ts +11 -0
  154. package/src/types/interface/api/type-message/response.ts +6 -0
  155. package/src/types/interface/index.ts +2 -0
  156. package/src/types/interface/models/index.ts +5 -0
  157. package/src/types/interface/models/link-list.dto.ts +28 -0
  158. package/src/types/interface/models/link.dto.ts +117 -0
  159. package/src/types/interface/models/organization.dto.ts +21 -0
  160. package/src/types/interface/models/project.dto.ts +16 -0
  161. package/src/types/interface/models/user.dto.ts +19 -0
  162. package/src/types/tests/link-enums.spec.ts +48 -0
  163. package/src/utils/enum.utils.ts +1 -0
  164. package/src/utils/error.utils.ts +30 -0
  165. package/src/utils/index.ts +3 -0
  166. package/src/utils/link-slug.utils.ts +10 -0
  167. package/tsconfig.check.json +15 -0
  168. package/tsconfig.json +36 -0
package/src/index.ts ADDED
@@ -0,0 +1,180 @@
1
+ import axios from 'axios';
2
+ import axiosRetry, { IAxiosRetryConfig, isNetworkOrIdempotentRequestError } from 'axios-retry';
3
+
4
+ export * from './api';
5
+ export * from './types';
6
+ export * from './utils';
7
+ export * from './constants';
8
+
9
+ import * as abyssAdminApi from './api/abyss.admin.api';
10
+ import * as linkApi from './api/link.api';
11
+ import * as linkAdminApi from './api/link.admin.api';
12
+ import * as linkPublicApi from './api/link.public.api';
13
+
14
+ const DEFAULT_BASE_URL = 'https://link-api.abyss-project.fr/api/';
15
+
16
+ export const API_KEY_HEADER = 'abyss-api-key';
17
+ export const ADMIN_TOKEN_HEADER = 'abyss-admin-token';
18
+
19
+ const NUMBER_RETRY_API = 10;
20
+ const BASE_DELAY_BETWEEN_RETRY = 5000;
21
+ const RETRY_CODES = [
22
+ 502, // Bad Gateway
23
+ 503, // Service Unavailable
24
+ ];
25
+
26
+ type CoreConfig = {
27
+ token?: string;
28
+ apiKey?: string;
29
+ adminToken?: string;
30
+ baseURL?: string;
31
+ };
32
+
33
+ type AbyssLinkCoreSDK = {
34
+ abyss: {
35
+ admin: {
36
+ activateUserService: typeof abyssAdminApi.activateUserServiceAbyssAdmin;
37
+ syncOrganization: typeof abyssAdminApi.syncOrganizationAbyssAdmin;
38
+ syncProject: typeof abyssAdminApi.syncProjectAbyssAdmin;
39
+ };
40
+ };
41
+ link: {
42
+ get: typeof linkApi.getLink;
43
+ paginate: typeof linkApi.paginateLink;
44
+ paginateProject: typeof linkApi.paginateProjectLink;
45
+ paginateOrganization: typeof linkApi.paginateOrganizationLink;
46
+ countOrganization: typeof linkApi.countOrganizationLink;
47
+ create: typeof linkApi.createLink;
48
+ createForOrganization: typeof linkApi.createForOrganizationLink;
49
+ update: typeof linkApi.updateLink;
50
+ delete: typeof linkApi.deleteLink;
51
+ transfer: typeof linkApi.transferLink;
52
+ getStats: typeof linkApi.getStatsLink;
53
+ getVisits: typeof linkApi.getVisitsLink;
54
+ getByOrigin: typeof linkApi.getByOriginLink;
55
+ getStatsByOrigin: typeof linkApi.getStatsByOriginLink;
56
+ public: {
57
+ unlock: typeof linkPublicApi.unlockLinkPublic;
58
+ };
59
+ admin: {
60
+ paginate: typeof linkAdminApi.paginateLinkAdmin;
61
+ get: typeof linkAdminApi.getLinkAdmin;
62
+ getVisits: typeof linkAdminApi.getVisitsLinkAdmin;
63
+ getCampaignStats: typeof linkAdminApi.getCampaignStatsLinkAdmin;
64
+ setProtection: typeof linkAdminApi.setProtectionLinkAdmin;
65
+ update: typeof linkAdminApi.updateLinkAdmin;
66
+ delete: typeof linkAdminApi.deleteLinkAdmin;
67
+ create: typeof linkAdminApi.createLinkAdmin;
68
+ getByOrigin: typeof linkAdminApi.getByOriginLinkAdmin;
69
+ getStatsByOrigin: typeof linkAdminApi.getStatsByOriginLinkAdmin;
70
+ recordCampaignView: typeof linkAdminApi.recordCampaignViewLinkAdmin;
71
+ listCampaignTokens: typeof linkAdminApi.listCampaignTokensLinkAdmin;
72
+ deleteCampaignToken: typeof linkAdminApi.deleteCampaignTokenLinkAdmin;
73
+ };
74
+ };
75
+ };
76
+
77
+ export class AbyssLinkCore {
78
+ private static config = {
79
+ token: '',
80
+ apiKey: '',
81
+ adminToken: '',
82
+ baseURL: DEFAULT_BASE_URL,
83
+ };
84
+ static axios = axios.create({
85
+ baseURL: this.config.baseURL,
86
+ withCredentials: true,
87
+ });
88
+
89
+ private static _sdk: AbyssLinkCoreSDK | null = null;
90
+
91
+ public static get sdk(): AbyssLinkCoreSDK {
92
+ if (!AbyssLinkCore._sdk) {
93
+ AbyssLinkCore._sdk = AbyssLinkCore.buildSDK();
94
+ }
95
+ return AbyssLinkCore._sdk;
96
+ }
97
+
98
+ private static buildSDK(): AbyssLinkCoreSDK {
99
+ return {
100
+ abyss: {
101
+ admin: {
102
+ activateUserService: abyssAdminApi.activateUserServiceAbyssAdmin,
103
+ syncOrganization: abyssAdminApi.syncOrganizationAbyssAdmin,
104
+ syncProject: abyssAdminApi.syncProjectAbyssAdmin,
105
+ },
106
+ },
107
+ link: {
108
+ get: linkApi.getLink,
109
+ paginate: linkApi.paginateLink,
110
+ paginateProject: linkApi.paginateProjectLink,
111
+ paginateOrganization: linkApi.paginateOrganizationLink,
112
+ countOrganization: linkApi.countOrganizationLink,
113
+ create: linkApi.createLink,
114
+ createForOrganization: linkApi.createForOrganizationLink,
115
+ update: linkApi.updateLink,
116
+ delete: linkApi.deleteLink,
117
+ transfer: linkApi.transferLink,
118
+ getStats: linkApi.getStatsLink,
119
+ getVisits: linkApi.getVisitsLink,
120
+ getByOrigin: linkApi.getByOriginLink,
121
+ getStatsByOrigin: linkApi.getStatsByOriginLink,
122
+ public: {
123
+ unlock: linkPublicApi.unlockLinkPublic,
124
+ },
125
+ admin: {
126
+ paginate: linkAdminApi.paginateLinkAdmin,
127
+ get: linkAdminApi.getLinkAdmin,
128
+ getVisits: linkAdminApi.getVisitsLinkAdmin,
129
+ getCampaignStats: linkAdminApi.getCampaignStatsLinkAdmin,
130
+ setProtection: linkAdminApi.setProtectionLinkAdmin,
131
+ update: linkAdminApi.updateLinkAdmin,
132
+ delete: linkAdminApi.deleteLinkAdmin,
133
+ create: linkAdminApi.createLinkAdmin,
134
+ getByOrigin: linkAdminApi.getByOriginLinkAdmin,
135
+ getStatsByOrigin: linkAdminApi.getStatsByOriginLinkAdmin,
136
+ recordCampaignView: linkAdminApi.recordCampaignViewLinkAdmin,
137
+ listCampaignTokens: linkAdminApi.listCampaignTokensLinkAdmin,
138
+ deleteCampaignToken: linkAdminApi.deleteCampaignTokenLinkAdmin,
139
+ },
140
+ },
141
+ };
142
+ }
143
+
144
+ public static enableAxiosRetry(axiosRetryConfig?: IAxiosRetryConfig): void {
145
+ axiosRetry(AbyssLinkCore.axios, {
146
+ retries: NUMBER_RETRY_API,
147
+ retryDelay: (retryCount) => retryCount * BASE_DELAY_BETWEEN_RETRY,
148
+ retryCondition: (error) =>
149
+ isNetworkOrIdempotentRequestError(error) ||
150
+ RETRY_CODES.includes(error.response?.status || 502),
151
+ ...axiosRetryConfig,
152
+ });
153
+ }
154
+
155
+ public static setConfig(config: CoreConfig): void {
156
+ AbyssLinkCore._sdk = null;
157
+
158
+ AbyssLinkCore.config = { ...AbyssLinkCore.config, ...config };
159
+
160
+ if (config.baseURL) {
161
+ AbyssLinkCore.axios.defaults.baseURL = config.baseURL;
162
+ }
163
+
164
+ if (config.token) {
165
+ AbyssLinkCore.axios.defaults.headers.common.Authorization = `Bearer ${config.token}`;
166
+ }
167
+
168
+ if (config.apiKey) {
169
+ AbyssLinkCore.axios.defaults.headers.common[API_KEY_HEADER] = `${config.apiKey}`;
170
+ }
171
+
172
+ if (config.adminToken) {
173
+ AbyssLinkCore.axios.defaults.headers.common[ADMIN_TOKEN_HEADER] = `${config.adminToken}`;
174
+ }
175
+ }
176
+
177
+ public static getConfig(): CoreConfig {
178
+ return AbyssLinkCore.config;
179
+ }
180
+ }
@@ -0,0 +1,37 @@
1
+ import { AbyssLinkCore } from '..';
2
+
3
+ describe('AbyssLinkCore.sdk.link.public', () => {
4
+ let post: jest.SpyInstance;
5
+
6
+ beforeEach(() => {
7
+ post = jest
8
+ .spyOn(AbyssLinkCore.axios, 'post')
9
+ .mockResolvedValue({ data: { data: { url: 'https://example.com/landing' } } });
10
+ });
11
+
12
+ afterEach(() => {
13
+ jest.restoreAllMocks();
14
+ });
15
+
16
+ it('unlocks with POST link/public/<slug>/unlock, the password and the visit data in the body', async () => {
17
+ const answer = await AbyssLinkCore.sdk.link.public.unlock({
18
+ slug: 'Launch',
19
+ password: 'open sesame',
20
+ ref: 'https://news.example.org/a',
21
+ utmSource: 'mail',
22
+ });
23
+
24
+ expect(post).toHaveBeenCalledWith('link/public/Launch/unlock', {
25
+ password: 'open sesame',
26
+ ref: 'https://news.example.org/a',
27
+ utmSource: 'mail',
28
+ });
29
+ expect(answer).toEqual({ data: { url: 'https://example.com/landing' } });
30
+ });
31
+
32
+ it('encodes the slug in the path', async () => {
33
+ await AbyssLinkCore.sdk.link.public.unlock({ slug: 'a/b', password: 'x' });
34
+
35
+ expect(post).toHaveBeenCalledWith('link/public/a%2Fb/unlock', { password: 'x' });
36
+ });
37
+ });
@@ -0,0 +1,191 @@
1
+ import { AbyssLinkCore, LinkKind, LinkResourceProduct, LinkStatsGranularity } from '..';
2
+
3
+ const LINK_ID = '11111111-1111-4111-8111-111111111111';
4
+ const ORGANIZATION_ID = '22222222-2222-4222-8222-222222222222';
5
+ const PROJECT_ID = '33333333-3333-4333-8333-333333333333';
6
+ const ORIGIN = {
7
+ service: LinkResourceProduct.BEAM,
8
+ resourceType: 'beam_transfer',
9
+ resourceId: '44444444-4444-4444-8444-444444444444',
10
+ };
11
+
12
+ /**
13
+ * `sdk.link.*` (the user routes of the Links screen) and `sdk.link.admin.*` (the back-office and
14
+ * Monitor): each call hits the route `link.controller.ts` / `link.admin.controller.ts` serve.
15
+ */
16
+ describe('AbyssLinkCore sdk.link surface', () => {
17
+ let get: jest.SpyInstance;
18
+ let post: jest.SpyInstance;
19
+ let put: jest.SpyInstance;
20
+ let del: jest.SpyInstance;
21
+
22
+ beforeEach(() => {
23
+ const ok = { data: { data: {} } };
24
+ get = jest.spyOn(AbyssLinkCore.axios, 'get').mockResolvedValue(ok);
25
+ post = jest.spyOn(AbyssLinkCore.axios, 'post').mockResolvedValue(ok);
26
+ put = jest.spyOn(AbyssLinkCore.axios, 'put').mockResolvedValue(ok);
27
+ del = jest.spyOn(AbyssLinkCore.axios, 'delete').mockResolvedValue(ok);
28
+ });
29
+
30
+ afterEach(() => {
31
+ jest.restoreAllMocks();
32
+ });
33
+
34
+ it('exposes the user routes and the admin routes', () => {
35
+ expect(Object.keys(AbyssLinkCore.sdk.link)).toEqual(
36
+ expect.arrayContaining([
37
+ 'get',
38
+ 'paginate',
39
+ 'paginateProject',
40
+ 'paginateOrganization',
41
+ 'countOrganization',
42
+ 'create',
43
+ 'createForOrganization',
44
+ 'update',
45
+ 'delete',
46
+ 'transfer',
47
+ 'getStats',
48
+ 'getVisits',
49
+ 'getByOrigin',
50
+ 'getStatsByOrigin',
51
+ 'admin',
52
+ ]),
53
+ );
54
+ expect(Object.keys(AbyssLinkCore.sdk.link.admin).sort()).toEqual([
55
+ 'create',
56
+ 'delete',
57
+ 'deleteCampaignToken',
58
+ 'get',
59
+ 'getByOrigin',
60
+ 'getCampaignStats',
61
+ 'getStatsByOrigin',
62
+ 'getVisits',
63
+ 'listCampaignTokens',
64
+ 'paginate',
65
+ 'recordCampaignView',
66
+ 'setProtection',
67
+ 'update',
68
+ ]);
69
+ });
70
+
71
+ it('reads the user routes', async () => {
72
+ const { link } = AbyssLinkCore.sdk;
73
+ await link.get({ linkId: LINK_ID });
74
+ expect(get).toHaveBeenLastCalledWith(`link/${LINK_ID}`);
75
+ await link.paginate({ page: 2, search: 'promo' });
76
+ expect(get).toHaveBeenLastCalledWith('link/paginate', { params: { page: 2, search: 'promo' } });
77
+ await link.paginateProject({ projectId: PROJECT_ID }, { page: 1 });
78
+ expect(get).toHaveBeenLastCalledWith(`link/project/${PROJECT_ID}/paginate`, {
79
+ params: { page: 1 },
80
+ });
81
+ await link.paginateOrganization({ organizationId: ORGANIZATION_ID }, { adminView: true });
82
+ expect(get).toHaveBeenLastCalledWith(`link/organization/${ORGANIZATION_ID}/paginate`, {
83
+ params: { adminView: true },
84
+ });
85
+ await link.countOrganization({ organizationId: ORGANIZATION_ID });
86
+ expect(get).toHaveBeenLastCalledWith(`link/organization/${ORGANIZATION_ID}/count`, {
87
+ params: {},
88
+ });
89
+ await link.getStats({ linkId: LINK_ID }, { granularity: LinkStatsGranularity.WEEK });
90
+ expect(get).toHaveBeenLastCalledWith(`link/${LINK_ID}/stats`, {
91
+ params: { granularity: LinkStatsGranularity.WEEK },
92
+ });
93
+ await link.getVisits({ linkId: LINK_ID }, { page: 3 });
94
+ expect(get).toHaveBeenLastCalledWith(`link/${LINK_ID}/visits`, { params: { page: 3 } });
95
+ await link.getByOrigin(ORIGIN);
96
+ expect(get).toHaveBeenLastCalledWith('link/origin', { params: ORIGIN });
97
+ await link.getStatsByOrigin({ ...ORIGIN, from: '2026-11-01' });
98
+ expect(get).toHaveBeenLastCalledWith('link/origin/stats', {
99
+ params: { ...ORIGIN, from: '2026-11-01' },
100
+ });
101
+ });
102
+
103
+ it('writes through the user routes', async () => {
104
+ const { link } = AbyssLinkCore.sdk;
105
+ await link.create({ targetUrl: 'https://example.com' });
106
+ expect(post).toHaveBeenLastCalledWith('link', { targetUrl: 'https://example.com' });
107
+ await link.createForOrganization(
108
+ { organizationId: ORGANIZATION_ID },
109
+ { targetUrl: 'https://example.com', projectId: PROJECT_ID },
110
+ );
111
+ expect(post).toHaveBeenLastCalledWith(`link/organization/${ORGANIZATION_ID}`, {
112
+ targetUrl: 'https://example.com',
113
+ projectId: PROJECT_ID,
114
+ });
115
+ await link.update({ linkId: LINK_ID }, { isActive: false });
116
+ expect(put).toHaveBeenLastCalledWith(`link/${LINK_ID}`, { isActive: false });
117
+ await link.transfer({ linkId: LINK_ID }, { newOwnerId: PROJECT_ID });
118
+ expect(post).toHaveBeenLastCalledWith(`link/${LINK_ID}/transfer`, { newOwnerId: PROJECT_ID });
119
+ await link.delete({ linkId: LINK_ID });
120
+ expect(del).toHaveBeenLastCalledWith(`link/${LINK_ID}`);
121
+ });
122
+
123
+ it('calls the admin routes', async () => {
124
+ const { admin } = AbyssLinkCore.sdk.link;
125
+ await admin.paginate({ isProtected: true });
126
+ expect(get).toHaveBeenLastCalledWith('admin/link/paginate', {
127
+ params: { isProtected: true },
128
+ });
129
+ await admin.get({ linkId: LINK_ID });
130
+ expect(get).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}`);
131
+ await admin.getVisits({ linkId: LINK_ID }, { limit: 10 });
132
+ expect(get).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}/visits`, {
133
+ params: { limit: 10 },
134
+ });
135
+ await admin.getCampaignStats(
136
+ { linkId: LINK_ID },
137
+ { from: '2026-11-01T00:00:00.000Z', to: '2026-11-11T00:00:00.000Z' },
138
+ );
139
+ expect(get).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}/campaign-stats`, {
140
+ params: { from: '2026-11-01T00:00:00.000Z', to: '2026-11-11T00:00:00.000Z' },
141
+ });
142
+ await admin.setProtection({ linkId: LINK_ID }, { isProtected: true, adminId: PROJECT_ID });
143
+ expect(put).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}/protection`, {
144
+ isProtected: true,
145
+ adminId: PROJECT_ID,
146
+ });
147
+ await admin.update({ linkId: LINK_ID }, { isActive: false });
148
+ expect(put).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}`, { isActive: false });
149
+ await admin.delete({ linkId: LINK_ID });
150
+ expect(del).toHaveBeenLastCalledWith(`admin/link/${LINK_ID}`);
151
+ });
152
+
153
+ it('calls the product routes (T3.1)', async () => {
154
+ const { admin } = AbyssLinkCore.sdk.link;
155
+ const scope = { userId: null, organizationId: ORGANIZATION_ID, projectId: PROJECT_ID };
156
+ await admin.create({
157
+ kind: LinkKind.RESOURCE,
158
+ origin: ORIGIN,
159
+ targetUrl: 'https://beam.example.com/b/x',
160
+ scope,
161
+ });
162
+ expect(post).toHaveBeenLastCalledWith('admin/link', {
163
+ kind: LinkKind.RESOURCE,
164
+ origin: ORIGIN,
165
+ targetUrl: 'https://beam.example.com/b/x',
166
+ scope,
167
+ });
168
+ await admin.getByOrigin(ORIGIN);
169
+ expect(get).toHaveBeenLastCalledWith('admin/link/origin', { params: ORIGIN });
170
+ await admin.getStatsByOrigin(ORIGIN, { granularity: LinkStatsGranularity.MONTH });
171
+ expect(get).toHaveBeenLastCalledWith('admin/link/origin/stats', {
172
+ params: { ...ORIGIN, granularity: LinkStatsGranularity.MONTH },
173
+ });
174
+ await admin.getStatsByOrigin(ORIGIN);
175
+ expect(get).toHaveBeenLastCalledWith('admin/link/origin/stats', { params: ORIGIN });
176
+ const view = { token: 'Ab3dE5gH9kLm', ip: '203.0.113.7', utm: { utmSource: 'mail' } };
177
+ await admin.recordCampaignView(view);
178
+ expect(post).toHaveBeenLastCalledWith('admin/link/campaign-view', view);
179
+ await admin.listCampaignTokens(ORIGIN);
180
+ expect(get).toHaveBeenLastCalledWith('admin/link/campaign-token', { params: ORIGIN });
181
+ await admin.deleteCampaignToken({ linkId: LINK_ID });
182
+ expect(del).toHaveBeenLastCalledWith(`admin/link/campaign-token/${LINK_ID}`);
183
+ });
184
+
185
+ it('returns the response body', async () => {
186
+ get.mockResolvedValueOnce({ data: { data: { link: { id: LINK_ID } } } });
187
+ await expect(AbyssLinkCore.sdk.link.get({ linkId: LINK_ID })).resolves.toEqual({
188
+ data: { link: { id: LINK_ID } },
189
+ });
190
+ });
191
+ });
@@ -0,0 +1,63 @@
1
+ import { AbyssLinkCore, ADMIN_TOKEN_HEADER } from '..';
2
+
3
+ const USER_ID = '11111111-1111-4111-8111-111111111111';
4
+ const ORGANIZATION_ID = '22222222-2222-4222-8222-222222222222';
5
+ const PROJECT_ID = '33333333-3333-4333-8333-333333333333';
6
+
7
+ describe('AbyssLinkCore SDK surface', () => {
8
+ let post: jest.SpyInstance;
9
+
10
+ beforeEach(() => {
11
+ post = jest.spyOn(AbyssLinkCore.axios, 'post').mockResolvedValue({ data: { data: {} } });
12
+ });
13
+
14
+ afterEach(() => {
15
+ jest.restoreAllMocks();
16
+ });
17
+
18
+ it('exposes exactly the three abyss.admin calls', () => {
19
+ expect(Object.keys(AbyssLinkCore.sdk)).toEqual(['abyss', 'link']);
20
+ expect(Object.keys(AbyssLinkCore.sdk.abyss)).toEqual(['admin']);
21
+ expect(Object.keys(AbyssLinkCore.sdk.abyss.admin).sort()).toEqual([
22
+ 'activateUserService',
23
+ 'syncOrganization',
24
+ 'syncProject',
25
+ ]);
26
+ });
27
+
28
+ it('activates a user with POST abyss/admin/user/activate-service', async () => {
29
+ const body = { userId: USER_ID };
30
+ await AbyssLinkCore.sdk.abyss.admin.activateUserService(body);
31
+ expect(post).toHaveBeenCalledWith('abyss/admin/user/activate-service', body);
32
+ });
33
+
34
+ it('syncs an organization with POST abyss/admin/organization/sync', async () => {
35
+ const body = { id: ORGANIZATION_ID, name: 'Org', slug: 'org' };
36
+ await AbyssLinkCore.sdk.abyss.admin.syncOrganization(body);
37
+ expect(post).toHaveBeenCalledWith('abyss/admin/organization/sync', body);
38
+ });
39
+
40
+ it('syncs a project with POST abyss/admin/project/sync', async () => {
41
+ const body = {
42
+ id: PROJECT_ID,
43
+ name: 'Project',
44
+ createdByUserId: null,
45
+ organizationId: ORGANIZATION_ID,
46
+ };
47
+ await AbyssLinkCore.sdk.abyss.admin.syncProject(body);
48
+ expect(post).toHaveBeenCalledWith('abyss/admin/project/sync', body);
49
+ });
50
+
51
+ it('returns the response body', async () => {
52
+ post.mockResolvedValueOnce({ data: { data: { user: { id: USER_ID, isAdmin: false } } } });
53
+ await expect(
54
+ AbyssLinkCore.sdk.abyss.admin.activateUserService({ userId: USER_ID }),
55
+ ).resolves.toEqual({ data: { user: { id: USER_ID, isAdmin: false } } });
56
+ });
57
+
58
+ it('sends the admin token and the base URL given to setConfig', () => {
59
+ AbyssLinkCore.setConfig({ adminToken: 'admin-token', baseURL: 'http://localhost:10144/api/' });
60
+ expect(AbyssLinkCore.axios.defaults.headers.common[ADMIN_TOKEN_HEADER]).toBe('admin-token');
61
+ expect(AbyssLinkCore.axios.defaults.baseURL).toBe('http://localhost:10144/api/');
62
+ });
63
+ });
@@ -0,0 +1,31 @@
1
+ export enum ErrorType {
2
+ apiError = 'api_error',
3
+ authError = 'authentication_error',
4
+ resourceError = 'resource_error',
5
+ }
6
+
7
+ export enum Code {
8
+ // HTTP Error
9
+ tokenFailure = 'token_authentication_failure',
10
+ authenticationFailure = 'authentication_credentials_failure',
11
+ accessNotAllowed = 'access_not_allowed',
12
+ notFound = 'not_found',
13
+ validationError = 'resource_validation_error',
14
+ constraintError = 'resource_constraint_error',
15
+ internalServerError = 'internal_server_error',
16
+ serviceUnavailable = 'service_unavailable',
17
+ invalidData = 'invalid_data',
18
+
19
+ // Link management (422 / 409 / 403 with an explicit code the front can name)
20
+ linkSlugForbidden = 'link_slug_forbidden',
21
+ linkTargetForbidden = 'link_target_forbidden',
22
+ linkSlugTaken = 'link_slug_taken',
23
+ linkProtected = 'link_protected',
24
+ linkManagedByProduct = 'link_managed_by_product',
25
+ // 422: the product resource already holds LINK_CAMPAIGN_TOKENS_PER_PROJECT campaign tokens (O5)
26
+ linkCampaignTokenLimit = 'link_campaign_token_limit',
27
+
28
+ // Link unlock (401 wrong password, 410 a link that can no longer be followed)
29
+ linkWrongPassword = 'link_wrong_password',
30
+ linkExpired = 'link_expired',
31
+ }
@@ -0,0 +1,4 @@
1
+ export * from './api-error.enum';
2
+ export * from './link.enum';
3
+ export * from './link-stats.enum';
4
+ export * from './link-share.enum';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Permission of the caller on a link, from abyss-main's content sharing (spec §5). Same values as
3
+ * `SharePermission` of `@abyss-project/main`, which this SDK does not depend on.
4
+ */
5
+ export enum LinkSharePermission {
6
+ VIEW = 'VIEW',
7
+ EDIT = 'EDIT',
8
+ MANAGE = 'MANAGE',
9
+ }
10
+
11
+ /** Scope of the organization list, same values as `ContentShareScope` of `@abyss-project/main`. */
12
+ export enum LinkShareScope {
13
+ ORGANIZATION = 'ORGANIZATION',
14
+ ORG_USER = 'ORG_USER',
15
+ ORG_TEAM = 'ORG_TEAM',
16
+ ORG_PROJECT = 'ORG_PROJECT',
17
+ }
@@ -0,0 +1,6 @@
1
+ /** Bucket of the series of `getStats`. A bucket sums the daily values of its UTC days. */
2
+ export enum LinkStatsGranularity {
3
+ DAY = 'DAY',
4
+ WEEK = 'WEEK',
5
+ MONTH = 'MONTH',
6
+ }
@@ -0,0 +1,34 @@
1
+ /** The three sorts of link (spec §2). Stored as is in `links.kind`. */
2
+ export enum LinkKind {
3
+ /** Owned by a user, served by Link at `<host>/<slug>`, billed on ABYSS_LINK. */
4
+ SHORTENED = 'SHORTENED',
5
+ /** Owned by a product, served by Link at `<host>/<prefix>/<code>`, never listed, never billed. */
6
+ RESOURCE = 'RESOURCE',
7
+ /** Owned by a user, no Link address: a token the target product (Spotlight) reads from its query string. */
8
+ CAMPAIGN_TOKEN = 'CAMPAIGN_TOKEN',
9
+ }
10
+
11
+ /** Where a visit was recorded. Stored as is in `link_visits.source`. */
12
+ export enum LinkVisitSource {
13
+ /** A 302 served by the Link host. */
14
+ REDIRECT = 'REDIRECT',
15
+ /** A page view a product reported for a campaign token. */
16
+ CAMPAIGN_VIEW = 'CAMPAIGN_VIEW',
17
+ }
18
+
19
+ /**
20
+ * The product a RESOURCE or CAMPAIGN_TOKEN link belongs to (`links.originService`).
21
+ * Link's own list, not main's `AbyssService`: a Link product is not a billing service, and
22
+ * Atlas, Draw and Retrospective are tools of AbyssWorkspace, not services.
23
+ */
24
+ export enum LinkResourceProduct {
25
+ BEAM = 'BEAM',
26
+ SPOTLIGHT = 'SPOTLIGHT',
27
+ ATLAS = 'ATLAS',
28
+ DRAW = 'DRAW',
29
+ RETROSPECTIVE = 'RETROSPECTIVE',
30
+ FORM = 'FORM',
31
+ EXTRACT = 'EXTRACT',
32
+ CLOUD = 'CLOUD',
33
+ MEMORIES = 'MEMORIES',
34
+ }
@@ -0,0 +1,2 @@
1
+ export * from './interface';
2
+ export * from './enum';
@@ -0,0 +1,5 @@
1
+ export * from './requests';
2
+ export * from './responses';
3
+ export * from './type-message/response';
4
+ export * from './type-message/api-error';
5
+ export * from './type-message/base-paginate';
@@ -0,0 +1,17 @@
1
+ export interface IActivateUserServiceAbyssAdminBody {
2
+ userId: string;
3
+ }
4
+
5
+ export interface ISyncOrganizationAbyssAdminBody {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ }
10
+
11
+ export interface ISyncProjectAbyssAdminBody {
12
+ id: string;
13
+ name: string;
14
+ /** null when the author's account was deleted: the project outlives it (#199). */
15
+ createdByUserId: string | null;
16
+ organizationId: string;
17
+ }
@@ -0,0 +1,4 @@
1
+ export * from './abyss.admin.request';
2
+ export * from './link.request';
3
+ export * from './link.admin.request';
4
+ export * from './link.public.request';