webflow-api 1.2.2 → 1.3.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 (61) hide show
  1. package/.eslintrc +31 -0
  2. package/.github/workflows/code-quality.yml +102 -0
  3. package/.github/workflows/npm-publish.yml +21 -0
  4. package/.github/workflows/semgrep.yml +24 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +6 -0
  7. package/README.md +50 -1
  8. package/jest.config.js +17 -0
  9. package/package.json +2 -7
  10. package/src/api/collection.ts +1 -1
  11. package/src/api/item.ts +4 -4
  12. package/src/api/meta.ts +2 -2
  13. package/src/api/oauth.ts +8 -2
  14. package/src/core/error.ts +1 -1
  15. package/src/core/webflow.ts +111 -4
  16. package/tests/api/collection.test.ts +147 -0
  17. package/tests/api/item.test.ts +180 -0
  18. package/tests/api/meta.test.ts +38 -0
  19. package/tests/api/oauth.test.ts +44 -0
  20. package/tests/api/site.test.ts +202 -0
  21. package/tests/api/user.test.ts +139 -0
  22. package/tests/api/webhook.test.ts +82 -0
  23. package/tests/core/error.test.ts +19 -0
  24. package/tests/core/response.test.ts +36 -0
  25. package/tests/core/webflow.test.ts +540 -0
  26. package/tests/fixtures/collection.fixture.ts +374 -0
  27. package/tests/fixtures/index.ts +7 -0
  28. package/tests/fixtures/item.fixture.ts +193 -0
  29. package/tests/fixtures/meta.fixture.ts +34 -0
  30. package/tests/fixtures/oauth.fixture.ts +38 -0
  31. package/tests/fixtures/site.fixture.ts +78 -0
  32. package/tests/fixtures/user.fixture.ts +175 -0
  33. package/tests/fixtures/webhook.fixture.ts +69 -0
  34. package/tsconfig.eslint.json +7 -0
  35. package/tsconfig.json +14 -0
  36. package/dist/api/collection.d.ts +0 -112
  37. package/dist/api/collection.js +0 -94
  38. package/dist/api/index.d.ts +0 -7
  39. package/dist/api/index.js +0 -23
  40. package/dist/api/item.d.ts +0 -177
  41. package/dist/api/item.js +0 -151
  42. package/dist/api/meta.d.ts +0 -53
  43. package/dist/api/meta.js +0 -25
  44. package/dist/api/oauth.d.ts +0 -69
  45. package/dist/api/oauth.js +0 -66
  46. package/dist/api/site.d.ts +0 -140
  47. package/dist/api/site.js +0 -137
  48. package/dist/api/user.d.ts +0 -143
  49. package/dist/api/user.js +0 -119
  50. package/dist/api/webhook.d.ts +0 -102
  51. package/dist/api/webhook.js +0 -80
  52. package/dist/core/error.d.ts +0 -21
  53. package/dist/core/error.js +0 -30
  54. package/dist/core/index.d.ts +0 -3
  55. package/dist/core/index.js +0 -19
  56. package/dist/core/response.d.ts +0 -32
  57. package/dist/core/response.js +0 -26
  58. package/dist/core/webflow.d.ts +0 -386
  59. package/dist/core/webflow.js +0 -445
  60. package/dist/index.d.ts +0 -2
  61. package/yarn.lock +0 -2830
package/.eslintrc ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "env": {
3
+ "es2021": true,
4
+ "browser": true
5
+ },
6
+ "extends": [
7
+ "eslint:recommended",
8
+ "plugin:@typescript-eslint/recommended",
9
+ "plugin:@typescript-eslint/recommended-requiring-type-checking"
10
+ ],
11
+ "parser": "@typescript-eslint/parser",
12
+ "parserOptions": {
13
+ "ecmaVersion": 12,
14
+ "project": "tsconfig.eslint.json"
15
+ },
16
+ "plugins": ["@typescript-eslint", "prettier"],
17
+ "rules": {
18
+ "prefer-const": "error",
19
+ "prettier/prettier": "error",
20
+ "@typescript-eslint/no-unused-vars": "off",
21
+ "@typescript-eslint/no-unused-params": "off",
22
+ "@typescript-eslint/no-unsafe-argument": "warn",
23
+ "@typescript-eslint/no-unsafe-assignment": "warn"
24
+ },
25
+ "overrides": [
26
+ {
27
+ "env": { "jest": true, "node": true },
28
+ "files": ["tests/**/*.ts"]
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,102 @@
1
+ name: Code Quality
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ name: TypeScript Build
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - name: Checkout
12
+ uses: actions/checkout@v2
13
+
14
+ - name: Install Node.js
15
+ uses: actions/setup-node@v1
16
+ with:
17
+ node-version: 14.x
18
+
19
+ - name: yarn install
20
+ run: yarn install
21
+
22
+ - run: yarn build
23
+ name: yarn build
24
+
25
+ typecheck:
26
+ name: TypeScript Typecheck
27
+ runs-on: ubuntu-latest
28
+
29
+ steps:
30
+ - name: Checkout
31
+ uses: actions/checkout@v2
32
+
33
+ - name: Install Node.js
34
+ uses: actions/setup-node@v1
35
+ with:
36
+ node-version: 14.x
37
+
38
+ - name: yarn install
39
+ run: yarn install
40
+
41
+ - run: yarn typecheck
42
+ name: yarn typecheck
43
+
44
+ test:
45
+ name: Jest CI Tests
46
+ runs-on: ubuntu-latest
47
+
48
+ steps:
49
+ - name: Checkout
50
+ uses: actions/checkout@v2
51
+
52
+ - name: Install Node.js
53
+ uses: actions/setup-node@v1
54
+ with:
55
+ node-version: 14.x
56
+
57
+ - name: yarn install
58
+ run: yarn install
59
+
60
+ - name: yarn build
61
+ run: yarn build
62
+
63
+ - run: yarn test:ci
64
+ name: yarn test:ci
65
+
66
+ prettier:
67
+ name: Prettier Formatting
68
+ runs-on: ubuntu-latest
69
+
70
+ steps:
71
+ - name: Checkout
72
+ uses: actions/checkout@v2
73
+
74
+ - name: Install Node.js
75
+ uses: actions/setup-node@v1
76
+ with:
77
+ node-version: 14.x
78
+
79
+ - name: yarn install
80
+ run: yarn install
81
+
82
+ - run: yarn format:check
83
+ name: yarn format:check
84
+
85
+ lint:
86
+ name: ESLint Linting
87
+ runs-on: ubuntu-latest
88
+
89
+ steps:
90
+ - name: Checkout
91
+ uses: actions/checkout@v2
92
+
93
+ - name: Install Node.js
94
+ uses: actions/setup-node@v1
95
+ with:
96
+ node-version: 14.x
97
+
98
+ - name: yarn install
99
+ run: yarn install
100
+
101
+ - run: yarn lint
102
+ name: yarn lint
@@ -0,0 +1,21 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish-npm:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 16
15
+ registry-url: https://registry.npmjs.org/
16
+ - run: |
17
+ yarn install
18
+ yarn test
19
+ yarn publish
20
+ env:
21
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,24 @@
1
+ on:
2
+ workflow_dispatch: {}
3
+ pull_request: {}
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ paths:
9
+ - .github/workflows/semgrep.yml
10
+ schedule:
11
+ # random HH:MM to avoid a load spike on GitHub Actions at 00:00
12
+ - cron: 34 11 * * *
13
+ name: Semgrep
14
+ jobs:
15
+ semgrep:
16
+ name: semgrep/ci
17
+ runs-on: ubuntu-20.04
18
+ env:
19
+ SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
20
+ container:
21
+ image: returntocorp/semgrep
22
+ steps:
23
+ - uses: actions/checkout@v3
24
+ - run: semgrep ci
@@ -0,0 +1,5 @@
1
+ dist
2
+ temp
3
+ *.json
4
+ coverage
5
+ .DS_Store
package/.prettierrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "semi": true,
3
+ "useTabs": false,
4
+ "printWidth": 100,
5
+ "trailingComma": "all"
6
+ }
package/README.md CHANGED
@@ -38,6 +38,41 @@ const webflow = new Webflow({
38
38
  });
39
39
  ```
40
40
 
41
+ ## Transitioning to API v2
42
+
43
+ We're actively working on a new version of the SDK that will fully support API v2. In the meantime, to make use of API v2 with our SDK, there are some important changes you need to be aware of:
44
+
45
+ ### Setting Up For API v2
46
+
47
+ When initializing your client, it's crucial to set the `beta` flag to true in the client options. This ensures you're targeting the API v2 endpoints.
48
+
49
+ ```javascript
50
+ const webflow = new Webflow({ beta: true, ...otherOptions });
51
+ ```
52
+
53
+ Please note, when the beta flag is set, several built-in methods will not be available. These methods include, but are not limited to, info, authenticatedUser, sites, site, etc. Attempting to use these will throw an error.
54
+
55
+ ### Calling API v2 Endpoints
56
+
57
+ To interact with API v2, you'll need to move away from using built-in methods, and instead use the provided HTTP methods directly.
58
+
59
+ For instance, where you previously used `sites()`:
60
+
61
+ ```javascript
62
+ // get the first site
63
+ const [site] = await webflow.sites();
64
+ ```
65
+
66
+ For API v2, you will need to use direct HTTP methods:
67
+
68
+ ```javascript
69
+ // get the first site
70
+ const sites = await webflow.get("/sites");
71
+ const site = sites[0];
72
+ ```
73
+
74
+ We understand that this is a shift in how you interact with the SDK, but rest assured, our upcoming SDK version will streamline this process and offer a more integrated experience with API v2.
75
+
41
76
  ## Basic Usage
42
77
 
43
78
  ### Chaining Calls
@@ -131,6 +166,20 @@ const url = webflow.authorizeUrl({
131
166
  res.redirect(url);
132
167
  ```
133
168
 
169
+ ### Using the scopes Parameter with v2 API
170
+
171
+ The v2 API introduces the concept of 'scopes', providing more control over app permissions. Instead of using the scope parameter as a single string, you can define multiple permissions using the scopes array:
172
+
173
+ ```javascript
174
+ const url = webflow.authorizeUrl({
175
+ client_id: "[CLIENT ID]",
176
+ redirect_uri: "https://my.server.com/oauth/callback",
177
+ scopes: ["read:sites", "write:items", "read:users"],
178
+ });
179
+ ```
180
+
181
+ For more information and a detailed list of available scopes, refer to our Scopes Guide.
182
+
134
183
  ### Access Token
135
184
 
136
185
  Once a user has authorized their Webflow resource(s), Webflow will redirect back to your server with a `code`. Use this to get an access token.
@@ -176,7 +225,7 @@ Get all sites available or lookup by site id.
176
225
  const sites = await webflow.sites();
177
226
 
178
227
  // Get a single site
179
- const site = await webflow.sites({ siteId: "[SITE ID]" });
228
+ const site = await webflow.site({ siteId: "[SITE ID]" });
180
229
  ```
181
230
 
182
231
  ### Collections
package/jest.config.js ADDED
@@ -0,0 +1,17 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ // A preset that is used as a base for Jest's configuration
4
+ preset: "ts-jest",
5
+
6
+ // The test environment that will be used for testing
7
+ testEnvironment: "node",
8
+
9
+ // A map from regular expressions to paths to transformers
10
+ transform: { "^.+\\.ts?$": "ts-jest" },
11
+
12
+ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
13
+ transformIgnorePatterns: ["<rootDir>/node_modules/"],
14
+
15
+ // Automatically clear mock calls, instances, contexts and results before every test
16
+ clearMocks: true,
17
+ };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "webflow-api",
3
3
  "description": "Webflow's official Node.js SDK for Data APIs",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "contributors": [
8
+ "Rodney Urquhart (https://github.com/rodneyu215)",
8
9
  "John Agan (https://github.com/johnagan)"
9
10
  ],
10
11
  "repository": {
@@ -12,12 +13,6 @@
12
13
  "type": "git"
13
14
  },
14
15
  "license": "MIT",
15
- "files": [
16
- "dist",
17
- "src",
18
- "LICENSE",
19
- "yarn.lock"
20
- ],
21
16
  "scripts": {
22
17
  "test": "yarn build && jest",
23
18
  "build": "yarn clean && tsc",
@@ -32,7 +32,7 @@ export type CollectionField = {
32
32
  required: boolean;
33
33
  editable: boolean;
34
34
  // TODO: add a better type
35
- validations?: Record<string, string | number | boolean | object>;
35
+ validations?: Record<string, any>;
36
36
  };
37
37
 
38
38
  /**************************************************************
package/src/api/item.ts CHANGED
@@ -36,7 +36,7 @@ export interface IDeletedItems {
36
36
  /**************************************************************
37
37
  * Types
38
38
  **************************************************************/
39
- export type PageinatedItems = PaginatedData & {
39
+ export type PaginatedItems = PaginatedData & {
40
40
  items: IItem[];
41
41
  };
42
42
 
@@ -77,7 +77,7 @@ export class Item extends WebflowRecord<IItem> implements IItem {
77
77
  requireArgs({ collectionId, itemId });
78
78
  const path = `/collections/${collectionId}/items/${itemId}`;
79
79
  // The API returns a paginated list with one record :(
80
- return client.get<PageinatedItems>(path);
80
+ return client.get<PaginatedItems>(path);
81
81
  }
82
82
 
83
83
  /**
@@ -96,7 +96,7 @@ export class Item extends WebflowRecord<IItem> implements IItem {
96
96
  requireArgs({ collectionId });
97
97
  const params = { limit, offset };
98
98
  const path = `/collections/${collectionId}/items`;
99
- return client.get<PageinatedItems>(path, { params });
99
+ return client.get<PaginatedItems>(path, { params });
100
100
  }
101
101
 
102
102
  /**
@@ -192,7 +192,7 @@ export class Item extends WebflowRecord<IItem> implements IItem {
192
192
  }
193
193
 
194
194
  /**
195
- * Unpublishes a list of Items
195
+ * Unpublish a list of Items
196
196
  * @param params The params for the request
197
197
  * @param params.collectionId The Collection ID
198
198
  * @param params.live Unpublish from the live site
package/src/api/meta.ts CHANGED
@@ -24,7 +24,7 @@ export interface IAuthenticatedUser {
24
24
  };
25
25
  }
26
26
 
27
- export interface IAuthentiationInfo {
27
+ export interface IAuthenticationInfo {
28
28
  application: InfoApplication;
29
29
  workspaces: string[];
30
30
  rateLimit: number;
@@ -48,7 +48,7 @@ export class Meta {
48
48
  * @returns The authentication info
49
49
  */
50
50
  static info(client: AxiosInstance) {
51
- return client.get<IAuthentiationInfo>("/info");
51
+ return client.get<IAuthenticationInfo>("/info");
52
52
  }
53
53
 
54
54
  /**
package/src/api/oauth.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AxiosInstance } from "axios";
2
- import { requireArgs } from "../core";
2
+ import { requireArgs, SupportedScope } from "../core";
3
3
 
4
4
  /**************************************************************
5
5
  * Interfaces
@@ -7,6 +7,7 @@ import { requireArgs } from "../core";
7
7
  export interface IAuthorizeUrlParams {
8
8
  state?: string;
9
9
  scope?: string;
10
+ scopes?: SupportedScope[];
10
11
  client_id: string;
11
12
  redirect_uri?: string;
12
13
  response_type?: string;
@@ -52,15 +53,20 @@ export class OAuth {
52
53
  * @returns The URL to authorize a user
53
54
  */
54
55
  static authorizeUrl(
55
- { response_type = "code", redirect_uri, client_id, state, scope }: IAuthorizeUrlParams,
56
+ { response_type = "code", redirect_uri, client_id, state, scope, scopes }: IAuthorizeUrlParams,
56
57
  client: AxiosInstance,
57
58
  ) {
58
59
  requireArgs({ client_id });
59
60
 
61
+ if (scope && scopes) {
62
+ throw new Error("Please provide either 'scope' or 'scopes', but not both.");
63
+ }
64
+
60
65
  const params = { response_type, client_id };
61
66
  if (redirect_uri) params["redirect_uri"] = redirect_uri;
62
67
  if (state) params["state"] = state;
63
68
  if (scope) params["scope"] = scope;
69
+ if (scopes && scopes.length > 0) params["scope"] = scopes.join("+");
64
70
 
65
71
  const url = "/oauth/authorize";
66
72
  const baseURL = client.defaults.baseURL.replace("api.", "");
package/src/core/error.ts CHANGED
@@ -16,7 +16,7 @@ export class RequestError extends Error implements IRequestError {
16
16
  err: string;
17
17
 
18
18
  constructor(error: IRequestError) {
19
- super(error.err ? error.err : "Unknown error occured");
19
+ super(error.err ? error.err : "Unknown error occurred");
20
20
  Object.assign(this, error);
21
21
  }
22
22
  }
@@ -22,8 +22,51 @@ export interface Options {
22
22
  token?: string;
23
23
  version?: string;
24
24
  headers?: Record<string, string>;
25
+ beta?: boolean;
25
26
  }
26
27
 
28
+ type MethodNames =
29
+ | "info"
30
+ | "authenticatedUser"
31
+ | "sites"
32
+ | "site"
33
+ | "publishSite"
34
+ | "domains"
35
+ | "collections"
36
+ | "collection"
37
+ | "items"
38
+ | "item"
39
+ | "createItem"
40
+ | "updateItem"
41
+ | "patchItem"
42
+ | "removeItem"
43
+ | "deleteItems"
44
+ | "publishItems";
45
+
46
+ export const SCOPES_ARRAY = [
47
+ "assets:read",
48
+ "assets:write",
49
+ "authorized_user:read",
50
+ "cms:read",
51
+ "cms:write",
52
+ "custom_code:read",
53
+ "custom_code:write",
54
+ "forms:read",
55
+ "forms:write",
56
+ "pages:read",
57
+ "pages:write",
58
+ "sites:read",
59
+ "sites:write",
60
+ "users:read",
61
+ "users:write",
62
+ "ecommerce:read",
63
+ "ecommerce:write",
64
+ ] as const;
65
+
66
+ export type SupportedScope = typeof SCOPES_ARRAY[number];
67
+
68
+ type ParamValueType = string | number | boolean | null | undefined;
69
+
27
70
  /**************************************************************
28
71
  * Class
29
72
  **************************************************************/
@@ -32,6 +75,43 @@ export class Webflow {
32
75
  constructor(public options: Options = {}) {
33
76
  this.client = axios.create(this.config);
34
77
  this.client.interceptors.response.use(ErrorInterceptor);
78
+
79
+ if (this.options.beta) {
80
+ this.removeNonBetaMethods();
81
+ }
82
+ }
83
+
84
+ private removeNonBetaMethods() {
85
+ const methodsToRemove: MethodNames[] = [
86
+ "info",
87
+ "authenticatedUser",
88
+ "sites",
89
+ "site",
90
+ "publishSite",
91
+ "domains",
92
+ "collections",
93
+ "collection",
94
+ "items",
95
+ "item",
96
+ "createItem",
97
+ "updateItem",
98
+ "patchItem",
99
+ "removeItem",
100
+ "deleteItems",
101
+ "publishItems",
102
+ ];
103
+
104
+ methodsToRemove.forEach((method) => {
105
+ Object.defineProperty(this, method, {
106
+ value: function (): never {
107
+ throw new Error(
108
+ `The method '${method}()' is not available in beta mode. Please disable the beta option to use this method.`,
109
+ );
110
+ },
111
+ enumerable: false,
112
+ configurable: true,
113
+ });
114
+ });
35
115
  }
36
116
 
37
117
  // Set the Authentication token
@@ -46,10 +126,11 @@ export class Webflow {
46
126
 
47
127
  // The Axios configuration
48
128
  get config() {
49
- const { host = DEFAULT_HOST, token, version, headers } = this.options;
129
+ const { host = DEFAULT_HOST, token, version, headers, beta = false } = this.options;
130
+ const effectiveHost = beta ? "webflow.com/beta" : host;
50
131
 
51
132
  const config: AxiosRequestConfig = {
52
- baseURL: `https://api.${host}/`,
133
+ baseURL: `https://api.${effectiveHost}/`,
53
134
  headers: {
54
135
  "Content-Type": "application/json",
55
136
  "User-Agent": USER_AGENT,
@@ -63,6 +144,32 @@ export class Webflow {
63
144
  // Add the Authorization header if a token is set
64
145
  if (token) config.headers.Authorization = `Bearer ${token}`;
65
146
 
147
+ config.paramsSerializer = {
148
+ serialize: (params: Record<string, ParamValueType>): string => {
149
+ if (typeof params !== "object" || params === null) {
150
+ return "";
151
+ }
152
+
153
+ const parts: string[] = [];
154
+
155
+ for (const key in params) {
156
+ const value = params[key];
157
+ if (value === undefined) continue;
158
+
159
+ const safeValue =
160
+ typeof value === "string" || typeof value === "number" ? value : String(value);
161
+
162
+ if (key === "scope") {
163
+ parts.push(`${key}=${safeValue}`);
164
+ } else {
165
+ parts.push(`${key}=${encodeURIComponent(safeValue)}`);
166
+ }
167
+ }
168
+
169
+ return parts.join("&");
170
+ },
171
+ };
172
+
66
173
  return config;
67
174
  }
68
175
 
@@ -328,7 +435,7 @@ export class Webflow {
328
435
  return res.data;
329
436
  }
330
437
  /**
331
- * Upublish a Collection Item
438
+ * Unpublish a Collection Item
332
439
  * @param params The Item information
333
440
  * @param params.collectionId The Collection ID
334
441
  * @param params.itemId The Item ID
@@ -503,7 +610,7 @@ export class Webflow {
503
610
  * @param params.siteId The Site Id
504
611
  * @param params.url The Url the Webhook should call on events
505
612
  * @param params.triggerType The type of event that should trigger the Webhook
506
- * @param params.filter The filter to apply to the Webhook (form_submssion only)
613
+ * @param params.filter The filter to apply to the Webhook (form_submission only)
507
614
  * @returns The created webhook
508
615
  */
509
616
  async createWebhook({