Add Handler factories (#61)
* Add createManifestHandler * Add register handler factory * Add docs to handlers factory * Improve tsup configuration (select entry points) * Reexport symbols from index * Improve tests
This commit is contained in:
parent
df504ecd29
commit
0352356fd3
14 changed files with 632 additions and 61 deletions
75
docs/api-handlers.md
Normal file
75
docs/api-handlers.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Api Handlers
|
||||
|
||||
Saleor Apps are meant to work in serverless environment, where Cloud Functions are the foundations of server-side code.
|
||||
|
||||
Currently, Saleor heavily relies on Next.js, but in the future, other platforms will be supported.
|
||||
|
||||
## Required handlers
|
||||
|
||||
Saleor requires 2 endpoints to be available for a standalone app:
|
||||
|
||||
- Manifest endpoint - Returns JSON object with app properties, like its name or permissions. [Read more](https://docs.saleor.io/docs/3.x/developer/extending/apps/manifest)
|
||||
- Register endpoint - During the installation process, Saleor sends `POST` request with auth token to this endpoint. [Read more](https://docs.saleor.io/docs/3.x/developer/extending/apps/installing-apps#installation-using-graphql-api)
|
||||
|
||||
## Api handlers built-in SDK
|
||||
|
||||
To hide Saleor internal logic, app-sdk provides handlers factories. They should work with minimal configuration, leaving
|
||||
App creators space for domain logic.
|
||||
|
||||
### Manifest handler factory
|
||||
|
||||
Example usage of manifest handler in Next.js
|
||||
|
||||
```typescript
|
||||
// pages/api/manifest.ts
|
||||
|
||||
import { createManifestHandler } from "@app-sdk/handlers/next";
|
||||
|
||||
export default createManifestHandler({
|
||||
manifestFactory(context) {
|
||||
return {
|
||||
name: "My Saleor App",
|
||||
tokenTargetUrl: `${context.appBaseUrl}/api/register`,
|
||||
appUrl: context.appBaseUrl,
|
||||
permissions: [],
|
||||
id: "my-saleor-app",
|
||||
version: "1",
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Options provided to handler factory
|
||||
|
||||
```typescript
|
||||
type CreateManifestHandlerOptions = {
|
||||
manifestFactory(context: { appBaseUrl: string }): AppManifest;
|
||||
};
|
||||
```
|
||||
|
||||
See [source](./src/handlers/next/create-manifest-handler.ts) for more details. See [manifest](../src/types.ts) too.
|
||||
|
||||
### App register handler factory
|
||||
|
||||
Example usage of app register handler in Next.js
|
||||
|
||||
```typescript
|
||||
// pages/api/register.ts
|
||||
|
||||
import { createAppRegisterHandler } from "@app-sdk/handlers/next";
|
||||
import { VercelAPL } from "./vercel-apl";
|
||||
|
||||
export default createAppRegisterHandler({
|
||||
apl: new VercelAPL(),
|
||||
});
|
||||
```
|
||||
|
||||
Options provided to handler factory
|
||||
|
||||
```typescript
|
||||
export type CreateAppRegisterHandlerOptions = {
|
||||
apl: APL;
|
||||
};
|
||||
```
|
||||
|
||||
See [APL](./apl.md) for details what is Auth Persistence Layer in Saleor apps
|
16
package.json
16
package.json
|
@ -6,8 +6,8 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm build",
|
||||
"watch": "tsup-node src/* --format esm,cjs --dts --watch",
|
||||
"build": "tsup-node src/* --format esm,cjs --dts && clear-package-json package.json -o dist/package.json --fields publishConfig",
|
||||
"watch": "tsup --watch",
|
||||
"build": "tsup && clear-package-json package.json -o dist/package.json --fields publishConfig",
|
||||
"clean": "rm -rf ./dist/*",
|
||||
"test": "vitest",
|
||||
"test:ci": "CI=true vitest --coverage",
|
||||
|
@ -20,7 +20,8 @@
|
|||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
"react-dom": ">=17",
|
||||
"next": "^12"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
|
@ -28,11 +29,13 @@
|
|||
"graphql": "^16.6.0",
|
||||
"jose": "^4.9.2",
|
||||
"node-fetch": "^3.2.10",
|
||||
"retes": "^0.32.0",
|
||||
"retes": "^0.33.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-c8": "^0.23.2",
|
||||
"node-mocks-http": "^1.11.0",
|
||||
"next": "^12.3.0",
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/debug": "^4.1.7",
|
||||
|
@ -98,6 +101,11 @@
|
|||
"import": "./app-bridge/index.mjs",
|
||||
"require": "./app-bridge/index.js"
|
||||
},
|
||||
"./handlers/next": {
|
||||
"types": "./handlers/next/index.d.ts",
|
||||
"import": "./handlers/next/index.mjs",
|
||||
"require": "./handlers/next/index.js"
|
||||
},
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.mjs",
|
||||
|
|
306
pnpm-lock.yaml
306
pnpm-lock.yaml
|
@ -29,12 +29,14 @@ specifiers:
|
|||
husky: ^8.0.1
|
||||
jose: ^4.9.2
|
||||
jsdom: ^20.0.0
|
||||
next: ^12.3.0
|
||||
node-fetch: ^3.2.10
|
||||
node-mocks-http: ^1.11.0
|
||||
prettier: 2.7.1
|
||||
react: ^18.2.0
|
||||
react-dom: 18.2.0
|
||||
release-it: ^15.4.1
|
||||
retes: ^0.32.0
|
||||
retes: ^0.33.0
|
||||
tsm: ^2.2.2
|
||||
tsup: ^6.2.3
|
||||
typescript: ^4.8.2
|
||||
|
@ -49,7 +51,7 @@ dependencies:
|
|||
graphql: 16.6.0
|
||||
jose: 4.9.2
|
||||
node-fetch: 3.2.10
|
||||
retes: 0.32.0
|
||||
retes: 0.33.0
|
||||
uuid: 8.3.2
|
||||
|
||||
devDependencies:
|
||||
|
@ -77,6 +79,8 @@ devDependencies:
|
|||
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.23.0
|
||||
husky: 8.0.1
|
||||
jsdom: 20.0.0
|
||||
next: 12.3.0_biqbaboplfbrettd7655fr4n2y
|
||||
node-mocks-http: 1.11.0
|
||||
prettier: 2.7.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
|
@ -482,6 +486,127 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@next/env/12.3.0:
|
||||
resolution: {integrity: sha512-PTJpjAFVbzBQ9xXpzMTroShvD5YDIIy46jQ7d4LrWpY+/5a8H90Tm8hE3Hvkc5RBRspVo7kvEOnqQms0A+2Q6w==}
|
||||
dev: true
|
||||
|
||||
/@next/swc-android-arm-eabi/12.3.0:
|
||||
resolution: {integrity: sha512-/PuirPnAKsYBw93w/7Q9hqy+KGOU9mjYprZ/faxMUJh/dc6v3rYLxkZKNG9nFPIW4QKNTCnhP40xF9hLnxO+xg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/12.3.0:
|
||||
resolution: {integrity: sha512-OaI+FhAM6P9B6Ybwbn0Zl8YwWido0lLwhDBi9WiYCh4RQmIXAyVIoIJPHo4fP05+mXaJ/k1trvDvuURvHOq2qw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/12.3.0:
|
||||
resolution: {integrity: sha512-9s4d3Mhii+WFce8o8Jok7WC3Bawkr9wEUU++SJRptjU1L5tsfYJMrSYCACHLhZujziNDLyExe4Hwwsccps1sfg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/12.3.0:
|
||||
resolution: {integrity: sha512-2scC4MqUTwGwok+wpVxP+zWp7WcCAVOtutki2E1n99rBOTnUOX6qXkgxSy083yBN6GqwuC/dzHeN7hIKjavfRA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/12.3.0:
|
||||
resolution: {integrity: sha512-xAlruUREij/bFa+qsE1tmsP28t7vz02N4ZDHt2lh3uJUniE0Ne9idyIDLc1Ed0IF2RjfgOp4ZVunuS3OM0sngw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/12.3.0:
|
||||
resolution: {integrity: sha512-jin2S4VT/cugc2dSZEUIabhYDJNgrUh7fufbdsaAezgcQzqfdfJqfxl4E9GuafzB4cbRPTaqA0V5uqbp0IyGkQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/12.3.0:
|
||||
resolution: {integrity: sha512-RqJHDKe0WImeUrdR0kayTkRWgp4vD/MS7g0r6Xuf8+ellOFH7JAAJffDW3ayuVZeMYOa7RvgNFcOoWnrTUl9Nw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/12.3.0:
|
||||
resolution: {integrity: sha512-nvNWoUieMjvDjpYJ/4SQe9lQs2xMj6ZRs8N+bmTrVu9leY2Fg3WD6W9p/1uU9hGO8u+OdF13wc4iRShu/WYIHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/12.3.0:
|
||||
resolution: {integrity: sha512-4ajhIuVU9PeQCMMhdDgZTLrHmjbOUFuIyg6J19hZqwEwDTSqQyrSLkbJs2Nd7IRiM6Ul/XyrtEFCpk4k+xD2+w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/12.3.0:
|
||||
resolution: {integrity: sha512-U092RBYbaGxoMAwpauePJEu2PuZSEoUCGJBvsptQr2/2XIMwAJDYM4c/M5NfYEsBr+yjvsYNsOpYfeQ88D82Yg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/12.3.0:
|
||||
resolution: {integrity: sha512-pzSzaxjDEJe67bUok9Nxf9rykbJfHXW0owICFsPBsqHyc+cr8vpF7g9e2APTCddtVhvjkga9ILoZJ9NxWS7Yiw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/12.3.0:
|
||||
resolution: {integrity: sha512-MQGUpMbYhQmTZ06a9e0hPQJnxFMwETo2WtyAotY3GEzbNCQVbCGhsvqEKcl+ZEHgShlHXUWvSffq1ZscY6gK7A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/12.3.0:
|
||||
resolution: {integrity: sha512-C/nw6OgQpEULWqs+wgMHXGvlJLguPRFFGqR2TAqWBerQ8J+Sg3z1ZTqwelkSi4FoqStGuZ2UdFHIDN1ySmR1xA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -648,6 +773,12 @@ packages:
|
|||
engines: {node: '>=14.16'}
|
||||
dev: true
|
||||
|
||||
/@swc/helpers/0.4.11:
|
||||
resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==}
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@szmarczak/http-timer/5.0.1:
|
||||
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
@ -976,6 +1107,14 @@ packages:
|
|||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||
dev: true
|
||||
|
||||
/accepts/1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
negotiator: 0.6.3
|
||||
dev: true
|
||||
|
||||
/acorn-globals/6.0.0:
|
||||
resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
|
||||
dependencies:
|
||||
|
@ -1520,6 +1659,13 @@ packages:
|
|||
resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==}
|
||||
dev: true
|
||||
|
||||
/content-disposition/0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/convert-source-map/1.8.0:
|
||||
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
|
||||
dependencies:
|
||||
|
@ -1702,6 +1848,11 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/depd/1.1.2:
|
||||
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/depd/2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -2801,6 +2952,11 @@ packages:
|
|||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/fs-extra/8.1.0:
|
||||
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
|
||||
engines: {node: '>=6 <7 || >=8'}
|
||||
|
@ -3784,6 +3940,15 @@ packages:
|
|||
semver: 6.3.0
|
||||
dev: true
|
||||
|
||||
/media-typer/0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||
dev: true
|
||||
|
||||
/merge-stream/2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
@ -3792,6 +3957,11 @@ packages:
|
|||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
/methods/1.1.2:
|
||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/micromatch/4.0.5:
|
||||
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
@ -3811,6 +3981,12 @@ packages:
|
|||
mime-db: 1.52.0
|
||||
dev: true
|
||||
|
||||
/mime/1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/mimic-fn/2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -3879,6 +4055,11 @@ packages:
|
|||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/negotiator/0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/netmask/2.0.2:
|
||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
@ -3891,6 +4072,51 @@ packages:
|
|||
type-fest: 2.19.0
|
||||
dev: true
|
||||
|
||||
/next/12.3.0_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-GpzI6me9V1+XYtfK0Ae9WD0mKqHyzQlGq1xH1rzNIYMASo4Tkl4rTe9jSqtBpXFhOS33KohXs9ZY38Akkhdciw==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
fibers: '>= 3.1.0'
|
||||
node-sass: ^6.0.0 || ^7.0.0
|
||||
react: ^17.0.2 || ^18.0.0-0
|
||||
react-dom: ^17.0.2 || ^18.0.0-0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
fibers:
|
||||
optional: true
|
||||
node-sass:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 12.3.0
|
||||
'@swc/helpers': 0.4.11
|
||||
caniuse-lite: 1.0.30001390
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
styled-jsx: 5.0.6_react@18.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 12.3.0
|
||||
'@next/swc-android-arm64': 12.3.0
|
||||
'@next/swc-darwin-arm64': 12.3.0
|
||||
'@next/swc-darwin-x64': 12.3.0
|
||||
'@next/swc-freebsd-x64': 12.3.0
|
||||
'@next/swc-linux-arm-gnueabihf': 12.3.0
|
||||
'@next/swc-linux-arm64-gnu': 12.3.0
|
||||
'@next/swc-linux-arm64-musl': 12.3.0
|
||||
'@next/swc-linux-x64-gnu': 12.3.0
|
||||
'@next/swc-linux-x64-musl': 12.3.0
|
||||
'@next/swc-win32-arm64-msvc': 12.3.0
|
||||
'@next/swc-win32-ia32-msvc': 12.3.0
|
||||
'@next/swc-win32-x64-msvc': 12.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
dev: true
|
||||
|
||||
/node-domexception/1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
|
@ -3915,6 +4141,22 @@ packages:
|
|||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
/node-mocks-http/1.11.0:
|
||||
resolution: {integrity: sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
content-disposition: 0.5.4
|
||||
depd: 1.1.2
|
||||
fresh: 0.5.2
|
||||
merge-descriptors: 1.0.1
|
||||
methods: 1.1.2
|
||||
mime: 1.6.0
|
||||
parseurl: 1.3.3
|
||||
range-parser: 1.2.1
|
||||
type-is: 1.6.18
|
||||
dev: true
|
||||
|
||||
/node-releases/2.0.6:
|
||||
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
|
||||
dev: true
|
||||
|
@ -4176,6 +4418,11 @@ packages:
|
|||
entities: 4.4.0
|
||||
dev: true
|
||||
|
||||
/parseurl/1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: true
|
||||
|
||||
/path-exists/4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -4238,6 +4485,15 @@ packages:
|
|||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
/postcss/8.4.14:
|
||||
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.4
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/postcss/8.4.16:
|
||||
resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
@ -4355,6 +4611,11 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/range-parser/1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/raw-body/2.5.1:
|
||||
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -4561,10 +4822,11 @@ packages:
|
|||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/retes/0.32.0:
|
||||
resolution: {integrity: sha512-pCz9SdztBaU4QMX/YA3A5tfEVgHdLwGdc7hJOJHF+XTJzHJksiwQTquZuz32DeJ4KRgo2P8h5sdPnyi+v4AzwQ==}
|
||||
/retes/0.33.0:
|
||||
resolution: {integrity: sha512-I6V1G2JkJ2JFIFSVuultNXepf7BW8SCaSUOq5IETM2fDjFim5Dg5F1zU/QbplNW0mqkk8QCw+I722v3nPkpRlA==}
|
||||
dependencies:
|
||||
busboy: 1.6.0
|
||||
zod: 3.19.1
|
||||
dev: false
|
||||
|
||||
/retry/0.13.1:
|
||||
|
@ -4867,6 +5129,22 @@ packages:
|
|||
acorn: 8.8.0
|
||||
dev: true
|
||||
|
||||
/styled-jsx/5.0.6_react@18.2.0:
|
||||
resolution: {integrity: sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '*'
|
||||
babel-plugin-macros: '*'
|
||||
react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/sucrase/3.25.0:
|
||||
resolution: {integrity: sha512-WxTtwEYXSmZArPGStGBicyRsg5TBEFhT5b7N+tF+zauImP0Acy+CoUK0/byJ8JNPK/5lbpWIVuFagI4+0l85QQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -5141,6 +5419,14 @@ packages:
|
|||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
/type-is/1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
dev: true
|
||||
|
||||
/typedarray-to-buffer/3.1.5:
|
||||
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
|
||||
dependencies:
|
||||
|
@ -5237,6 +5523,14 @@ packages:
|
|||
requires-port: 1.0.0
|
||||
dev: true
|
||||
|
||||
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
@ -5603,4 +5897,8 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/zod/3.19.1:
|
||||
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
|
||||
dev: false
|
||||
|
||||
publishDirectory: dist
|
||||
|
|
46
src/handlers/next/create-app-register-handler.test.ts
Normal file
46
src/handlers/next/create-app-register-handler.test.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { APL } from "../../APL";
|
||||
import { createAppRegisterHandler } from "./create-app-register-handler";
|
||||
|
||||
describe("create-app-register-handler", () => {
|
||||
it("Sets auth data for correct request", async () => {
|
||||
const mockApl: APL = {
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
getAll: vi.fn(),
|
||||
};
|
||||
|
||||
const { res, req } = createMocks({
|
||||
/**
|
||||
* Use body, instead of params, otherwise - for some reason - param is not accessible in mock request
|
||||
* Maybe this is a bug https://github.com/howardabrams/node-mocks-http/blob/master/lib/mockRequest.js
|
||||
*/
|
||||
body: {
|
||||
auth_token: "mock-auth-token",
|
||||
},
|
||||
headers: {
|
||||
host: "some-saleor-host.cloud",
|
||||
"x-forwarded-proto": "https",
|
||||
"saleor-domain": "https://mock-saleor-domain.saleor.cloud",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const handler = createAppRegisterHandler({
|
||||
apl: mockApl,
|
||||
});
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
/**
|
||||
* It fails -> params.auth_token isn't present
|
||||
*/
|
||||
expect(mockApl.set).toHaveBeenCalledWith({
|
||||
domain: "https://mock-saleor-domain.saleor.cloud",
|
||||
token: "mock-auth-token",
|
||||
});
|
||||
});
|
||||
});
|
43
src/handlers/next/create-app-register-handler.ts
Normal file
43
src/handlers/next/create-app-register-handler.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import type { Handler } from "retes";
|
||||
import { toNextHandler } from "retes/adapter";
|
||||
import { withMethod } from "retes/middleware";
|
||||
import { Response } from "retes/response";
|
||||
|
||||
import { APL } from "../../APL";
|
||||
import { SALEOR_DOMAIN_HEADER } from "../../const";
|
||||
import { withAuthTokenRequired, withSaleorDomainPresent } from "../../middleware";
|
||||
|
||||
export type CreateAppRegisterHandlerOptions = {
|
||||
apl: APL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates API handler for Next.js. Creates handler called by Saleor that registers app.
|
||||
* Hides implementation details if possible
|
||||
* In the future this will be extracted to separate sdk/next package
|
||||
*/
|
||||
export const createAppRegisterHandler = ({ apl }: CreateAppRegisterHandlerOptions) => {
|
||||
const baseHandler: Handler = async (request) => {
|
||||
const authToken = request.params.auth_token;
|
||||
const saleorDomain = request.headers[SALEOR_DOMAIN_HEADER] as string;
|
||||
|
||||
try {
|
||||
await apl.set({ domain: saleorDomain, token: authToken });
|
||||
} catch {
|
||||
return Response.InternalServerError({
|
||||
success: false,
|
||||
error: {
|
||||
message: "Registration failed: could not save the auth data.",
|
||||
},
|
||||
});
|
||||
}
|
||||
return Response.OK({ success: true });
|
||||
};
|
||||
|
||||
return toNextHandler([
|
||||
withMethod("POST"),
|
||||
withSaleorDomainPresent,
|
||||
withAuthTokenRequired,
|
||||
baseHandler,
|
||||
]);
|
||||
};
|
41
src/handlers/next/create-manifest-handler.test.ts
Normal file
41
src/handlers/next/create-manifest-handler.test.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { AppManifest } from "../../types";
|
||||
import { createManifestHandler } from "./create-manifest-handler";
|
||||
|
||||
describe("createManifestHandler", () => {
|
||||
it("Creates a handler that responds with Manifest", async () => {
|
||||
const { res, req } = createMocks({
|
||||
headers: {
|
||||
host: "some-saleor-host.cloud",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const handler = createManifestHandler({
|
||||
manifestFactory(context: { appBaseUrl: string }): AppManifest {
|
||||
return {
|
||||
name: "Mock name",
|
||||
tokenTargetUrl: `${context.appBaseUrl}/api/register`,
|
||||
appUrl: context.appBaseUrl,
|
||||
permissions: [],
|
||||
id: "app-id",
|
||||
version: "1",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._getData()).toEqual({
|
||||
appUrl: "https://some-saleor-host.cloud",
|
||||
id: "app-id",
|
||||
name: "Mock name",
|
||||
permissions: [],
|
||||
tokenTargetUrl: "https://some-saleor-host.cloud/api/register",
|
||||
version: "1",
|
||||
});
|
||||
});
|
||||
});
|
29
src/handlers/next/create-manifest-handler.ts
Normal file
29
src/handlers/next/create-manifest-handler.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Handler } from "retes";
|
||||
import { toNextHandler } from "retes/adapter";
|
||||
import { Response } from "retes/response";
|
||||
|
||||
import { withBaseURL } from "../../middleware";
|
||||
import { AppManifest } from "../../types";
|
||||
|
||||
export type CreateManifestHandlerOptions = {
|
||||
manifestFactory(context: { appBaseUrl: string }): AppManifest;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates API handler for Next.js. Helps with Manifest creation, hides
|
||||
* implementation details if possible
|
||||
* In the future this will be extracted to separate sdk/next package
|
||||
*/
|
||||
export const createManifestHandler = (options: CreateManifestHandlerOptions) => {
|
||||
const baseHandler: Handler = async (request) => {
|
||||
const { baseURL } = request.context;
|
||||
|
||||
const manifest = options.manifestFactory({
|
||||
appBaseUrl: baseURL,
|
||||
});
|
||||
|
||||
return Response.OK(manifest);
|
||||
};
|
||||
|
||||
return toNextHandler([withBaseURL, baseHandler]);
|
||||
};
|
1
src/handlers/next/index.ts
Normal file
1
src/handlers/next/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./create-manifest-handler";
|
3
src/handlers/next/readme.md
Normal file
3
src/handlers/next/readme.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
Handlers with adapters to Next.js
|
||||
|
||||
TODO Extract to separate package
|
1
src/handlers/readme.md
Normal file
1
src/handlers/readme.md
Normal file
|
@ -0,0 +1 @@
|
|||
Place for Saleor-related API handlers - without frameworks
|
58
src/index.ts
58
src/index.ts
|
@ -1,53 +1,5 @@
|
|||
import fg from "fast-glob";
|
||||
import { print } from "graphql/language/printer.js";
|
||||
import path from "path";
|
||||
|
||||
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
const dropFileExtension = (filename: string) => path.parse(filename).name;
|
||||
|
||||
/**
|
||||
* @deprecated remove magic
|
||||
*/
|
||||
export const inferWebhooks = async (
|
||||
baseURL: string,
|
||||
webhooksPath: string,
|
||||
generatedGraphQL: any
|
||||
) => {
|
||||
let entries;
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
entries = await fg(["*.js"], { cwd: webhooksPath });
|
||||
} else {
|
||||
entries = await fg(["*.ts"], { cwd: "pages/api/webhooks" });
|
||||
}
|
||||
|
||||
return entries.map(dropFileExtension).map((name: string) => {
|
||||
const camelCaseName = name.split("-").map(capitalize).join("");
|
||||
|
||||
const eventName = name.toUpperCase().replace(/-/g, "_");
|
||||
let eventType: string;
|
||||
if (Object.values(generatedGraphQL.WebhookEventTypeAsyncEnum).includes(eventName)) {
|
||||
eventType = "asyncEvents";
|
||||
} else if (Object.values(generatedGraphQL.WebhookEventTypeSyncEnum).includes(eventName)) {
|
||||
eventType = "syncEvents";
|
||||
} else {
|
||||
throw Error("Event type not found.");
|
||||
}
|
||||
|
||||
const statement = `${camelCaseName}SubscriptionDocument`;
|
||||
let query: string;
|
||||
if (statement in generatedGraphQL) {
|
||||
query = print((generatedGraphQL as any)[statement]);
|
||||
} else {
|
||||
throw Error("Subscription not found.");
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
[eventType]: [eventName],
|
||||
query,
|
||||
targetUrl: `${baseURL}/api/webhooks/${name}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
export * from "./const";
|
||||
export * from "./headers";
|
||||
export * from "./infer-webhooks";
|
||||
export * from "./types";
|
||||
export * from "./urls";
|
||||
|
|
53
src/infer-webhooks.ts
Normal file
53
src/infer-webhooks.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import fg from "fast-glob";
|
||||
import { print } from "graphql/language/printer";
|
||||
import path from "path";
|
||||
|
||||
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
const dropFileExtension = (filename: string) => path.parse(filename).name;
|
||||
|
||||
/**
|
||||
* @deprecated remove magic -> change CLI to use static code generation
|
||||
*/
|
||||
export const inferWebhooks = async (
|
||||
baseURL: string,
|
||||
webhooksPath: string,
|
||||
generatedGraphQL: any
|
||||
) => {
|
||||
let entries;
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
entries = await fg(["*.js"], { cwd: webhooksPath });
|
||||
} else {
|
||||
entries = await fg(["*.ts"], { cwd: "pages/api/webhooks" });
|
||||
}
|
||||
|
||||
return entries.map(dropFileExtension).map((name: string) => {
|
||||
const camelCaseName = name.split("-").map(capitalize).join("");
|
||||
|
||||
const eventName = name.toUpperCase().replace(/-/g, "_");
|
||||
let eventType: string;
|
||||
if (Object.values(generatedGraphQL.WebhookEventTypeAsyncEnum).includes(eventName)) {
|
||||
eventType = "asyncEvents";
|
||||
} else if (Object.values(generatedGraphQL.WebhookEventTypeSyncEnum).includes(eventName)) {
|
||||
eventType = "syncEvents";
|
||||
} else {
|
||||
throw Error("Event type not found.");
|
||||
}
|
||||
|
||||
const statement = `${camelCaseName}SubscriptionDocument`;
|
||||
let query: string;
|
||||
if (statement in generatedGraphQL) {
|
||||
query = print((generatedGraphQL as any)[statement]);
|
||||
} else {
|
||||
throw Error("Subscription not found.");
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
[eventType]: [eventName],
|
||||
query,
|
||||
targetUrl: `${baseURL}/api/webhooks/${name}`,
|
||||
};
|
||||
});
|
||||
};
|
|
@ -9,6 +9,7 @@ export const withBaseURL: Middleware = (handler) => async (request) => {
|
|||
|
||||
debug("Middleware called with host: %s, protocol %s", host, request.headers["x-forwarded-proto"]);
|
||||
|
||||
request.context ??= {};
|
||||
request.context.baseURL = `${protocol}://${host}`;
|
||||
|
||||
debug("context.baseURL resolved to be: \"%s\"", request.context.baseURL);
|
||||
|
|
20
tsup.config.ts
Normal file
20
tsup.config.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
/**
|
||||
* TODO Introduce breaking change and remove exporting from paths, merge everything to index
|
||||
*/
|
||||
"src/*",
|
||||
"src/index.ts",
|
||||
"src/APL/index.ts",
|
||||
"src/app-bridge/index.ts",
|
||||
"src/handlers/next/index.ts",
|
||||
"src/middleware/index.ts",
|
||||
],
|
||||
dts: true,
|
||||
clean: true,
|
||||
format: ["esm", "cjs"],
|
||||
splitting: true,
|
||||
external: ["**/*.md"],
|
||||
});
|
Loading…
Reference in a new issue