@wix/sdk 1.6.4 → 1.7.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.
@@ -18,6 +18,7 @@ export type WixAppOAuthStrategy = AuthenticationStrategy & {
18
18
  * @param opts.appId The Wix App ID
19
19
  * @param opts.appSecret The Wix App Secret
20
20
  * @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
21
+ * @param opts.publicKey An optional public key for validating webhook requests
21
22
  * @returns An authentication strategy that can be used with WixClient
22
23
  * @example
23
24
  * ```ts
@@ -49,6 +50,7 @@ export type WixAppOAuthStrategy = AuthenticationStrategy & {
49
50
  */
50
51
  export declare function WixAppOAuthStrategy(opts: {
51
52
  appId: string;
52
- appSecret: string;
53
+ appSecret?: string;
54
+ publicKey?: string;
53
55
  refreshToken?: string;
54
56
  }): WixAppOAuthStrategy;
@@ -1,3 +1,4 @@
1
+ import { verify } from 'jsonwebtoken';
1
2
  /**
2
3
  * Creates an authentication strategy for Wix Apps OAuth installation process.
3
4
  * Use this authentication strategy when making requests to Wix APIs from your Wix App backend.
@@ -5,6 +6,7 @@
5
6
  * @param opts.appId The Wix App ID
6
7
  * @param opts.appSecret The Wix App Secret
7
8
  * @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
9
+ * @param opts.publicKey An optional public key for validating webhook requests
8
10
  * @returns An authentication strategy that can be used with WixClient
9
11
  * @example
10
12
  * ```ts
@@ -42,6 +44,9 @@ export function WixAppOAuthStrategy(opts) {
42
44
  return `https://www.wix.com/installer/install?appId=${opts.appId}&redirectUrl=${redirectUrl}`;
43
45
  },
44
46
  async handleOAuthCallback(url, oauthOpts) {
47
+ if (!opts.appSecret) {
48
+ throw new Error('App secret is required for handling OAuth callback. Make sure to pass it to the WixAppOAuthStrategy');
49
+ }
45
50
  const params = new URLSearchParams(new URL(url).search);
46
51
  const state = params.get('state');
47
52
  if (state && oauthOpts?.state && state !== oauthOpts.state) {
@@ -79,6 +84,9 @@ export function WixAppOAuthStrategy(opts) {
79
84
  if (!refreshToken) {
80
85
  throw new Error('Missing refresh token. Either pass it to the WixAppOAuthStrategy or use the handleOAuthCallback method to retrieve it.');
81
86
  }
87
+ if (!opts.appSecret) {
88
+ throw new Error('App secret is required for refreshing access tokens. Make sure to pass it to the WixAppOAuthStrategy');
89
+ }
82
90
  const tokensRes = await fetch('https://www.wixapis.com/oauth/access', {
83
91
  method: 'POST',
84
92
  headers: {
@@ -102,5 +110,15 @@ export function WixAppOAuthStrategy(opts) {
102
110
  },
103
111
  };
104
112
  },
113
+ decodeJWT(token) {
114
+ if (!opts.publicKey) {
115
+ throw new Error('Missing public key. Make sure to pass it to the WixAppOAuthStrategy');
116
+ }
117
+ const decoded = verify(token, opts.publicKey);
118
+ return {
119
+ decoded,
120
+ valid: true,
121
+ };
122
+ },
105
123
  };
106
124
  }
@@ -1,6 +1,7 @@
1
1
  import { IOAuthStrategy, Tokens } from './types.js';
2
2
  export declare function OAuthStrategy(config: {
3
3
  clientId: string;
4
+ publicKey?: string;
4
5
  tokens?: Tokens;
5
6
  }): IOAuthStrategy;
6
7
  export interface TokenResponse {
@@ -6,6 +6,7 @@ import { API_URL } from '../../common.js';
6
6
  import { LoginState, TokenRole, } from './types.js';
7
7
  import { addPostMessageListener, loadFrame } from '../../iframeUtils.js';
8
8
  import { EMAIL_EXISTS, INVALID_CAPTCHA, INVALID_PASSWORD, MISSING_CAPTCHA, RESET_PASSWORD, } from './constants.js';
9
+ import { verify } from 'jsonwebtoken';
9
10
  import { biHeaderGenerator } from '../../bi/biHeaderGenerator.js';
10
11
  import { pkceChallenge } from './pkce-challenge.js';
11
12
  const moduleWithTokens = { redirects, authentication, recovery, verification };
@@ -334,6 +335,16 @@ export function OAuthStrategy(config) {
334
335
  sendPasswordResetEmail,
335
336
  captchaInvisibleSiteKey: '6LdoPaUfAAAAAJphvHoUoOob7mx0KDlXyXlgrx5v',
336
337
  captchaVisibleSiteKey: '6Ld0J8IcAAAAANyrnxzrRlX1xrrdXsOmsepUYosy',
338
+ decodeJWT(token) {
339
+ if (!config.publicKey) {
340
+ throw new Error('Missing public key. Make sure to pass it to the OAuthStrategy');
341
+ }
342
+ const decoded = verify(token, config.publicKey);
343
+ return {
344
+ decoded,
345
+ valid: true,
346
+ };
347
+ },
337
348
  };
338
349
  }
339
350
  const fetchTokens = async (payload) => {
@@ -1,4 +1,4 @@
1
- import { AuthenticationStrategy, BoundAuthenticationStrategy, BuildRESTFunction, Host, HostModule, HostModuleAPI, RESTFunctionDescriptor } from '@wix/sdk-types';
1
+ import { AuthenticationStrategy, BoundAuthenticationStrategy, BuildRESTFunction, Host, HostModule, HostModuleAPI, RESTFunctionDescriptor, EventDefinition } from '@wix/sdk-types';
2
2
  import { ConditionalExcept, EmptyObject } from 'type-fest';
3
3
  import { AmbassadorFunctionDescriptor, BuildAmbassadorFunction } from './ambassador-modules.js';
4
4
  import { PublicMetadata } from './common.js';
@@ -60,7 +60,27 @@ export type WixClient<H extends Host<any> | undefined = undefined, Z extends Aut
60
60
  data: Result;
61
61
  errors?: GraphQLFormattedError[];
62
62
  }>;
63
+ webhooks: {
64
+ process<ExpectedEvents extends EventDefinition[] = []>(jwt: string, opts?: {
65
+ expectedEvents: ExpectedEvents;
66
+ }): ProcessedEvent<ExpectedEvents>;
67
+ processRequest<ExpectedEvents extends EventDefinition[] = []>(request: Request, opts?: {
68
+ expectedEvents: ExpectedEvents;
69
+ }): Promise<ProcessedEvent<ExpectedEvents>>;
70
+ };
63
71
  } & BuildDescriptors<T, H>;
72
+ type ResolvePossibleEvents<T extends EventDefinition[]> = {
73
+ [K in keyof T]: T[K] extends EventDefinition ? {
74
+ eventType: T[K]['type'];
75
+ payload: T[K]['__payload'];
76
+ } : never;
77
+ } extends (infer U)[] ? U : never;
78
+ export type ProcessedEvent<T extends EventDefinition[] = []> = {
79
+ instanceId: string;
80
+ } & (T['length'] extends 0 ? {
81
+ eventType: string;
82
+ payload: unknown;
83
+ } : ResolvePossibleEvents<T>);
64
84
  export declare function createClient<H extends Host<any> | undefined = undefined, Z extends AuthenticationStrategy<H> = AuthenticationStrategy<H>, T extends Descriptors = EmptyObject>(config: {
65
85
  modules?: H extends Host<any> ? AssertHostMatches<T, H> : T;
66
86
  auth?: Z;
@@ -6,9 +6,10 @@ import { buildRESTDescriptor } from './rest-modules.js';
6
6
  import { FetchErrorResponse } from './fetch-error.js';
7
7
  export function createClient(config) {
8
8
  const _headers = config.headers || { Authorization: '' };
9
- const authStrategy = config.auth || {
10
- getAuthHeaders: () => Promise.resolve({ headers: {} }),
11
- };
9
+ const authStrategy = config.auth ||
10
+ {
11
+ getAuthHeaders: (_) => Promise.resolve({ headers: {} }),
12
+ };
12
13
  const boundGetAuthHeaders = authStrategy.getAuthHeaders.bind(undefined, config.host);
13
14
  authStrategy.getAuthHeaders = boundGetAuthHeaders;
14
15
  const boundFetch = async (url, options) => {
@@ -54,7 +55,9 @@ export function createClient(config) {
54
55
  _headers[k] = headers[k];
55
56
  }
56
57
  };
57
- const wrappedModules = config.modules ? use(config.modules) : {};
58
+ const wrappedModules = config.modules
59
+ ? use(config.modules)
60
+ : {};
58
61
  return {
59
62
  ...wrappedModules,
60
63
  auth: authStrategy,
@@ -82,5 +85,37 @@ export function createClient(config) {
82
85
  const { data, errors } = await res.json();
83
86
  return { data: data ?? {}, errors };
84
87
  },
88
+ webhooks: {
89
+ process: (jwt, opts = {
90
+ expectedEvents: [],
91
+ }) => {
92
+ if (!authStrategy.decodeJWT) {
93
+ throw new Error('decodeJWT is not supported by the authentication strategy');
94
+ }
95
+ const { decoded, valid } = authStrategy.decodeJWT(jwt);
96
+ if (!valid) {
97
+ throw new Error('JWT is not valid');
98
+ }
99
+ const parsedDecoded = JSON.parse(decoded.data);
100
+ const eventType = parsedDecoded.eventType;
101
+ const instanceId = parsedDecoded.instanceId;
102
+ const payload = JSON.parse(parsedDecoded.data);
103
+ if (opts.expectedEvents.length > 0 &&
104
+ !opts.expectedEvents.some(({ type }) => type === eventType)) {
105
+ throw new Error(`Unexpected event type: ${eventType}. Expected one of: ${opts.expectedEvents
106
+ .map((x) => x.type)
107
+ .join(', ')}`);
108
+ }
109
+ return {
110
+ instanceId,
111
+ eventType,
112
+ payload,
113
+ };
114
+ },
115
+ async processRequest(request, opts) {
116
+ const body = await request.text();
117
+ return this.process(body, opts);
118
+ },
119
+ },
85
120
  };
86
121
  }
@@ -18,6 +18,7 @@ export type WixAppOAuthStrategy = AuthenticationStrategy & {
18
18
  * @param opts.appId The Wix App ID
19
19
  * @param opts.appSecret The Wix App Secret
20
20
  * @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
21
+ * @param opts.publicKey An optional public key for validating webhook requests
21
22
  * @returns An authentication strategy that can be used with WixClient
22
23
  * @example
23
24
  * ```ts
@@ -49,6 +50,7 @@ export type WixAppOAuthStrategy = AuthenticationStrategy & {
49
50
  */
50
51
  export declare function WixAppOAuthStrategy(opts: {
51
52
  appId: string;
52
- appSecret: string;
53
+ appSecret?: string;
54
+ publicKey?: string;
53
55
  refreshToken?: string;
54
56
  }): WixAppOAuthStrategy;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WixAppOAuthStrategy = void 0;
4
+ const jsonwebtoken_1 = require("jsonwebtoken");
4
5
  /**
5
6
  * Creates an authentication strategy for Wix Apps OAuth installation process.
6
7
  * Use this authentication strategy when making requests to Wix APIs from your Wix App backend.
@@ -8,6 +9,7 @@ exports.WixAppOAuthStrategy = void 0;
8
9
  * @param opts.appId The Wix App ID
9
10
  * @param opts.appSecret The Wix App Secret
10
11
  * @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
12
+ * @param opts.publicKey An optional public key for validating webhook requests
11
13
  * @returns An authentication strategy that can be used with WixClient
12
14
  * @example
13
15
  * ```ts
@@ -45,6 +47,9 @@ function WixAppOAuthStrategy(opts) {
45
47
  return `https://www.wix.com/installer/install?appId=${opts.appId}&redirectUrl=${redirectUrl}`;
46
48
  },
47
49
  async handleOAuthCallback(url, oauthOpts) {
50
+ if (!opts.appSecret) {
51
+ throw new Error('App secret is required for handling OAuth callback. Make sure to pass it to the WixAppOAuthStrategy');
52
+ }
48
53
  const params = new URLSearchParams(new URL(url).search);
49
54
  const state = params.get('state');
50
55
  if (state && oauthOpts?.state && state !== oauthOpts.state) {
@@ -82,6 +87,9 @@ function WixAppOAuthStrategy(opts) {
82
87
  if (!refreshToken) {
83
88
  throw new Error('Missing refresh token. Either pass it to the WixAppOAuthStrategy or use the handleOAuthCallback method to retrieve it.');
84
89
  }
90
+ if (!opts.appSecret) {
91
+ throw new Error('App secret is required for refreshing access tokens. Make sure to pass it to the WixAppOAuthStrategy');
92
+ }
85
93
  const tokensRes = await fetch('https://www.wixapis.com/oauth/access', {
86
94
  method: 'POST',
87
95
  headers: {
@@ -105,6 +113,16 @@ function WixAppOAuthStrategy(opts) {
105
113
  },
106
114
  };
107
115
  },
116
+ decodeJWT(token) {
117
+ if (!opts.publicKey) {
118
+ throw new Error('Missing public key. Make sure to pass it to the WixAppOAuthStrategy');
119
+ }
120
+ const decoded = (0, jsonwebtoken_1.verify)(token, opts.publicKey);
121
+ return {
122
+ decoded,
123
+ valid: true,
124
+ };
125
+ },
108
126
  };
109
127
  }
110
128
  exports.WixAppOAuthStrategy = WixAppOAuthStrategy;
@@ -1,6 +1,7 @@
1
1
  import { IOAuthStrategy, Tokens } from './types.js';
2
2
  export declare function OAuthStrategy(config: {
3
3
  clientId: string;
4
+ publicKey?: string;
4
5
  tokens?: Tokens;
5
6
  }): IOAuthStrategy;
6
7
  export interface TokenResponse {
@@ -9,6 +9,7 @@ const common_js_1 = require("../../common.js");
9
9
  const types_js_1 = require("./types.js");
10
10
  const iframeUtils_js_1 = require("../../iframeUtils.js");
11
11
  const constants_js_1 = require("./constants.js");
12
+ const jsonwebtoken_1 = require("jsonwebtoken");
12
13
  const biHeaderGenerator_js_1 = require("../../bi/biHeaderGenerator.js");
13
14
  const pkce_challenge_js_1 = require("./pkce-challenge.js");
14
15
  const moduleWithTokens = { redirects: redirects_1.redirects, authentication: identity_1.authentication, recovery: identity_1.recovery, verification: identity_1.verification };
@@ -337,6 +338,16 @@ function OAuthStrategy(config) {
337
338
  sendPasswordResetEmail,
338
339
  captchaInvisibleSiteKey: '6LdoPaUfAAAAAJphvHoUoOob7mx0KDlXyXlgrx5v',
339
340
  captchaVisibleSiteKey: '6Ld0J8IcAAAAANyrnxzrRlX1xrrdXsOmsepUYosy',
341
+ decodeJWT(token) {
342
+ if (!config.publicKey) {
343
+ throw new Error('Missing public key. Make sure to pass it to the OAuthStrategy');
344
+ }
345
+ const decoded = (0, jsonwebtoken_1.verify)(token, config.publicKey);
346
+ return {
347
+ decoded,
348
+ valid: true,
349
+ };
350
+ },
340
351
  };
341
352
  }
342
353
  exports.OAuthStrategy = OAuthStrategy;
@@ -1,4 +1,4 @@
1
- import { AuthenticationStrategy, BoundAuthenticationStrategy, BuildRESTFunction, Host, HostModule, HostModuleAPI, RESTFunctionDescriptor } from '@wix/sdk-types';
1
+ import { AuthenticationStrategy, BoundAuthenticationStrategy, BuildRESTFunction, Host, HostModule, HostModuleAPI, RESTFunctionDescriptor, EventDefinition } from '@wix/sdk-types';
2
2
  import { ConditionalExcept, EmptyObject } from 'type-fest';
3
3
  import { AmbassadorFunctionDescriptor, BuildAmbassadorFunction } from './ambassador-modules.js';
4
4
  import { PublicMetadata } from './common.js';
@@ -60,7 +60,27 @@ export type WixClient<H extends Host<any> | undefined = undefined, Z extends Aut
60
60
  data: Result;
61
61
  errors?: GraphQLFormattedError[];
62
62
  }>;
63
+ webhooks: {
64
+ process<ExpectedEvents extends EventDefinition[] = []>(jwt: string, opts?: {
65
+ expectedEvents: ExpectedEvents;
66
+ }): ProcessedEvent<ExpectedEvents>;
67
+ processRequest<ExpectedEvents extends EventDefinition[] = []>(request: Request, opts?: {
68
+ expectedEvents: ExpectedEvents;
69
+ }): Promise<ProcessedEvent<ExpectedEvents>>;
70
+ };
63
71
  } & BuildDescriptors<T, H>;
72
+ type ResolvePossibleEvents<T extends EventDefinition[]> = {
73
+ [K in keyof T]: T[K] extends EventDefinition ? {
74
+ eventType: T[K]['type'];
75
+ payload: T[K]['__payload'];
76
+ } : never;
77
+ } extends (infer U)[] ? U : never;
78
+ export type ProcessedEvent<T extends EventDefinition[] = []> = {
79
+ instanceId: string;
80
+ } & (T['length'] extends 0 ? {
81
+ eventType: string;
82
+ payload: unknown;
83
+ } : ResolvePossibleEvents<T>);
64
84
  export declare function createClient<H extends Host<any> | undefined = undefined, Z extends AuthenticationStrategy<H> = AuthenticationStrategy<H>, T extends Descriptors = EmptyObject>(config: {
65
85
  modules?: H extends Host<any> ? AssertHostMatches<T, H> : T;
66
86
  auth?: Z;
@@ -9,9 +9,10 @@ const rest_modules_js_1 = require("./rest-modules.js");
9
9
  const fetch_error_js_1 = require("./fetch-error.js");
10
10
  function createClient(config) {
11
11
  const _headers = config.headers || { Authorization: '' };
12
- const authStrategy = config.auth || {
13
- getAuthHeaders: () => Promise.resolve({ headers: {} }),
14
- };
12
+ const authStrategy = config.auth ||
13
+ {
14
+ getAuthHeaders: (_) => Promise.resolve({ headers: {} }),
15
+ };
15
16
  const boundGetAuthHeaders = authStrategy.getAuthHeaders.bind(undefined, config.host);
16
17
  authStrategy.getAuthHeaders = boundGetAuthHeaders;
17
18
  const boundFetch = async (url, options) => {
@@ -57,7 +58,9 @@ function createClient(config) {
57
58
  _headers[k] = headers[k];
58
59
  }
59
60
  };
60
- const wrappedModules = config.modules ? use(config.modules) : {};
61
+ const wrappedModules = config.modules
62
+ ? use(config.modules)
63
+ : {};
61
64
  return {
62
65
  ...wrappedModules,
63
66
  auth: authStrategy,
@@ -85,6 +88,38 @@ function createClient(config) {
85
88
  const { data, errors } = await res.json();
86
89
  return { data: data ?? {}, errors };
87
90
  },
91
+ webhooks: {
92
+ process: (jwt, opts = {
93
+ expectedEvents: [],
94
+ }) => {
95
+ if (!authStrategy.decodeJWT) {
96
+ throw new Error('decodeJWT is not supported by the authentication strategy');
97
+ }
98
+ const { decoded, valid } = authStrategy.decodeJWT(jwt);
99
+ if (!valid) {
100
+ throw new Error('JWT is not valid');
101
+ }
102
+ const parsedDecoded = JSON.parse(decoded.data);
103
+ const eventType = parsedDecoded.eventType;
104
+ const instanceId = parsedDecoded.instanceId;
105
+ const payload = JSON.parse(parsedDecoded.data);
106
+ if (opts.expectedEvents.length > 0 &&
107
+ !opts.expectedEvents.some(({ type }) => type === eventType)) {
108
+ throw new Error(`Unexpected event type: ${eventType}. Expected one of: ${opts.expectedEvents
109
+ .map((x) => x.type)
110
+ .join(', ')}`);
111
+ }
112
+ return {
113
+ instanceId,
114
+ eventType,
115
+ payload,
116
+ };
117
+ },
118
+ async processRequest(request, opts) {
119
+ const body = await request.text();
120
+ return this.process(body, opts);
121
+ },
122
+ },
88
123
  };
89
124
  }
90
125
  exports.createClient = createClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/sdk",
3
- "version": "1.6.4",
3
+ "version": "1.7.0",
4
4
  "license": "UNLICENSED",
5
5
  "author": {
6
6
  "name": "Ronny Ringel",
@@ -58,11 +58,15 @@
58
58
  "*.{js,ts}": "yarn lint"
59
59
  },
60
60
  "dependencies": {
61
+ "@babel/runtime": "^7.23.2",
61
62
  "@wix/identity": "^1.0.71",
62
- "@wix/image-kit": "^1.46.0",
63
+ "@wix/image-kit": "^1.49.0",
63
64
  "@wix/redirects": "^1.0.31",
64
- "@wix/sdk-types": "^1.5.3",
65
+ "@wix/sdk-types": "^1.5.4",
65
66
  "crypto-js": "^4.2.0",
67
+ "jsonwebtoken": "^9.0.2",
68
+ "pkce-challenge": "^3.1.0",
69
+ "querystring": "^0.2.1",
66
70
  "type-fest": "^4.9.0"
67
71
  },
68
72
  "optionalDependencies": {
@@ -71,19 +75,21 @@
71
75
  "devDependencies": {
72
76
  "@types/crypto-js": "^4.2.1",
73
77
  "@types/is-ci": "^3.0.4",
78
+ "@types/jsonwebtoken": "^9.0.5",
74
79
  "@types/node": "^20.10.6",
75
- "@wix/ecom": "^1.0.440",
76
- "@wix/events": "^1.0.134",
80
+ "@vitest/ui": "^1.1.3",
81
+ "@wix/ecom": "^1.0.468",
82
+ "@wix/events": "^1.0.141",
77
83
  "@wix/metro": "^1.0.73",
78
- "@wix/metro-runtime": "^1.1587.0",
84
+ "@wix/metro-runtime": "^1.1611.0",
79
85
  "eslint": "^8.56.0",
80
86
  "eslint-config-sdk": "0.0.0",
81
87
  "graphql": "^16.8.0",
82
88
  "is-ci": "^3.0.1",
83
89
  "jsdom": "^22.1.0",
84
- "msw": "^2.0.11",
90
+ "msw": "^2.0.12",
85
91
  "typescript": "^5.3.3",
86
- "vitest": "^0.34.6",
92
+ "vitest": "^1.1.3",
87
93
  "vitest-teamcity-reporter": "^0.2.2"
88
94
  },
89
95
  "yoshiFlowLibrary": {
@@ -109,5 +115,5 @@
109
115
  "wallaby": {
110
116
  "autoDetect": true
111
117
  },
112
- "falconPackageHash": "16fe89900be2a97b0197aa9ae070fa9735a543d8affe31f1c1473037"
118
+ "falconPackageHash": "d7def5d521cd246af737cbbae2bf6712ccfa33ace5a4aaacee8af1b5"
113
119
  }