@frontegg/nextjs 6.3.0 → 6.4.0-alpha.3053691410
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/README.md +245 -4
- package/index.cjs.js +19 -18
- package/index.esm.js +19 -18
- package/package.json +5 -5
- package/session.d.ts +3 -1
package/README.md
CHANGED
|
@@ -1,7 +1,248 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Frontegg is a web platform where SaaS companies can set up their fully managed, scalable and brand aware - SaaS features
|
|
4
|
+
and integrate them into their SaaS portals in up to 5 lines of code.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Table of Contents
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Create new NextJS project](#create-new-nextjs-project)
|
|
10
|
+
- [Add to existing project](#add-to-existing-project)
|
|
11
|
+
- [Getting Started](#getting-started)
|
|
12
|
+
- [Create Frontegg worksapce](#create-frontegg-worksapce)
|
|
13
|
+
- [Setup environment](#setup-environment)
|
|
14
|
+
- [Documentation](#documentation)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Frontegg Provider Options](#frontegg-provider-options)
|
|
17
|
+
- [getSession](#getsession)
|
|
18
|
+
- [withSSRSession](#withssrsession)
|
|
19
|
+
- [Next.js middlewares usage](#nextjs-middlewares-usage)
|
|
20
|
+
- for more [visit](https://docs.frontegg.com/docs/self-service-introduction)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Create new NextJS project
|
|
25
|
+
|
|
26
|
+
To start a new Create Next App project with TypeScript, you can run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx create-next-app --example "https://github.com/frontegg/frontegg-nextjs" --example-path "apps/example" my-nextjs-app-name
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
or
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
yarn create next-app --example "https://github.com/frontegg/frontegg-nextjs" --example-path "apps/example" my-nextjs-app-name
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> If you've previously installed `create-react-app` globally via `npm install -g create-next-app`, we recommend you uninstall the package using `npm uninstall -g create-next-app` or `yarn global remove create-next-app` to ensure that `npx` always uses the latest version.
|
|
39
|
+
>
|
|
40
|
+
> Global installations of `create-next-app` are no longer supported.
|
|
41
|
+
|
|
42
|
+
### Add to existing project
|
|
43
|
+
|
|
44
|
+
To Add Frontegg to your existing Nextjs project, follow below steps:
|
|
45
|
+
|
|
46
|
+
1. Use package manager to install Frontegg Next.JS library.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install --save @frontegg/nextjs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
or
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
yarn add --save @frontegg/nextjs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
2. Wrap the default export with `withFronteggApp` in `./pages/_app.tsx`:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// ./pages/_app.tsx
|
|
62
|
+
|
|
63
|
+
import { withFronteggApp } from '@frontegg/nextjs';
|
|
64
|
+
|
|
65
|
+
function CustomApp({ Component, pageProps }: AppProps) {
|
|
66
|
+
return <Component {...pageProps} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default withFronteggApp(CustomApp);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. Create files for frontegg middleware under `./pages/api/frontegg/[...frontegg-middleware].ts`:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// ./pages/api/frontegg/[...frontegg-middleware].ts
|
|
76
|
+
|
|
77
|
+
export { fronteggMiddleware as default } from '@frontegg/nextjs';
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
4. Create placeholder pages for frontegg router under `./pages/[...frontegg-router].tsx`:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// ./pages/[...frontegg-router].tsx
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
FronteggRouter as default,
|
|
87
|
+
FronteggRouterProps as getServerSideProps,
|
|
88
|
+
} from '@frontegg/nextjs';
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Getting Started
|
|
92
|
+
|
|
93
|
+
### Create Frontegg worksapce
|
|
94
|
+
|
|
95
|
+
Navigate to [Frontegg Portal Settgins](https://portal.frontegg.com/development/settings), If you don't have application
|
|
96
|
+
follow integration steps after signing up.
|
|
97
|
+
|
|
98
|
+
Next, configure the "Allowed Origins" in your application under "Domain" tab of the "Settings" page :
|
|
99
|
+
|
|
100
|
+
- http://localhost:3000 // for development environments
|
|
101
|
+
- https://my-company-domain.com // for production environments
|
|
102
|
+
|
|
103
|
+
Copy ClientID, Frontegg Domain from "Settings" page, You'll need these values in the next step.
|
|
104
|
+
|
|
105
|
+
### Setup environment
|
|
106
|
+
|
|
107
|
+
To setup your Next.js application to communicate with Frontegg, you have to create a new file named `.env.local` under
|
|
108
|
+
your root project directory, this file will be used to store environment variables that will be used, configuration
|
|
109
|
+
options:
|
|
110
|
+
|
|
111
|
+
```dotenv
|
|
112
|
+
# The AppUrl is to tell Frontegg your application hostname
|
|
113
|
+
FRONTEGG_APP_URL='http://localhost:3000'
|
|
114
|
+
|
|
115
|
+
# The Frontegg domain is your unique URL to connect to the Frontegg gateway
|
|
116
|
+
FRONTEGG_BASE_URL='https://{YOUR_SUB_DOMAIN}.frontegg.com'
|
|
117
|
+
|
|
118
|
+
# Your Frontegg application's Client ID
|
|
119
|
+
FRONTEGG_CLIENT_ID='{YOUR_APPLICATION_CLIENT_ID}'
|
|
120
|
+
|
|
121
|
+
# The statless session encruption password, used to encrypt
|
|
122
|
+
# jwt before sending it to the client side.
|
|
123
|
+
#
|
|
124
|
+
# For quick password generation use the following command:
|
|
125
|
+
# node -e "console.log(crypto.randomBytes(32).toString('hex'))"
|
|
126
|
+
FRONTEGG_ENCRYPTION_PASSWORD='{SESSION_ENCRYPTION_PASSWORD}'
|
|
127
|
+
|
|
128
|
+
# The statless session cookie name
|
|
129
|
+
FRONTEGG_COOKIE_NAME='fe_session'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Documentation
|
|
133
|
+
|
|
134
|
+
### API Reference
|
|
135
|
+
|
|
136
|
+
Visit [Frontegg Docs](https://docs.frontegg.com) for the full documentation.
|
|
137
|
+
|
|
138
|
+
### Frontegg Provider Options
|
|
139
|
+
|
|
140
|
+
Pass seconds argument to `withFronteggApp` function in `_app.ts` file to customize
|
|
141
|
+
Frontegg library.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
// ./pages/_app.tsx
|
|
145
|
+
|
|
146
|
+
import { withFronteggApp } from '@frontegg/nextjs';
|
|
147
|
+
|
|
148
|
+
function CustomApp({ Component, pageProps }: AppProps) {
|
|
149
|
+
return <Component {...pageProps} />;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default withFronteggApp(CustomApp, {
|
|
153
|
+
/**
|
|
154
|
+
* Frontegg options for customizations
|
|
155
|
+
*/
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### getSession
|
|
160
|
+
|
|
161
|
+
For any pages that required AccessToken in Server Side, you can use:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { GetServerSideProps } from 'next';
|
|
165
|
+
import { getSession } from '@frontegg/nextjs';
|
|
166
|
+
|
|
167
|
+
export default function MyPage({ products }) {
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
<h1>My Page</h1>
|
|
171
|
+
{products}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const getServerSideProps: GetServerSideProps = async (context) => {
|
|
177
|
+
const session = await getSession(context.req);
|
|
178
|
+
if (session) {
|
|
179
|
+
const { data } = await fetch('{external}/product', {
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: 'bearer ' + session.accessToken,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return { props: { products: data } };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { props: { products: [] } };
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### withSSRSession
|
|
192
|
+
|
|
193
|
+
withSSRSession HOC can be used to automatic redirect users to login screen if not logged in:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { GetServerSideProps } from 'next';
|
|
197
|
+
import { withSSRSession } from '@frontegg/nextjs';
|
|
198
|
+
|
|
199
|
+
export default function MyPage({ products }) {
|
|
200
|
+
return (
|
|
201
|
+
<div>
|
|
202
|
+
<h1>My Page</h1>
|
|
203
|
+
{products}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const getServerSideProps: GetServerSideProps = withSSRSession(
|
|
209
|
+
async (context, session) => {
|
|
210
|
+
const { data } = await fetch('{external}/product', {
|
|
211
|
+
headers: {
|
|
212
|
+
Authorization: 'bearer ' + session.accessToken,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
return { props: { products: data } };
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Next.js middlewares usage
|
|
221
|
+
|
|
222
|
+
To prevent access unauthenticated user to all routes, use [Next.js middlewares](https://nextjs.org/docs/advanced-features/middleware).
|
|
223
|
+
|
|
224
|
+
**Note: If you were using Middleware prior to 12.2, please see the [upgrade guide](https://nextjs.org/docs/messages/middleware-upgrade-guide).**
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// /middleware.ts
|
|
228
|
+
import { NextResponse } from "next/server";
|
|
229
|
+
import type { NextRequest } from "next/server";
|
|
230
|
+
import { getSession } from '@frontegg/nextjs';
|
|
231
|
+
|
|
232
|
+
export const middleware = async (request: NextRequest) => {
|
|
233
|
+
const session = await getSession(request);
|
|
234
|
+
|
|
235
|
+
console.log("middleware session", session);
|
|
236
|
+
|
|
237
|
+
if(!session){
|
|
238
|
+
// redirect unauthenticated user to /account/login page
|
|
239
|
+
return NextResponse.redirect(new URL('/account/login', req.url))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return NextResponse.next();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const config = {
|
|
246
|
+
matcher: "/(.*)",
|
|
247
|
+
};
|
|
248
|
+
```
|
package/index.cjs.js
CHANGED
|
@@ -1367,44 +1367,45 @@ function _defineProperty(obj, key, value) {
|
|
|
1367
1367
|
|
|
1368
1368
|
function getSession(req) {
|
|
1369
1369
|
return __awaiter(this, void 0, void 0, /*#__PURE__*/regenerator.mark(function _callee() {
|
|
1370
|
-
var sealFromCookies, compressedJwt, jwt, publicKey, _yield$jwtVerify, payload, session;
|
|
1370
|
+
var cookieStr, sealFromCookies, compressedJwt, jwt, publicKey, _yield$jwtVerify, payload, session;
|
|
1371
1371
|
|
|
1372
1372
|
return regenerator.wrap(function _callee$(_context) {
|
|
1373
1373
|
while (1) {
|
|
1374
1374
|
switch (_context.prev = _context.next) {
|
|
1375
1375
|
case 0:
|
|
1376
1376
|
_context.prev = 0;
|
|
1377
|
-
|
|
1377
|
+
cookieStr = "credentials" in req ? req.headers.get("cookie") || "" : req.headers.cookie || "";
|
|
1378
|
+
sealFromCookies = cookie__default["default"].parse(cookieStr)[fronteggConfig.cookieName];
|
|
1378
1379
|
|
|
1379
1380
|
if (sealFromCookies) {
|
|
1380
|
-
_context.next =
|
|
1381
|
+
_context.next = 5;
|
|
1381
1382
|
break;
|
|
1382
1383
|
}
|
|
1383
1384
|
|
|
1384
1385
|
return _context.abrupt("return", undefined);
|
|
1385
1386
|
|
|
1386
|
-
case
|
|
1387
|
-
_context.next =
|
|
1387
|
+
case 5:
|
|
1388
|
+
_context.next = 7;
|
|
1388
1389
|
return ironSession.unsealData(sealFromCookies, {
|
|
1389
1390
|
password: fronteggConfig.passwordsAsMap
|
|
1390
1391
|
});
|
|
1391
1392
|
|
|
1392
|
-
case
|
|
1393
|
+
case 7:
|
|
1393
1394
|
compressedJwt = _context.sent;
|
|
1394
|
-
_context.next =
|
|
1395
|
+
_context.next = 10;
|
|
1395
1396
|
return uncompress(compressedJwt);
|
|
1396
1397
|
|
|
1397
|
-
case
|
|
1398
|
+
case 10:
|
|
1398
1399
|
jwt = _context.sent;
|
|
1399
|
-
_context.next =
|
|
1400
|
+
_context.next = 13;
|
|
1400
1401
|
return fronteggConfig.getJwtPublicKey();
|
|
1401
1402
|
|
|
1402
|
-
case
|
|
1403
|
+
case 13:
|
|
1403
1404
|
publicKey = _context.sent;
|
|
1404
|
-
_context.next =
|
|
1405
|
+
_context.next = 16;
|
|
1405
1406
|
return jose.jwtVerify(jwt, publicKey);
|
|
1406
1407
|
|
|
1407
|
-
case
|
|
1408
|
+
case 16:
|
|
1408
1409
|
_yield$jwtVerify = _context.sent;
|
|
1409
1410
|
payload = _yield$jwtVerify.payload;
|
|
1410
1411
|
session = {
|
|
@@ -1413,27 +1414,27 @@ function getSession(req) {
|
|
|
1413
1414
|
};
|
|
1414
1415
|
|
|
1415
1416
|
if (!(session.user.exp * 1000 < Date.now())) {
|
|
1416
|
-
_context.next =
|
|
1417
|
+
_context.next = 21;
|
|
1417
1418
|
break;
|
|
1418
1419
|
}
|
|
1419
1420
|
|
|
1420
1421
|
return _context.abrupt("return", undefined);
|
|
1421
1422
|
|
|
1422
|
-
case
|
|
1423
|
+
case 21:
|
|
1423
1424
|
return _context.abrupt("return", session);
|
|
1424
1425
|
|
|
1425
|
-
case
|
|
1426
|
-
_context.prev =
|
|
1426
|
+
case 24:
|
|
1427
|
+
_context.prev = 24;
|
|
1427
1428
|
_context.t0 = _context["catch"](0);
|
|
1428
1429
|
console.error(_context.t0);
|
|
1429
1430
|
return _context.abrupt("return", undefined);
|
|
1430
1431
|
|
|
1431
|
-
case
|
|
1432
|
+
case 28:
|
|
1432
1433
|
case "end":
|
|
1433
1434
|
return _context.stop();
|
|
1434
1435
|
}
|
|
1435
1436
|
}
|
|
1436
|
-
}, _callee, null, [[0,
|
|
1437
|
+
}, _callee, null, [[0, 24]]);
|
|
1437
1438
|
}));
|
|
1438
1439
|
}
|
|
1439
1440
|
function withSSRSession(handler) {
|
package/index.esm.js
CHANGED
|
@@ -1341,44 +1341,45 @@ function _defineProperty(obj, key, value) {
|
|
|
1341
1341
|
|
|
1342
1342
|
function getSession(req) {
|
|
1343
1343
|
return __awaiter(this, void 0, void 0, /*#__PURE__*/regenerator.mark(function _callee() {
|
|
1344
|
-
var sealFromCookies, compressedJwt, jwt, publicKey, _yield$jwtVerify, payload, session;
|
|
1344
|
+
var cookieStr, sealFromCookies, compressedJwt, jwt, publicKey, _yield$jwtVerify, payload, session;
|
|
1345
1345
|
|
|
1346
1346
|
return regenerator.wrap(function _callee$(_context) {
|
|
1347
1347
|
while (1) {
|
|
1348
1348
|
switch (_context.prev = _context.next) {
|
|
1349
1349
|
case 0:
|
|
1350
1350
|
_context.prev = 0;
|
|
1351
|
-
|
|
1351
|
+
cookieStr = "credentials" in req ? req.headers.get("cookie") || "" : req.headers.cookie || "";
|
|
1352
|
+
sealFromCookies = cookie.parse(cookieStr)[fronteggConfig.cookieName];
|
|
1352
1353
|
|
|
1353
1354
|
if (sealFromCookies) {
|
|
1354
|
-
_context.next =
|
|
1355
|
+
_context.next = 5;
|
|
1355
1356
|
break;
|
|
1356
1357
|
}
|
|
1357
1358
|
|
|
1358
1359
|
return _context.abrupt("return", undefined);
|
|
1359
1360
|
|
|
1360
|
-
case
|
|
1361
|
-
_context.next =
|
|
1361
|
+
case 5:
|
|
1362
|
+
_context.next = 7;
|
|
1362
1363
|
return unsealData(sealFromCookies, {
|
|
1363
1364
|
password: fronteggConfig.passwordsAsMap
|
|
1364
1365
|
});
|
|
1365
1366
|
|
|
1366
|
-
case
|
|
1367
|
+
case 7:
|
|
1367
1368
|
compressedJwt = _context.sent;
|
|
1368
|
-
_context.next =
|
|
1369
|
+
_context.next = 10;
|
|
1369
1370
|
return uncompress(compressedJwt);
|
|
1370
1371
|
|
|
1371
|
-
case
|
|
1372
|
+
case 10:
|
|
1372
1373
|
jwt = _context.sent;
|
|
1373
|
-
_context.next =
|
|
1374
|
+
_context.next = 13;
|
|
1374
1375
|
return fronteggConfig.getJwtPublicKey();
|
|
1375
1376
|
|
|
1376
|
-
case
|
|
1377
|
+
case 13:
|
|
1377
1378
|
publicKey = _context.sent;
|
|
1378
|
-
_context.next =
|
|
1379
|
+
_context.next = 16;
|
|
1379
1380
|
return jwtVerify(jwt, publicKey);
|
|
1380
1381
|
|
|
1381
|
-
case
|
|
1382
|
+
case 16:
|
|
1382
1383
|
_yield$jwtVerify = _context.sent;
|
|
1383
1384
|
payload = _yield$jwtVerify.payload;
|
|
1384
1385
|
session = {
|
|
@@ -1387,27 +1388,27 @@ function getSession(req) {
|
|
|
1387
1388
|
};
|
|
1388
1389
|
|
|
1389
1390
|
if (!(session.user.exp * 1000 < Date.now())) {
|
|
1390
|
-
_context.next =
|
|
1391
|
+
_context.next = 21;
|
|
1391
1392
|
break;
|
|
1392
1393
|
}
|
|
1393
1394
|
|
|
1394
1395
|
return _context.abrupt("return", undefined);
|
|
1395
1396
|
|
|
1396
|
-
case
|
|
1397
|
+
case 21:
|
|
1397
1398
|
return _context.abrupt("return", session);
|
|
1398
1399
|
|
|
1399
|
-
case
|
|
1400
|
-
_context.prev =
|
|
1400
|
+
case 24:
|
|
1401
|
+
_context.prev = 24;
|
|
1401
1402
|
_context.t0 = _context["catch"](0);
|
|
1402
1403
|
console.error(_context.t0);
|
|
1403
1404
|
return _context.abrupt("return", undefined);
|
|
1404
1405
|
|
|
1405
|
-
case
|
|
1406
|
+
case 28:
|
|
1406
1407
|
case "end":
|
|
1407
1408
|
return _context.stop();
|
|
1408
1409
|
}
|
|
1409
1410
|
}
|
|
1410
|
-
}, _callee, null, [[0,
|
|
1411
|
+
}, _callee, null, [[0, 24]]);
|
|
1411
1412
|
}));
|
|
1412
1413
|
}
|
|
1413
1414
|
function withSSRSession(handler) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontegg/nextjs",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0-alpha.3053691410",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@frontegg/js": "6.
|
|
6
|
-
"@frontegg/react-hooks": "6.
|
|
5
|
+
"@frontegg/js": "6.6.1",
|
|
6
|
+
"@frontegg/react-hooks": "6.6.1",
|
|
7
7
|
"jose": "^4.8.0",
|
|
8
|
-
"iron-session": "^6.1
|
|
8
|
+
"iron-session": "^6.2.1",
|
|
9
9
|
"http-proxy": "^1.18.1",
|
|
10
10
|
"cookie": "^0.5.0"
|
|
11
11
|
},
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
"main": "./index.cjs.js",
|
|
17
17
|
"module": "./index.esm.js",
|
|
18
18
|
"typings": "./index.d.ts"
|
|
19
|
-
}
|
|
19
|
+
}
|
package/session.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ import { IncomingMessage } from 'http';
|
|
|
3
3
|
import { FronteggNextJSSession } from './types';
|
|
4
4
|
import { ParsedUrlQuery } from 'querystring';
|
|
5
5
|
import { GetServerSidePropsContext, GetServerSidePropsResult, PreviewData } from 'next';
|
|
6
|
-
|
|
6
|
+
declare type RequestType = IncomingMessage | Request;
|
|
7
|
+
export declare function getSession(req: RequestType): Promise<FronteggNextJSSession | undefined>;
|
|
7
8
|
export declare function withSSRSession<P extends {
|
|
8
9
|
[key: string]: any;
|
|
9
10
|
} = {
|
|
10
11
|
[key: string]: any;
|
|
11
12
|
}, Q extends ParsedUrlQuery = ParsedUrlQuery, D extends PreviewData = PreviewData>(handler: (context: GetServerSidePropsContext<Q>, session: FronteggNextJSSession) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>): (context: GetServerSidePropsContext<Q>) => Promise<GetServerSidePropsResult<P>>;
|
|
13
|
+
export {};
|