@wix/sdk 1.5.9 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/ambassador-modules.d.ts +31 -0
- package/build/cjs/ambassador-modules.js +95 -0
- package/build/cjs/auth/ApiKeyAuthStrategy.d.ts +16 -0
- package/build/cjs/auth/ApiKeyAuthStrategy.js +26 -0
- package/build/cjs/auth/WixAppOAuthStrategy.d.ts +54 -0
- package/build/cjs/auth/WixAppOAuthStrategy.js +110 -0
- package/build/cjs/auth/oauth2/OAuthStrategy.d.ts +12 -0
- package/build/cjs/auth/oauth2/OAuthStrategy.js +361 -0
- package/build/cjs/auth/oauth2/constants.d.ts +5 -0
- package/build/cjs/auth/oauth2/constants.js +8 -0
- package/build/cjs/auth/oauth2/pkce-challenge.d.ts +5 -0
- package/build/cjs/auth/oauth2/pkce-challenge.js +41 -0
- package/build/cjs/auth/oauth2/types.d.ts +121 -0
- package/build/cjs/auth/oauth2/types.js +19 -0
- package/build/cjs/bi/biHeaderGenerator.d.ts +12 -0
- package/build/cjs/bi/biHeaderGenerator.js +21 -0
- package/build/cjs/common.d.ts +7 -0
- package/build/cjs/common.js +7 -0
- package/build/cjs/fetch-error.d.ts +9 -0
- package/build/cjs/fetch-error.js +35 -0
- package/build/cjs/helpers.d.ts +4 -0
- package/build/cjs/helpers.js +16 -0
- package/build/cjs/host-modules.d.ts +3 -0
- package/build/cjs/host-modules.js +10 -0
- package/build/cjs/iframeUtils.d.ts +4 -0
- package/build/cjs/iframeUtils.js +50 -0
- package/build/cjs/index.d.ts +9 -0
- package/build/cjs/index.js +28 -0
- package/build/cjs/rest-modules.d.ts +7 -0
- package/build/cjs/rest-modules.js +87 -0
- package/build/cjs/tokenHelpers.d.ts +4 -0
- package/build/cjs/tokenHelpers.js +17 -0
- package/build/cjs/wixClient.d.ts +70 -0
- package/build/cjs/wixClient.js +90 -0
- package/build/cjs/wixMedia.d.ts +46 -0
- package/build/cjs/wixMedia.js +160 -0
- package/build/esm/ambassador-modules.d.ts +31 -0
- package/build/esm/ambassador-modules.js +89 -0
- package/build/esm/auth/ApiKeyAuthStrategy.d.ts +16 -0
- package/build/esm/auth/ApiKeyAuthStrategy.js +22 -0
- package/build/esm/auth/WixAppOAuthStrategy.d.ts +54 -0
- package/build/esm/auth/WixAppOAuthStrategy.js +106 -0
- package/build/esm/auth/oauth2/OAuthStrategy.d.ts +12 -0
- package/build/esm/auth/oauth2/OAuthStrategy.js +357 -0
- package/build/esm/auth/oauth2/constants.d.ts +5 -0
- package/build/esm/auth/oauth2/constants.js +5 -0
- package/build/esm/auth/oauth2/pkce-challenge.d.ts +5 -0
- package/build/esm/auth/oauth2/pkce-challenge.js +33 -0
- package/build/esm/auth/oauth2/types.d.ts +121 -0
- package/build/esm/auth/oauth2/types.js +16 -0
- package/build/esm/bi/biHeaderGenerator.d.ts +12 -0
- package/build/esm/bi/biHeaderGenerator.js +17 -0
- package/build/esm/common.d.ts +7 -0
- package/build/esm/common.js +4 -0
- package/build/esm/fetch-error.d.ts +9 -0
- package/build/esm/fetch-error.js +31 -0
- package/build/esm/helpers.d.ts +4 -0
- package/build/esm/helpers.js +11 -0
- package/build/esm/host-modules.d.ts +3 -0
- package/build/esm/host-modules.js +5 -0
- package/build/esm/iframeUtils.d.ts +4 -0
- package/build/esm/iframeUtils.js +43 -0
- package/build/esm/index.d.ts +9 -0
- package/build/esm/index.js +9 -0
- package/build/esm/rest-modules.d.ts +7 -0
- package/build/esm/rest-modules.js +82 -0
- package/build/esm/tokenHelpers.d.ts +4 -0
- package/build/esm/tokenHelpers.js +11 -0
- package/build/esm/wixClient.d.ts +70 -0
- package/build/esm/wixClient.js +86 -0
- package/build/esm/wixMedia.d.ts +46 -0
- package/build/esm/wixMedia.js +156 -0
- package/package.json +42 -22
- package/build/browser/index.mjs +0 -1075
- package/build/index.d.mts +0 -389
- package/build/index.d.ts +0 -389
- package/build/index.js +0 -1115
- package/build/index.mjs +0 -1066
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HttpClient as SDKHttpClient } from '@wix/sdk-types';
|
|
2
|
+
import { RESTModuleOptions } from './rest-modules.js';
|
|
3
|
+
export type RequestContext = {
|
|
4
|
+
isSSR: boolean;
|
|
5
|
+
host: string;
|
|
6
|
+
protocol?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Ambassador request options types are copied mostly from AxiosRequestConfig.
|
|
10
|
+
* They are copied and not imported to reduce the amount of dependencies (to reduce install time).
|
|
11
|
+
* https://github.com/axios/axios/blob/3f53eb6960f05a1f88409c4b731a40de595cb825/index.d.ts#L307-L315
|
|
12
|
+
*/
|
|
13
|
+
export type Method = 'get' | 'GET' | 'delete' | 'DELETE' | 'head' | 'HEAD' | 'options' | 'OPTIONS' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH' | 'purge' | 'PURGE' | 'link' | 'LINK' | 'unlink' | 'UNLINK';
|
|
14
|
+
type ResponseTransformer = (data: any, headers?: any) => any;
|
|
15
|
+
export type AmbassadorRequestOptions<T = any> = {
|
|
16
|
+
_?: T;
|
|
17
|
+
url?: string;
|
|
18
|
+
method?: Method;
|
|
19
|
+
params?: any;
|
|
20
|
+
data?: any;
|
|
21
|
+
transformResponse?: ResponseTransformer | ResponseTransformer[];
|
|
22
|
+
};
|
|
23
|
+
export type AmbassadorFactory<Request, Response> = (payload: Request) => ((context: RequestContext) => AmbassadorRequestOptions<Response>) & {
|
|
24
|
+
__isAmbassador: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type AmbassadorFunctionDescriptor<Request = any, Response = any> = AmbassadorFactory<Request, Response>;
|
|
27
|
+
export type BuildAmbassadorFunction<T extends AmbassadorFunctionDescriptor> = T extends AmbassadorFunctionDescriptor<infer Request, infer Response> ? (req: Request) => Promise<Response> : never;
|
|
28
|
+
export declare const toHTTPModule: <Request_1, Response_1>(factory: AmbassadorFactory<Request_1, Response_1>) => (httpClient: SDKHttpClient) => (payload: Request_1) => Promise<Response_1>;
|
|
29
|
+
export declare const ambassadorModuleOptions: () => RESTModuleOptions;
|
|
30
|
+
export declare const isAmbassadorModule: (module: any) => boolean;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAmbassadorModule = exports.ambassadorModuleOptions = exports.toHTTPModule = void 0;
|
|
4
|
+
const parseMethod = (method) => {
|
|
5
|
+
switch (method) {
|
|
6
|
+
case 'get':
|
|
7
|
+
case 'GET':
|
|
8
|
+
return 'GET';
|
|
9
|
+
case 'post':
|
|
10
|
+
case 'POST':
|
|
11
|
+
return 'POST';
|
|
12
|
+
case 'put':
|
|
13
|
+
case 'PUT':
|
|
14
|
+
return 'PUT';
|
|
15
|
+
case 'delete':
|
|
16
|
+
case 'DELETE':
|
|
17
|
+
return 'DELETE';
|
|
18
|
+
case 'patch':
|
|
19
|
+
case 'PATCH':
|
|
20
|
+
return 'PATCH';
|
|
21
|
+
case 'head':
|
|
22
|
+
case 'HEAD':
|
|
23
|
+
return 'HEAD';
|
|
24
|
+
case 'options':
|
|
25
|
+
case 'OPTIONS':
|
|
26
|
+
return 'OPTIONS';
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unknown method: ${method}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const toHTTPModule = (factory) => (httpClient) => async (payload) => {
|
|
32
|
+
let requestOptions;
|
|
33
|
+
const HTTPFactory = (context) => {
|
|
34
|
+
requestOptions = factory(payload)(context);
|
|
35
|
+
if (requestOptions.url === undefined) {
|
|
36
|
+
throw new Error('Url was not successfully created for this request, please reach out to support channels for assistance.');
|
|
37
|
+
}
|
|
38
|
+
const { method, url, params } = requestOptions;
|
|
39
|
+
return {
|
|
40
|
+
...requestOptions,
|
|
41
|
+
method: parseMethod(method),
|
|
42
|
+
url,
|
|
43
|
+
data: requestOptions.data,
|
|
44
|
+
params,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
const response = await httpClient.request(HTTPFactory);
|
|
49
|
+
if (requestOptions === undefined) {
|
|
50
|
+
throw new Error('Request options were not created for this request, please reach out to support channels for assistance.');
|
|
51
|
+
}
|
|
52
|
+
const transformations = Array.isArray(requestOptions.transformResponse)
|
|
53
|
+
? requestOptions.transformResponse
|
|
54
|
+
: [requestOptions.transformResponse];
|
|
55
|
+
/**
|
|
56
|
+
* Based on Axios implementation:
|
|
57
|
+
* https://github.com/axios/axios/blob/3f53eb6960f05a1f88409c4b731a40de595cb825/lib/core/transformData.js#L22
|
|
58
|
+
*/
|
|
59
|
+
let data = response.data;
|
|
60
|
+
transformations.forEach((transform) => {
|
|
61
|
+
if (transform) {
|
|
62
|
+
data = transform(response.data, response.headers);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (typeof e === 'object' &&
|
|
69
|
+
e !== null &&
|
|
70
|
+
'response' in e &&
|
|
71
|
+
typeof e.response === 'object' &&
|
|
72
|
+
e.response !== null &&
|
|
73
|
+
'data' in e.response) {
|
|
74
|
+
throw e.response.data;
|
|
75
|
+
}
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
exports.toHTTPModule = toHTTPModule;
|
|
80
|
+
const ambassadorModuleOptions = () => ({
|
|
81
|
+
HTTPHost: self.location.host,
|
|
82
|
+
});
|
|
83
|
+
exports.ambassadorModuleOptions = ambassadorModuleOptions;
|
|
84
|
+
/*
|
|
85
|
+
* Because of issues with tree-shaking, we cant really put static parameter on module.
|
|
86
|
+
* We still have check for __isAmbassador for backward compatibility
|
|
87
|
+
*/
|
|
88
|
+
const isAmbassadorModule = (module) => {
|
|
89
|
+
if (module.__isAmbassador) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const fn = module();
|
|
93
|
+
return Boolean(fn.__isAmbassador);
|
|
94
|
+
};
|
|
95
|
+
exports.isAmbassadorModule = isAmbassadorModule;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AuthenticationStrategy } from '@wix/sdk-types';
|
|
2
|
+
export interface IApiKeyStrategy extends AuthenticationStrategy {
|
|
3
|
+
setSiteId(siteId?: string): void;
|
|
4
|
+
setAccountId(accountId?: string): void;
|
|
5
|
+
}
|
|
6
|
+
type Context = {
|
|
7
|
+
siteId: string;
|
|
8
|
+
accountId?: string;
|
|
9
|
+
} | {
|
|
10
|
+
siteId?: string;
|
|
11
|
+
accountId: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function ApiKeyStrategy({ siteId, accountId, apiKey, }: {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
} & Context): IApiKeyStrategy;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiKeyStrategy = void 0;
|
|
4
|
+
function ApiKeyStrategy({ siteId, accountId, apiKey, }) {
|
|
5
|
+
const headers = { Authorization: apiKey };
|
|
6
|
+
if (siteId) {
|
|
7
|
+
headers['wix-site-id'] = siteId;
|
|
8
|
+
}
|
|
9
|
+
if (accountId) {
|
|
10
|
+
headers['wix-account-id'] = accountId;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
setSiteId(_siteId) {
|
|
14
|
+
headers['wix-site-id'] = _siteId;
|
|
15
|
+
},
|
|
16
|
+
setAccountId(_accountId) {
|
|
17
|
+
headers['wix-account-id'] = _accountId;
|
|
18
|
+
},
|
|
19
|
+
async getAuthHeaders() {
|
|
20
|
+
return {
|
|
21
|
+
headers,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
exports.ApiKeyStrategy = ApiKeyStrategy;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AuthenticationStrategy } from '@wix/sdk-types';
|
|
2
|
+
export type WixAppOAuthStrategy = AuthenticationStrategy & {
|
|
3
|
+
getInstallUrl({ redirectUrl }: {
|
|
4
|
+
redirectUrl: string;
|
|
5
|
+
}): string;
|
|
6
|
+
handleOAuthCallback(url: string, opts?: {
|
|
7
|
+
state: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
instanceId: string;
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Creates an authentication strategy for Wix Apps OAuth installation process.
|
|
16
|
+
* Use this authentication strategy when making requests to Wix APIs from your Wix App backend.
|
|
17
|
+
* @param opts Options for initializing the authentication strategy
|
|
18
|
+
* @param opts.appId The Wix App ID
|
|
19
|
+
* @param opts.appSecret The Wix App Secret
|
|
20
|
+
* @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
|
|
21
|
+
* @returns An authentication strategy that can be used with WixClient
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { WixAppOAuthStrategy, createClient } from '@wix/sdk';
|
|
25
|
+
* import { products } from '@wix/stores';
|
|
26
|
+
*
|
|
27
|
+
* const client = createClient({
|
|
28
|
+
* auth: WixAppOAuthStrategy({
|
|
29
|
+
* appId: 'appId',
|
|
30
|
+
* appSecret: 'appSecret',
|
|
31
|
+
* }),
|
|
32
|
+
* modules: { products },
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* const installUrl = client.auth.getInstallUrl({ redirectUrl: 'https://example.com' });
|
|
36
|
+
* // Redirect the user to the installUrl
|
|
37
|
+
*
|
|
38
|
+
* ...
|
|
39
|
+
*
|
|
40
|
+
* // in the callback handler of your http server
|
|
41
|
+
* // req.url is the url of the callback request
|
|
42
|
+
* const { instanceId, refreshToken } = await client.auth.handleOAuthCallback(req.url);
|
|
43
|
+
*
|
|
44
|
+
* // store the instanceId and refreshToken in your database
|
|
45
|
+
* // use the authorized client
|
|
46
|
+
* const products = await client.products.queryProducts().find();
|
|
47
|
+
*
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function WixAppOAuthStrategy(opts: {
|
|
51
|
+
appId: string;
|
|
52
|
+
appSecret: string;
|
|
53
|
+
refreshToken?: string;
|
|
54
|
+
}): WixAppOAuthStrategy;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WixAppOAuthStrategy = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Creates an authentication strategy for Wix Apps OAuth installation process.
|
|
6
|
+
* Use this authentication strategy when making requests to Wix APIs from your Wix App backend.
|
|
7
|
+
* @param opts Options for initializing the authentication strategy
|
|
8
|
+
* @param opts.appId The Wix App ID
|
|
9
|
+
* @param opts.appSecret The Wix App Secret
|
|
10
|
+
* @param opts.refreshToken An optional refresh token previously retrieved from Wix OAuth API
|
|
11
|
+
* @returns An authentication strategy that can be used with WixClient
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { WixAppOAuthStrategy, createClient } from '@wix/sdk';
|
|
15
|
+
* import { products } from '@wix/stores';
|
|
16
|
+
*
|
|
17
|
+
* const client = createClient({
|
|
18
|
+
* auth: WixAppOAuthStrategy({
|
|
19
|
+
* appId: 'appId',
|
|
20
|
+
* appSecret: 'appSecret',
|
|
21
|
+
* }),
|
|
22
|
+
* modules: { products },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const installUrl = client.auth.getInstallUrl({ redirectUrl: 'https://example.com' });
|
|
26
|
+
* // Redirect the user to the installUrl
|
|
27
|
+
*
|
|
28
|
+
* ...
|
|
29
|
+
*
|
|
30
|
+
* // in the callback handler of your http server
|
|
31
|
+
* // req.url is the url of the callback request
|
|
32
|
+
* const { instanceId, refreshToken } = await client.auth.handleOAuthCallback(req.url);
|
|
33
|
+
*
|
|
34
|
+
* // store the instanceId and refreshToken in your database
|
|
35
|
+
* // use the authorized client
|
|
36
|
+
* const products = await client.products.queryProducts().find();
|
|
37
|
+
*
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
41
|
+
function WixAppOAuthStrategy(opts) {
|
|
42
|
+
let refreshToken = opts.refreshToken;
|
|
43
|
+
return {
|
|
44
|
+
getInstallUrl({ redirectUrl }) {
|
|
45
|
+
return `https://www.wix.com/installer/install?appId=${opts.appId}&redirectUrl=${redirectUrl}`;
|
|
46
|
+
},
|
|
47
|
+
async handleOAuthCallback(url, oauthOpts) {
|
|
48
|
+
const params = new URLSearchParams(new URL(url).search);
|
|
49
|
+
const state = params.get('state');
|
|
50
|
+
if (state && oauthOpts?.state && state !== oauthOpts.state) {
|
|
51
|
+
throw new Error(`Invalid OAuth callback URL. Expected state to be "${oauthOpts.state}" but got "${state}"`);
|
|
52
|
+
}
|
|
53
|
+
const code = params.get('code');
|
|
54
|
+
const instanceId = params.get('instanceId');
|
|
55
|
+
if (!code || !instanceId) {
|
|
56
|
+
throw new Error('Invalid OAuth callback URL. Make sure you pass the url including the code and instanceId query params.');
|
|
57
|
+
}
|
|
58
|
+
const tokensRes = await fetch('https://www.wixapis.com/oauth/access', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
code,
|
|
65
|
+
client_id: opts.appId,
|
|
66
|
+
client_secret: opts.appSecret,
|
|
67
|
+
grant_type: 'authorization_code',
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
if (tokensRes.status !== 200) {
|
|
71
|
+
throw new Error(`Failed to exchange authorization code for refresh token. Unexpected status code from Wix OAuth API: ${tokensRes.status}`);
|
|
72
|
+
}
|
|
73
|
+
const tokens = await tokensRes.json();
|
|
74
|
+
refreshToken = tokens.refresh_token;
|
|
75
|
+
return {
|
|
76
|
+
instanceId,
|
|
77
|
+
accessToken: tokens.access_token,
|
|
78
|
+
refreshToken: tokens.refresh_token,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
async getAuthHeaders() {
|
|
82
|
+
if (!refreshToken) {
|
|
83
|
+
throw new Error('Missing refresh token. Either pass it to the WixAppOAuthStrategy or use the handleOAuthCallback method to retrieve it.');
|
|
84
|
+
}
|
|
85
|
+
const tokensRes = await fetch('https://www.wixapis.com/oauth/access', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
refresh_token: refreshToken,
|
|
92
|
+
client_id: opts.appId,
|
|
93
|
+
client_secret: opts.appSecret,
|
|
94
|
+
grant_type: 'refresh_token',
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
if (tokensRes.status !== 200) {
|
|
98
|
+
throw new Error(`Failed to exchange refresh token for access token. Unexpected status code from Wix OAuth API: ${tokensRes.status}`);
|
|
99
|
+
}
|
|
100
|
+
const tokens = (await tokensRes.json());
|
|
101
|
+
refreshToken = tokens.refresh_token;
|
|
102
|
+
return {
|
|
103
|
+
headers: {
|
|
104
|
+
Authorization: tokens.access_token,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
exports.WixAppOAuthStrategy = WixAppOAuthStrategy;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IOAuthStrategy, Tokens } from './types.js';
|
|
2
|
+
export declare function OAuthStrategy(config: {
|
|
3
|
+
clientId: string;
|
|
4
|
+
tokens?: Tokens;
|
|
5
|
+
}): IOAuthStrategy;
|
|
6
|
+
export interface TokenResponse {
|
|
7
|
+
access_token: string;
|
|
8
|
+
expires_in: number;
|
|
9
|
+
refresh_token: string | null;
|
|
10
|
+
token_type: string;
|
|
11
|
+
scope?: string | null;
|
|
12
|
+
}
|