@cntrl-site/sdk 0.7.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -8,15 +8,14 @@ const core_1 = require("@cntrl-site/core");
8
8
  const isomorphic_fetch_1 = __importDefault(require("isomorphic-fetch"));
9
9
  const url_1 = require("url");
10
10
  class Client {
11
- constructor(projectId, APIUrl, fetchImpl = isomorphic_fetch_1.default) {
12
- this.projectId = projectId;
13
- this.APIUrl = APIUrl;
11
+ constructor(APIUrl, fetchImpl = isomorphic_fetch_1.default) {
14
12
  this.fetchImpl = fetchImpl;
15
- if (projectId.length === 0) {
16
- throw new Error('CNTRL SDK: Project ID is empty. Did you forget to pass it?');
13
+ this.url = new url_1.URL(APIUrl);
14
+ if (!this.url.username) {
15
+ throw new Error('Project ID is missing in the URL.');
17
16
  }
18
- if (APIUrl.length === 0) {
19
- throw new Error('CNTRL SDK: API URL is empty. Did you forget to pass it?');
17
+ if (!this.url.password) {
18
+ throw new Error('API key is missing in the URL.');
20
19
  }
21
20
  }
22
21
  async getProject() {
@@ -38,8 +37,9 @@ class Client {
38
37
  const articleId = this.findArticleIdByPageSlug(pageSlug, project.pages);
39
38
  const articleResponse = await this.fetchArticle(articleId);
40
39
  const articleData = await articleResponse.json();
41
- const article = core_1.ArticleSchema.parse(articleData);
42
- return article;
40
+ const article = core_1.ArticleSchema.parse(articleData.article);
41
+ const keyframes = core_1.KeyframesSchema.parse(articleData.keyframes);
42
+ return { article, keyframes };
43
43
  }
44
44
  catch (e) {
45
45
  throw e;
@@ -51,12 +51,6 @@ class Client {
51
51
  const typePresets = core_1.TypePresetsSchema.parse(data);
52
52
  return typePresets;
53
53
  }
54
- async getKeyframes(articleId) {
55
- const response = await this.fetchKeyframes(articleId);
56
- const data = await response.json();
57
- const keyframes = core_1.KeyframesSchema.parse(data);
58
- return keyframes;
59
- }
60
54
  static getPageMeta(projectMeta, pageMeta) {
61
55
  return pageMeta.enabled ? {
62
56
  title: pageMeta.title ? pageMeta.title : projectMeta.title,
@@ -67,41 +61,49 @@ class Client {
67
61
  } : projectMeta;
68
62
  }
69
63
  async fetchProject() {
70
- const url = new url_1.URL(`/projects/${this.projectId}`, this.APIUrl);
71
- const response = await this.fetchImpl(url.href);
64
+ const { username: projectId, password: apiKey, origin } = this.url;
65
+ const url = new url_1.URL(`/projects/${projectId}`, origin);
66
+ const response = await this.fetchImpl(url.href, {
67
+ headers: {
68
+ Authorization: `Bearer ${apiKey}`
69
+ }
70
+ });
72
71
  if (!response.ok) {
73
- throw new Error(`Failed to fetch project with id #${this.projectId}: ${response.statusText}`);
72
+ throw new Error(`Failed to fetch project with id #${projectId}: ${response.statusText}`);
74
73
  }
75
74
  return response;
76
75
  }
77
76
  async fetchArticle(articleId) {
78
- const url = new url_1.URL(`/articles/${articleId}`, this.APIUrl);
79
- const response = await this.fetchImpl(url.href);
77
+ const { username: projectId, password: apiKey, origin } = this.url;
78
+ const url = new url_1.URL(`/projects/${projectId}/articles/${articleId}`, origin);
79
+ const response = await this.fetchImpl(url.href, {
80
+ headers: {
81
+ Authorization: `Bearer ${apiKey}`
82
+ }
83
+ });
80
84
  if (!response.ok) {
81
85
  throw new Error(`Failed to fetch article with id #${articleId}: ${response.statusText}`);
82
86
  }
83
87
  return response;
84
88
  }
85
- async fetchKeyframes(articleId) {
86
- const url = new url_1.URL(`/keyframes/${articleId}`, this.APIUrl);
87
- const response = await this.fetchImpl(url.href);
88
- if (!response.ok) {
89
- throw new Error(`Failed to fetch keyframes for the article with id #${articleId}: ${response.statusText}`);
90
- }
91
- return response;
92
- }
93
89
  async fetchTypePresets() {
94
- const url = new url_1.URL(`/projects/${this.projectId}/type-presets`, this.APIUrl);
95
- const response = await this.fetchImpl(url.href);
90
+ const { username: projectId, password: apiKey, origin } = this.url;
91
+ const url = new url_1.URL(`/projects/${projectId}/type-presets`, origin);
92
+ const response = await this.fetchImpl(url.href, {
93
+ headers: {
94
+ Authorization: `Bearer ${apiKey}`
95
+ }
96
+ });
96
97
  if (!response.ok) {
97
- throw new Error(`Failed to fetch type presets for the project with id #${this.projectId}: ${response.statusText}`);
98
+ throw new Error(`Failed to fetch type presets for the project with id #${projectId}: ${response.statusText}`);
98
99
  }
99
100
  return response;
100
101
  }
101
102
  findArticleIdByPageSlug(slug, pages) {
103
+ const { username: projectId } = this.url;
102
104
  const page = pages.find((page) => page.slug === slug);
103
105
  if (!page) {
104
- throw new Error(`Page with a slug ${slug} was not found in project with id #${this.projectId}`);
106
+ throw new Error(`Page with a slug ${slug} was not found in project with id #${projectId}`);
105
107
  }
106
108
  return page.articleId;
107
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk",
3
- "version": "0.7.2",
3
+ "version": "1.0.0",
4
4
  "description": "Generic SDK for use in public websites.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/index.ts",
@@ -7,42 +7,45 @@ import { keyframesMock } from './__mock__/keyframesMock';
7
7
 
8
8
  describe('Client', () => {
9
9
  it('returns project', async () => {
10
+ const projectId = 'MY_PROJECT_ID';
11
+ const apiKey = 'MY_API_KEY';
10
12
  let fetchCalledTimes = 0;
11
- const projectId = 'projectId';
12
- const apiUrl = 'https://api.cntrl.site/';
13
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
13
14
  const fetch = async (url: string) => {
14
15
  fetchCalledTimes += 1;
15
- expect(url).toBe(`${apiUrl}projects/${projectId}`);
16
+ expect(url).toBe(`https://api.cntrl.site/projects/${projectId}`);
16
17
  return Promise.resolve({
17
18
  ok: true,
18
19
  json: () => Promise.resolve(projectMock),
19
20
  statusText: ''
20
21
  });
21
22
  };
22
- const client = new Client(projectId, apiUrl, fetch);
23
+ const client = new Client(apiUrl, fetch);
23
24
  const project = await client.getProject();
24
25
  expect(fetchCalledTimes).toBe(1);
25
26
  expect(project).toEqual(projectMock);
26
27
  });
27
28
 
28
29
  it('throws an error upon project fetch failure', async () => {
29
- const projectId = 'projectId';
30
- const apiUrl = 'https://api.cntrl.site/';
30
+ const projectId = 'MY_PROJECT_ID';
31
+ const apiKey = 'MY_API_KEY';
32
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
31
33
  const fetch = async () => Promise.resolve({
32
34
  ok: false,
33
35
  statusText: 'reason',
34
36
  json: () => Promise.resolve()
35
37
  });
36
- const client = new Client(projectId, apiUrl, fetch);
37
- await expect(client.getProject()).rejects.toEqual(new Error('Failed to fetch project with id #projectId: reason'));
38
+ const client = new Client(apiUrl, fetch);
39
+ await expect(client.getProject()).rejects.toEqual(new Error('Failed to fetch project with id #MY_PROJECT_ID: reason'));
38
40
  });
39
41
 
40
42
  it('returns article by page slug', async () => {
41
43
  let fetchCalledTimes = 0;
42
- const projectId = 'projectId';
43
- const apiUrl = 'https://api.cntrl.site/';
44
- const projectApiUrl = `${apiUrl}projects/projectId`;
45
- const articleApiUrl = `${apiUrl}articles/articleId`;
44
+ const projectId = 'MY_PROJECT_ID';
45
+ const apiKey = 'MY_API_KEY';
46
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
47
+ const projectApiUrl = `https://api.cntrl.site/projects/${projectId}`;
48
+ const articleApiUrl = `https://api.cntrl.site/projects/${projectId}/articles/articleId`;
46
49
  const fetch = async (url: string) => {
47
50
  fetchCalledTimes += 1;
48
51
  if (fetchCalledTimes === 1) {
@@ -53,33 +56,36 @@ describe('Client', () => {
53
56
  }
54
57
  return Promise.resolve({
55
58
  ok: true,
56
- json: () => Promise.resolve(url === projectApiUrl ? projectMock : articleMock),
59
+ json: () => Promise.resolve(url === projectApiUrl ? projectMock : { article: articleMock, keyframes: keyframesMock }),
57
60
  statusText: ''
58
61
  });
59
62
  };
60
- const client = new Client(projectId, apiUrl, fetch);
61
- const article = await client.getPageArticle('/');
63
+ const client = new Client(apiUrl, fetch);
64
+ const { article, keyframes } = await client.getPageArticle('/');
62
65
  expect(fetchCalledTimes).toBe(2);
63
66
  expect(article).toEqual(articleMock);
67
+ expect(keyframes).toEqual(keyframesMock);
64
68
  });
65
69
 
66
70
  it('throws an error upon project fetch failure when trying to get article by slug', async () => {
67
- const projectId = 'projectId';
68
- const apiUrl = 'https://api.cntrl.site/';
71
+ const projectId = 'MY_PROJECT_ID';
72
+ const apiKey = 'MY_API_KEY';
73
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
69
74
  const fetch = async () => Promise.resolve({
70
75
  ok: false,
71
76
  statusText: 'reason',
72
77
  json: () => Promise.resolve()
73
78
  });
74
- const client = new Client(projectId, apiUrl, fetch);
79
+ const client = new Client(apiUrl, fetch);
75
80
  await expect(client.getPageArticle('/'))
76
- .rejects.toEqual(new Error('Failed to fetch project with id #projectId: reason'));
81
+ .rejects.toEqual(new Error(`Failed to fetch project with id #${projectId}: reason`));
77
82
  });
78
83
 
79
84
  it('throws an error upon article fetch failure when trying to get article by slug', async () => {
80
- const projectId = 'projectId';
81
- const apiUrl = 'https://api.cntrl.site/';
82
- const projectApiUrl = `${apiUrl}projects/projectId`;
85
+ const projectId = 'MY_PROJECT_ID';
86
+ const apiKey = 'MY_API_KEY';
87
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
88
+ const projectApiUrl = `https://api.cntrl.site/projects/${projectId}`;
83
89
  const fetch = (url: string) => {
84
90
  return Promise.resolve({
85
91
  ok: url === projectApiUrl,
@@ -87,15 +93,16 @@ describe('Client', () => {
87
93
  statusText: 'reason'
88
94
  });
89
95
  };
90
- const client = new Client(projectId, apiUrl, fetch);
96
+ const client = new Client(apiUrl, fetch);
91
97
  await expect(client.getPageArticle('/'))
92
98
  .rejects.toEqual(new Error('Failed to fetch article with id #articleId: reason'));
93
99
  });
94
100
 
95
101
  it('throws an error when trying to fetch article by nonexistent slug', async () => {
96
- const projectId = 'projectId';
97
- const apiUrl = 'https://api.cntrl.site/';
98
- const projectApiUrl = `${apiUrl}projects/projectId`;
102
+ const projectId = 'MY_PROJECT_ID';
103
+ const apiKey = 'MY_API_KEY';
104
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
105
+ const projectApiUrl = `https://api.cntrl.site/projects/${projectId}`;
99
106
  const slug = '/nonexistent-slug';
100
107
  const fetch = (url: string) => {
101
108
  return Promise.resolve({
@@ -104,33 +111,35 @@ describe('Client', () => {
104
111
  statusText: 'reason'
105
112
  });
106
113
  };
107
- const client = new Client(projectId, apiUrl, fetch);
114
+ const client = new Client(apiUrl, fetch);
108
115
  await expect(client.getPageArticle(slug))
109
116
  .rejects.toEqual(new Error(`Page with a slug ${slug} was not found in project with id #${projectId}`));
110
117
  });
111
118
 
112
119
  it('returns type presets by project id', async () => {
113
120
  let fetchCalledTimes = 0;
114
- const projectId = 'projectId';
115
- const apiUrl = 'https://api.cntrl.site/';
121
+ const projectId = 'MY_PROJECT_ID';
122
+ const apiKey = 'MY_API_KEY';
123
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
116
124
  const fetch = (url: string) => {
117
125
  fetchCalledTimes += 1;
118
- expect(url).toBe(`${apiUrl}projects/${projectId}/type-presets`);
126
+ expect(url).toBe(`https://api.cntrl.site/projects/${projectId}/type-presets`);
119
127
  return Promise.resolve({
120
128
  ok: true,
121
129
  json: () => Promise.resolve(typePresetsMock),
122
130
  statusText: ''
123
131
  });
124
132
  };
125
- const client = new Client(projectId, apiUrl, fetch);
133
+ const client = new Client(apiUrl, fetch);
126
134
  const presets = await client.getTypePresets();
127
135
  expect(presets).toEqual(typePresetsMock);
128
136
  expect(fetchCalledTimes).toEqual(1);
129
137
  });
130
138
 
131
139
  it('throws an error upon type presets fetch failure', async () => {
132
- const projectId = 'projectId';
133
- const apiUrl = 'https://api.cntrl.site/';
140
+ const projectId = 'MY_PROJECT_ID';
141
+ const apiKey = 'MY_API_KEY';
142
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
134
143
  const fetch = () => {
135
144
  return Promise.resolve({
136
145
  ok: false,
@@ -138,49 +147,12 @@ describe('Client', () => {
138
147
  statusText: 'reason'
139
148
  });
140
149
  };
141
- const client = new Client(projectId, apiUrl, fetch);
150
+ const client = new Client(apiUrl, fetch);
142
151
  await expect(client.getTypePresets()).rejects.toEqual(
143
152
  new Error(`Failed to fetch type presets for the project with id #${projectId}: reason`)
144
153
  );
145
154
  });
146
155
 
147
- it('returns keyframes by article id', async () => {
148
- let fetchCalledTimes = 0;
149
- const projectId = 'projectId';
150
- const articleId = 'articleId';
151
- const apiUrl = 'https://api.cntrl.site/';
152
- const fetch = (url: string) => {
153
- fetchCalledTimes += 1;
154
- expect(url).toBe(`${apiUrl}keyframes/${articleId}`);
155
- return Promise.resolve({
156
- ok: true,
157
- json: () => Promise.resolve(keyframesMock),
158
- statusText: ''
159
- });
160
- };
161
- const client = new Client(projectId, apiUrl, fetch);
162
- const presets = await client.getKeyframes(articleId);
163
- expect(presets).toEqual(keyframesMock);
164
- expect(fetchCalledTimes).toEqual(1);
165
- });
166
-
167
- it('throws an error upon keyframes fetch failure', async () => {
168
- const projectId = 'projectId';
169
- const articleId = 'articleId';
170
- const apiUrl = 'https://api.cntrl.site/';
171
- const fetch = () => {
172
- return Promise.resolve({
173
- ok: false,
174
- json: () => Promise.resolve(),
175
- statusText: 'reason'
176
- });
177
- };
178
- const client = new Client(projectId, apiUrl, fetch);
179
- await expect(client.getKeyframes(articleId)).rejects.toEqual(
180
- new Error(`Failed to fetch keyframes for the article with id #${articleId}: reason`)
181
- );
182
- });
183
-
184
156
  it('merges two meta objects into one with priority of page-based over project-based', () => {
185
157
  const pageMeta: TPageMeta = {
186
158
  enabled: true,
@@ -219,4 +191,20 @@ describe('Client', () => {
219
191
  expect(meta.description).toBeUndefined();
220
192
  expect(meta.title).toBeUndefined();
221
193
  });
194
+
195
+ it('throws an error when no project ID passed to the connect URL', async () => {
196
+ const projectId = '';
197
+ const apiKey = 'MY_API_KEY';
198
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
199
+ expect(() => new Client(apiUrl)).toThrow(new Error('Project ID is missing in the URL.'));
200
+ expect(() => new Client('https://api.cntrl.site'))
201
+ .toThrow(new Error('Project ID is missing in the URL.'));
202
+ });
203
+
204
+ it('throws an error when no API key passed to the connect URL', async () => {
205
+ const projectId = 'whatever';
206
+ const apiKey = '';
207
+ const apiUrl = `https://${projectId}:${apiKey}@api.cntrl.site/`;
208
+ expect(() => new Client(apiUrl)).toThrow(new Error('API key is missing in the URL.'));
209
+ });
222
210
  });
@@ -15,16 +15,17 @@ import fetch from 'isomorphic-fetch';
15
15
  import { URL } from 'url';
16
16
 
17
17
  export class Client {
18
+ private url: URL;
18
19
  constructor(
19
- private projectId: string,
20
- private APIUrl: string,
20
+ APIUrl: string,
21
21
  private fetchImpl: FetchImpl = fetch
22
22
  ) {
23
- if (projectId.length === 0) {
24
- throw new Error('CNTRL SDK: Project ID is empty. Did you forget to pass it?');
23
+ this.url = new URL(APIUrl);
24
+ if (!this.url.username) {
25
+ throw new Error('Project ID is missing in the URL.');
25
26
  }
26
- if (APIUrl.length === 0) {
27
- throw new Error('CNTRL SDK: API URL is empty. Did you forget to pass it?');
27
+ if (!this.url.password) {
28
+ throw new Error('API key is missing in the URL.');
28
29
  }
29
30
  }
30
31
 
@@ -39,7 +40,7 @@ export class Client {
39
40
  }
40
41
  }
41
42
 
42
- async getPageArticle(pageSlug: string): Promise<TArticle> {
43
+ async getPageArticle(pageSlug: string): Promise<{ article: TArticle, keyframes: TKeyframeAny[] }> {
43
44
  try {
44
45
  const projectResponse = await this.fetchProject();
45
46
  const data = await projectResponse.json();
@@ -47,8 +48,9 @@ export class Client {
47
48
  const articleId = this.findArticleIdByPageSlug(pageSlug, project.pages);
48
49
  const articleResponse = await this.fetchArticle(articleId);
49
50
  const articleData = await articleResponse.json();
50
- const article = ArticleSchema.parse(articleData);
51
- return article;
51
+ const article = ArticleSchema.parse(articleData.article);
52
+ const keyframes = KeyframesSchema.parse(articleData.keyframes);
53
+ return { article, keyframes };
52
54
  } catch (e) {
53
55
  throw e;
54
56
  }
@@ -61,13 +63,6 @@ export class Client {
61
63
  return typePresets;
62
64
  }
63
65
 
64
- async getKeyframes(articleId: string): Promise<TKeyframeAny[]> {
65
- const response = await this.fetchKeyframes(articleId);
66
- const data = await response.json();
67
- const keyframes = KeyframesSchema.parse(data);
68
- return keyframes;
69
- }
70
-
71
66
  public static getPageMeta(projectMeta: TMeta, pageMeta: TPageMeta): TMeta {
72
67
  return pageMeta.enabled ? {
73
68
  title: pageMeta.title ? pageMeta.title : projectMeta.title,
@@ -79,47 +74,54 @@ export class Client {
79
74
  }
80
75
 
81
76
  private async fetchProject(): Promise<FetchImplResponse> {
82
- const url = new URL(`/projects/${this.projectId}`, this.APIUrl);
83
- const response = await this.fetchImpl(url.href);
77
+ const { username: projectId, password: apiKey, origin } = this.url;
78
+ const url = new URL(`/projects/${projectId}`, origin);
79
+ const response = await this.fetchImpl(url.href, {
80
+ headers: {
81
+ Authorization: `Bearer ${apiKey}`
82
+ }
83
+ });
84
84
  if (!response.ok) {
85
- throw new Error(`Failed to fetch project with id #${this.projectId}: ${response.statusText}`);
85
+ throw new Error(`Failed to fetch project with id #${projectId}: ${response.statusText}`);
86
86
  }
87
87
  return response;
88
88
  }
89
89
 
90
90
  private async fetchArticle(articleId: string): Promise<FetchImplResponse> {
91
- const url = new URL(`/articles/${articleId}`, this.APIUrl);
92
- const response = await this.fetchImpl(url.href);
91
+ const { username: projectId, password: apiKey, origin } = this.url;
92
+ const url = new URL(`/projects/${projectId}/articles/${articleId}`, origin);
93
+ const response = await this.fetchImpl(url.href, {
94
+ headers: {
95
+ Authorization: `Bearer ${apiKey}`
96
+ }
97
+ });
93
98
  if (!response.ok) {
94
99
  throw new Error(`Failed to fetch article with id #${articleId}: ${response.statusText}`);
95
100
  }
96
101
  return response;
97
102
  }
98
103
 
99
- private async fetchKeyframes(articleId: string): Promise<FetchImplResponse> {
100
- const url = new URL(`/keyframes/${articleId}`, this.APIUrl);
101
- const response = await this.fetchImpl(url.href);
102
- if (!response.ok) {
103
- throw new Error(`Failed to fetch keyframes for the article with id #${articleId}: ${response.statusText}`);
104
- }
105
- return response;
106
- }
107
-
108
104
  private async fetchTypePresets(): Promise<FetchImplResponse> {
109
- const url = new URL(`/projects/${this.projectId}/type-presets`, this.APIUrl);
110
- const response = await this.fetchImpl(url.href);
105
+ const { username: projectId, password: apiKey, origin } = this.url;
106
+ const url = new URL(`/projects/${projectId}/type-presets`, origin);
107
+ const response = await this.fetchImpl(url.href, {
108
+ headers: {
109
+ Authorization: `Bearer ${apiKey}`
110
+ }
111
+ });
111
112
  if (!response.ok) {
112
113
  throw new Error(
113
- `Failed to fetch type presets for the project with id #${this.projectId}: ${response.statusText}`
114
+ `Failed to fetch type presets for the project with id #${projectId}: ${response.statusText}`
114
115
  );
115
116
  }
116
117
  return response;
117
118
  }
118
119
 
119
120
  private findArticleIdByPageSlug(slug: string, pages: TPage[]): string {
121
+ const { username: projectId } = this.url;
120
122
  const page = pages.find((page) => page.slug === slug);
121
123
  if (!page) {
122
- throw new Error(`Page with a slug ${slug} was not found in project with id #${this.projectId}`);
124
+ throw new Error(`Page with a slug ${slug} was not found in project with id #${projectId}`);
123
125
  }
124
126
  return page.articleId;
125
127
  }
@@ -131,4 +133,4 @@ interface FetchImplResponse {
131
133
  statusText: string;
132
134
  }
133
135
 
134
- type FetchImpl = (url: string) => Promise<FetchImplResponse>;
136
+ type FetchImpl = (url: string, init?: RequestInit) => Promise<FetchImplResponse>;
Binary file