@shopware-ag/app-server-sdk 1.0.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/README.md +43 -0
- package/dist/commonjs/app.d.ts +31 -0
- package/dist/commonjs/app.d.ts.map +1 -0
- package/dist/commonjs/app.js +25 -0
- package/dist/commonjs/app.js.map +1 -0
- package/dist/commonjs/context-resolver.d.ts +30 -0
- package/dist/commonjs/context-resolver.d.ts.map +1 -0
- package/dist/commonjs/context-resolver.js +70 -0
- package/dist/commonjs/context-resolver.js.map +1 -0
- package/dist/commonjs/framework/hono.d.ts +36 -0
- package/dist/commonjs/framework/hono.d.ts.map +1 -0
- package/dist/commonjs/framework/hono.js +84 -0
- package/dist/commonjs/framework/hono.js.map +1 -0
- package/dist/commonjs/helper/app-actions.d.ts +13 -0
- package/dist/commonjs/helper/app-actions.d.ts.map +1 -0
- package/dist/commonjs/helper/app-actions.js +54 -0
- package/dist/commonjs/helper/app-actions.js.map +1 -0
- package/dist/commonjs/http-client.d.ts +67 -0
- package/dist/commonjs/http-client.d.ts.map +1 -0
- package/dist/commonjs/http-client.js +155 -0
- package/dist/commonjs/http-client.js.map +1 -0
- package/dist/commonjs/mod.d.ts +6 -0
- package/dist/commonjs/mod.d.ts.map +1 -0
- package/dist/commonjs/mod.js +16 -0
- package/dist/commonjs/mod.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/registration.d.ts +18 -0
- package/dist/commonjs/registration.d.ts.map +1 -0
- package/dist/commonjs/registration.js +89 -0
- package/dist/commonjs/registration.js.map +1 -0
- package/dist/commonjs/repository.d.ts +51 -0
- package/dist/commonjs/repository.d.ts.map +1 -0
- package/dist/commonjs/repository.js +68 -0
- package/dist/commonjs/repository.js.map +1 -0
- package/dist/commonjs/service/cloudflare.d.ts +43 -0
- package/dist/commonjs/service/cloudflare.d.ts.map +1 -0
- package/dist/commonjs/service/cloudflare.js +45 -0
- package/dist/commonjs/service/cloudflare.js.map +1 -0
- package/dist/commonjs/service/deno.d.ts +18 -0
- package/dist/commonjs/service/deno.d.ts.map +1 -0
- package/dist/commonjs/service/deno.js +48 -0
- package/dist/commonjs/service/deno.js.map +1 -0
- package/dist/commonjs/signer.d.ts +12 -0
- package/dist/commonjs/signer.d.ts.map +1 -0
- package/dist/commonjs/signer.js +58 -0
- package/dist/commonjs/signer.js.map +1 -0
- package/dist/commonjs/types.d.ts +28 -0
- package/dist/commonjs/types.d.ts.map +1 -0
- package/dist/commonjs/types.js +3 -0
- package/dist/commonjs/types.js.map +1 -0
- package/dist/deno/app.d.ts +31 -0
- package/dist/deno/app.d.ts.map +1 -0
- package/dist/deno/app.js +21 -0
- package/dist/deno/app.js.map +1 -0
- package/dist/deno/context-resolver.d.ts +30 -0
- package/dist/deno/context-resolver.d.ts.map +1 -0
- package/dist/deno/context-resolver.js +65 -0
- package/dist/deno/context-resolver.js.map +1 -0
- package/dist/deno/framework/hono.d.ts +36 -0
- package/dist/deno/framework/hono.d.ts.map +1 -0
- package/dist/deno/framework/hono.js +81 -0
- package/dist/deno/framework/hono.js.map +1 -0
- package/dist/deno/helper/app-actions.d.ts +13 -0
- package/dist/deno/helper/app-actions.d.ts.map +1 -0
- package/dist/deno/helper/app-actions.js +49 -0
- package/dist/deno/helper/app-actions.js.map +1 -0
- package/dist/deno/http-client.d.ts +67 -0
- package/dist/deno/http-client.d.ts.map +1 -0
- package/dist/deno/http-client.js +148 -0
- package/dist/deno/http-client.js.map +1 -0
- package/dist/deno/mod.d.ts +6 -0
- package/dist/deno/mod.d.ts.map +1 -0
- package/dist/deno/mod.js +5 -0
- package/dist/deno/mod.js.map +1 -0
- package/dist/deno/package.json +3 -0
- package/dist/deno/registration.d.ts +18 -0
- package/dist/deno/registration.d.ts.map +1 -0
- package/dist/deno/registration.js +84 -0
- package/dist/deno/registration.js.map +1 -0
- package/dist/deno/repository.d.ts +51 -0
- package/dist/deno/repository.d.ts.map +1 -0
- package/dist/deno/repository.js +63 -0
- package/dist/deno/repository.js.map +1 -0
- package/dist/deno/service/cloudflare.d.ts +43 -0
- package/dist/deno/service/cloudflare.d.ts.map +1 -0
- package/dist/deno/service/cloudflare.js +41 -0
- package/dist/deno/service/cloudflare.js.map +1 -0
- package/dist/deno/service/deno.d.ts +18 -0
- package/dist/deno/service/deno.d.ts.map +1 -0
- package/dist/deno/service/deno.js +44 -0
- package/dist/deno/service/deno.js.map +1 -0
- package/dist/deno/signer.d.ts +12 -0
- package/dist/deno/signer.d.ts.map +1 -0
- package/dist/deno/signer.js +54 -0
- package/dist/deno/signer.js.map +1 -0
- package/dist/deno/types.d.ts +28 -0
- package/dist/deno/types.d.ts.map +1 -0
- package/dist/deno/types.js +2 -0
- package/dist/deno/types.js.map +1 -0
- package/dist/esm/app.d.ts +31 -0
- package/dist/esm/app.d.ts.map +1 -0
- package/dist/esm/app.js +21 -0
- package/dist/esm/app.js.map +1 -0
- package/dist/esm/context-resolver.d.ts +30 -0
- package/dist/esm/context-resolver.d.ts.map +1 -0
- package/dist/esm/context-resolver.js +65 -0
- package/dist/esm/context-resolver.js.map +1 -0
- package/dist/esm/framework/hono.d.ts +36 -0
- package/dist/esm/framework/hono.d.ts.map +1 -0
- package/dist/esm/framework/hono.js +81 -0
- package/dist/esm/framework/hono.js.map +1 -0
- package/dist/esm/helper/app-actions.d.ts +13 -0
- package/dist/esm/helper/app-actions.d.ts.map +1 -0
- package/dist/esm/helper/app-actions.js +49 -0
- package/dist/esm/helper/app-actions.js.map +1 -0
- package/dist/esm/http-client.d.ts +67 -0
- package/dist/esm/http-client.d.ts.map +1 -0
- package/dist/esm/http-client.js +148 -0
- package/dist/esm/http-client.js.map +1 -0
- package/dist/esm/mod.d.ts +6 -0
- package/dist/esm/mod.d.ts.map +1 -0
- package/dist/esm/mod.js +5 -0
- package/dist/esm/mod.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/registration.d.ts +18 -0
- package/dist/esm/registration.d.ts.map +1 -0
- package/dist/esm/registration.js +84 -0
- package/dist/esm/registration.js.map +1 -0
- package/dist/esm/repository.d.ts +51 -0
- package/dist/esm/repository.d.ts.map +1 -0
- package/dist/esm/repository.js +63 -0
- package/dist/esm/repository.js.map +1 -0
- package/dist/esm/service/cloudflare.d.ts +43 -0
- package/dist/esm/service/cloudflare.d.ts.map +1 -0
- package/dist/esm/service/cloudflare.js +41 -0
- package/dist/esm/service/cloudflare.js.map +1 -0
- package/dist/esm/service/deno.d.ts +18 -0
- package/dist/esm/service/deno.d.ts.map +1 -0
- package/dist/esm/service/deno.js +44 -0
- package/dist/esm/service/deno.js.map +1 -0
- package/dist/esm/signer.d.ts +12 -0
- package/dist/esm/signer.d.ts.map +1 -0
- package/dist/esm/signer.js +54 -0
- package/dist/esm/signer.js.map +1 -0
- package/dist/esm/types.d.ts +28 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# App Server
|
|
2
|
+
|
|
3
|
+
This package can be used to create a Shopware App Backend. It's build independent of any JavaScript framework. It relies on Fetch-standardized Request and Response objects.
|
|
4
|
+
|
|
5
|
+
## Standalone example with Bun
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { AppServer, InMemoryShopRepository } from '@shopware-ag/app-server-sdk'
|
|
9
|
+
import { createNotificationResponse } from '@shopware-ag/app-server-sdk/helper/app-actions'
|
|
10
|
+
|
|
11
|
+
const app = new AppServer({
|
|
12
|
+
appName: 'MyApp',
|
|
13
|
+
appSecret: 'my-secret',
|
|
14
|
+
authorizeCallbackUrl: 'http://localhost:3000/authorize/callback',
|
|
15
|
+
}, new InMemoryShopRepository());
|
|
16
|
+
|
|
17
|
+
const server = Bun.serve({
|
|
18
|
+
port: 3000,
|
|
19
|
+
async fetch(request) {
|
|
20
|
+
const { pathname } = new URL(request.url);
|
|
21
|
+
if (pathname === '/authorize') {
|
|
22
|
+
return app.registration.authorize(request);
|
|
23
|
+
} else if (pathname === '/authorize/callback') {
|
|
24
|
+
return app.registration.authorizeCallback(request);
|
|
25
|
+
} else if (pathname === '/app/product') {
|
|
26
|
+
const context = await app.contextResolver.fromSource(request);
|
|
27
|
+
|
|
28
|
+
// do something with payload, and http client
|
|
29
|
+
|
|
30
|
+
const notification = createNotificationResponse('success', 'Product created');
|
|
31
|
+
|
|
32
|
+
// sign the response, with the shop secret
|
|
33
|
+
await app.signer.signResponse(notification, context.shop.getShopSecret());
|
|
34
|
+
|
|
35
|
+
return resp;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new Response('Not found', { status: 404 });
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(`Listening on localhost:${server.port}`);
|
|
43
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ContextResolver } from "./context-resolver.js";
|
|
2
|
+
import { Registration } from "./registration.js";
|
|
3
|
+
import type { ShopInterface, ShopRepositoryInterface } from "./repository.js";
|
|
4
|
+
import { WebCryptoHmacSigner } from "./signer.js";
|
|
5
|
+
/**
|
|
6
|
+
* AppServer is the main class, this is where you start your app
|
|
7
|
+
*/
|
|
8
|
+
export declare class AppServer<Shop extends ShopInterface = ShopInterface> {
|
|
9
|
+
cfg: Configuration;
|
|
10
|
+
repository: ShopRepositoryInterface<Shop>;
|
|
11
|
+
registration: Registration;
|
|
12
|
+
contextResolver: ContextResolver<Shop>;
|
|
13
|
+
signer: WebCryptoHmacSigner;
|
|
14
|
+
constructor(cfg: Configuration, repository: ShopRepositoryInterface<Shop>);
|
|
15
|
+
}
|
|
16
|
+
interface Configuration {
|
|
17
|
+
/**
|
|
18
|
+
* Your app name
|
|
19
|
+
*/
|
|
20
|
+
appName: string;
|
|
21
|
+
/**
|
|
22
|
+
* Your app secret
|
|
23
|
+
*/
|
|
24
|
+
appSecret: string;
|
|
25
|
+
/**
|
|
26
|
+
* URL to authorize callback url
|
|
27
|
+
*/
|
|
28
|
+
authorizeCallbackUrl: string;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;;GAEG;AACH,qBAAa,SAAS,CAAC,IAAI,SAAS,aAAa,GAAG,aAAa;IAMxD,GAAG,EAAE,aAAa;IAClB,UAAU,EAAE,uBAAuB,CAAC,IAAI,CAAC;IAN1C,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,EAAE,mBAAmB,CAAC;gBAG3B,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,uBAAuB,CAAC,IAAI,CAAC;CAMjD;AAED,UAAU,aAAa;IACtB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;CAC7B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppServer = void 0;
|
|
4
|
+
const context_resolver_js_1 = require("./context-resolver.js");
|
|
5
|
+
const registration_js_1 = require("./registration.js");
|
|
6
|
+
const signer_js_1 = require("./signer.js");
|
|
7
|
+
/**
|
|
8
|
+
* AppServer is the main class, this is where you start your app
|
|
9
|
+
*/
|
|
10
|
+
class AppServer {
|
|
11
|
+
cfg;
|
|
12
|
+
repository;
|
|
13
|
+
registration;
|
|
14
|
+
contextResolver;
|
|
15
|
+
signer;
|
|
16
|
+
constructor(cfg, repository) {
|
|
17
|
+
this.cfg = cfg;
|
|
18
|
+
this.repository = repository;
|
|
19
|
+
this.registration = new registration_js_1.Registration(this);
|
|
20
|
+
this.contextResolver = new context_resolver_js_1.ContextResolver(this);
|
|
21
|
+
this.signer = new signer_js_1.WebCryptoHmacSigner();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.AppServer = AppServer;
|
|
25
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":";;;AAAA,+DAAwD;AACxD,uDAAiD;AAEjD,2CAAkD;AAElD;;GAEG;AACH,MAAa,SAAS;IAMb;IACA;IAND,YAAY,CAAe;IAC3B,eAAe,CAAwB;IACvC,MAAM,CAAsB;IAEnC,YACQ,GAAkB,EAClB,UAAyC;QADzC,QAAG,GAAH,GAAG,CAAe;QAClB,eAAU,GAAV,UAAU,CAA+B;QAEhD,IAAI,CAAC,YAAY,GAAG,IAAI,8BAAY,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,eAAe,GAAG,IAAI,qCAAe,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,IAAI,+BAAmB,EAAE,CAAC;IACzC,CAAC;CACD;AAbD,8BAaC","sourcesContent":["import { ContextResolver } from \"./context-resolver.js\";\nimport { Registration } from \"./registration.js\";\nimport type { ShopInterface, ShopRepositoryInterface } from \"./repository.js\";\nimport { WebCryptoHmacSigner } from \"./signer.js\";\n\n/**\n * AppServer is the main class, this is where you start your app\n */\nexport class AppServer<Shop extends ShopInterface = ShopInterface> {\n\tpublic registration: Registration;\n\tpublic contextResolver: ContextResolver<Shop>;\n\tpublic signer: WebCryptoHmacSigner;\n\n\tconstructor(\n\t\tpublic cfg: Configuration,\n\t\tpublic repository: ShopRepositoryInterface<Shop>,\n\t) {\n\t\tthis.registration = new Registration(this);\n\t\tthis.contextResolver = new ContextResolver(this);\n\t\tthis.signer = new WebCryptoHmacSigner();\n\t}\n}\n\ninterface Configuration {\n\t/**\n\t * Your app name\n\t */\n\tappName: string;\n\n\t/**\n\t * Your app secret\n\t */\n\tappSecret: string;\n\n\t/**\n\t * URL to authorize callback url\n\t */\n\tauthorizeCallbackUrl: string;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AppServer } from "./app.js";
|
|
2
|
+
import { HttpClient } from "./http-client.js";
|
|
3
|
+
import type { ShopInterface } from "./repository.js";
|
|
4
|
+
/**
|
|
5
|
+
* ContextResolver is a helper class to create a Context object from a request.
|
|
6
|
+
* The context contains the shop, the payload and an instance of the HttpClient
|
|
7
|
+
*/
|
|
8
|
+
export declare class ContextResolver<Shop extends ShopInterface = ShopInterface> {
|
|
9
|
+
private app;
|
|
10
|
+
constructor(app: AppServer);
|
|
11
|
+
/**
|
|
12
|
+
* Create a context from a request body
|
|
13
|
+
*/
|
|
14
|
+
fromAPI<Payload = unknown>(req: Request): Promise<Context<Shop, Payload>>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a context from a request query parameters
|
|
17
|
+
* This is usually a module request from the shopware admin
|
|
18
|
+
*/
|
|
19
|
+
fromBrowser<Payload = unknown>(req: Request): Promise<Context<Shop, Payload>>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Context is the parsed data from the request
|
|
23
|
+
*/
|
|
24
|
+
export declare class Context<Shop extends ShopInterface = ShopInterface, Payload = unknown> {
|
|
25
|
+
shop: Shop;
|
|
26
|
+
payload: Payload;
|
|
27
|
+
httpClient: HttpClient;
|
|
28
|
+
constructor(shop: Shop, payload: Payload, httpClient: HttpClient);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=context-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-resolver.d.ts","sourceRoot":"","sources":["../../src/context-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;GAGG;AACH,qBAAa,eAAe,CAAC,IAAI,SAAS,aAAa,GAAG,aAAa;IAC1D,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,SAAS;IAElC;;OAEG;IACU,OAAO,CAAC,OAAO,GAAG,OAAO,EACrC,GAAG,EAAE,OAAO,GACV,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAmClC;;;OAGG;IACU,WAAW,CAAC,OAAO,GAAG,OAAO,EACzC,GAAG,EAAE,OAAO,GACV,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;CA6BlC;AAED;;GAEG;AACH,qBAAa,OAAO,CACnB,IAAI,SAAS,aAAa,GAAG,aAAa,EAC1C,OAAO,GAAG,OAAO;IAGT,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,OAAO;IAChB,UAAU,EAAE,UAAU;gBAFtB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,UAAU;CAE9B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Context = exports.ContextResolver = void 0;
|
|
4
|
+
const http_client_js_1 = require("./http-client.js");
|
|
5
|
+
/**
|
|
6
|
+
* ContextResolver is a helper class to create a Context object from a request.
|
|
7
|
+
* The context contains the shop, the payload and an instance of the HttpClient
|
|
8
|
+
*/
|
|
9
|
+
class ContextResolver {
|
|
10
|
+
app;
|
|
11
|
+
constructor(app) {
|
|
12
|
+
this.app = app;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create a context from a request body
|
|
16
|
+
*/
|
|
17
|
+
async fromAPI(req) {
|
|
18
|
+
const webHookContent = await req.text();
|
|
19
|
+
const webHookBody = JSON.parse(webHookContent);
|
|
20
|
+
const shop = await this.app.repository.getShopById(webHookBody.source.shopId);
|
|
21
|
+
if (shop === null) {
|
|
22
|
+
throw new Error(`Cannot find shop by id ${webHookBody.source.shopId}`);
|
|
23
|
+
}
|
|
24
|
+
const signature = req.headers.get("shopware-shop-signature");
|
|
25
|
+
if (signature === null) {
|
|
26
|
+
throw new Error("Missing shopware-shop-signature header");
|
|
27
|
+
}
|
|
28
|
+
if (!(await this.app.signer.verify(signature, webHookContent, shop.getShopSecret()))) {
|
|
29
|
+
throw new Error("Invalid signature");
|
|
30
|
+
}
|
|
31
|
+
return new Context(shop, webHookBody, new http_client_js_1.HttpClient(shop));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a context from a request query parameters
|
|
35
|
+
* This is usually a module request from the shopware admin
|
|
36
|
+
*/
|
|
37
|
+
async fromBrowser(req) {
|
|
38
|
+
const url = new URL(req.url);
|
|
39
|
+
const shopId = url.searchParams.get("shop-id");
|
|
40
|
+
if (shopId === null) {
|
|
41
|
+
throw new Error("Missing shop-id query parameter");
|
|
42
|
+
}
|
|
43
|
+
const shop = await this.app.repository.getShopById(shopId);
|
|
44
|
+
if (shop === null) {
|
|
45
|
+
throw new Error(`Cannot find shop by id ${shopId}`);
|
|
46
|
+
}
|
|
47
|
+
await this.app.signer.verifyGetRequest(req, shop.getShopSecret());
|
|
48
|
+
const paramsObject = {};
|
|
49
|
+
url.searchParams.forEach((value, key) => {
|
|
50
|
+
paramsObject[key] = value;
|
|
51
|
+
});
|
|
52
|
+
return new Context(shop, paramsObject, new http_client_js_1.HttpClient(shop));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.ContextResolver = ContextResolver;
|
|
56
|
+
/**
|
|
57
|
+
* Context is the parsed data from the request
|
|
58
|
+
*/
|
|
59
|
+
class Context {
|
|
60
|
+
shop;
|
|
61
|
+
payload;
|
|
62
|
+
httpClient;
|
|
63
|
+
constructor(shop, payload, httpClient) {
|
|
64
|
+
this.shop = shop;
|
|
65
|
+
this.payload = payload;
|
|
66
|
+
this.httpClient = httpClient;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.Context = Context;
|
|
70
|
+
//# sourceMappingURL=context-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-resolver.js","sourceRoot":"","sources":["../../src/context-resolver.ts"],"names":[],"mappings":";;;AACA,qDAA8C;AAG9C;;;GAGG;AACH,MAAa,eAAe;IACP;IAApB,YAAoB,GAAc;QAAd,QAAG,GAAH,GAAG,CAAW;IAAG,CAAC;IAEtC;;OAEG;IACI,KAAK,CAAC,OAAO,CACnB,GAAY;QAEZ,MAAM,cAAc,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAE/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CACjD,WAAW,CAAC,MAAM,CAAC,MAAM,CACzB,CAAC;QAEF,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAE7D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC3D,CAAC;QAED,IACC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAC7B,SAAS,EACT,cAAc,EACd,IAAI,CAAC,aAAa,EAAE,CACpB,CAAC,EACD,CAAC;YACF,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACtC,CAAC;QAED,OAAO,IAAI,OAAO,CACjB,IAAY,EACZ,WAAW,EACX,IAAI,2BAAU,CAAC,IAAI,CAAC,CACpB,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,WAAW,CACvB,GAAY;QAEZ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAE/C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE3D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QAElE,MAAM,YAAY,GAA2B,EAAE,CAAC;QAEhD,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACvC,YAAY,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,OAAO,CACjB,IAAY,EACZ,YAAuB,EACvB,IAAI,2BAAU,CAAC,IAAI,CAAC,CACpB,CAAC;IACH,CAAC;CACD;AA9ED,0CA8EC;AAED;;GAEG;AACH,MAAa,OAAO;IAKX;IACA;IACA;IAHR,YACQ,IAAU,EACV,OAAgB,EAChB,UAAsB;QAFtB,SAAI,GAAJ,IAAI,CAAM;QACV,YAAO,GAAP,OAAO,CAAS;QAChB,eAAU,GAAV,UAAU,CAAY;IAC3B,CAAC;CACJ;AATD,0BASC","sourcesContent":["import type { AppServer } from \"./app.js\";\nimport { HttpClient } from \"./http-client.js\";\nimport type { ShopInterface } from \"./repository.js\";\n\n/**\n * ContextResolver is a helper class to create a Context object from a request.\n * The context contains the shop, the payload and an instance of the HttpClient\n */\nexport class ContextResolver<Shop extends ShopInterface = ShopInterface> {\n\tconstructor(private app: AppServer) {}\n\n\t/**\n\t * Create a context from a request body\n\t */\n\tpublic async fromAPI<Payload = unknown>(\n\t\treq: Request,\n\t): Promise<Context<Shop, Payload>> {\n\t\tconst webHookContent = await req.text();\n\t\tconst webHookBody = JSON.parse(webHookContent);\n\n\t\tconst shop = await this.app.repository.getShopById(\n\t\t\twebHookBody.source.shopId,\n\t\t);\n\n\t\tif (shop === null) {\n\t\t\tthrow new Error(`Cannot find shop by id ${webHookBody.source.shopId}`);\n\t\t}\n\n\t\tconst signature = req.headers.get(\"shopware-shop-signature\");\n\n\t\tif (signature === null) {\n\t\t\tthrow new Error(\"Missing shopware-shop-signature header\");\n\t\t}\n\n\t\tif (\n\t\t\t!(await this.app.signer.verify(\n\t\t\t\tsignature,\n\t\t\t\twebHookContent,\n\t\t\t\tshop.getShopSecret(),\n\t\t\t))\n\t\t) {\n\t\t\tthrow new Error(\"Invalid signature\");\n\t\t}\n\n\t\treturn new Context<Shop, Payload>(\n\t\t\tshop as Shop,\n\t\t\twebHookBody,\n\t\t\tnew HttpClient(shop),\n\t\t);\n\t}\n\n\t/**\n\t * Create a context from a request query parameters\n\t * This is usually a module request from the shopware admin\n\t */\n\tpublic async fromBrowser<Payload = unknown>(\n\t\treq: Request,\n\t): Promise<Context<Shop, Payload>> {\n\t\tconst url = new URL(req.url);\n\n\t\tconst shopId = url.searchParams.get(\"shop-id\");\n\n\t\tif (shopId === null) {\n\t\t\tthrow new Error(\"Missing shop-id query parameter\");\n\t\t}\n\n\t\tconst shop = await this.app.repository.getShopById(shopId);\n\n\t\tif (shop === null) {\n\t\t\tthrow new Error(`Cannot find shop by id ${shopId}`);\n\t\t}\n\n\t\tawait this.app.signer.verifyGetRequest(req, shop.getShopSecret());\n\n\t\tconst paramsObject: Record<string, string> = {};\n\n\t\turl.searchParams.forEach((value, key) => {\n\t\t\tparamsObject[key] = value;\n\t\t});\n\n\t\treturn new Context<Shop, Payload>(\n\t\t\tshop as Shop,\n\t\t\tparamsObject as Payload,\n\t\t\tnew HttpClient(shop),\n\t\t);\n\t}\n}\n\n/**\n * Context is the parsed data from the request\n */\nexport class Context<\n\tShop extends ShopInterface = ShopInterface,\n\tPayload = unknown,\n> {\n\tconstructor(\n\t\tpublic shop: Shop,\n\t\tpublic payload: Payload,\n\t\tpublic httpClient: HttpClient,\n\t) {}\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AppServer } from "../app.js";
|
|
2
|
+
import type { Context } from "../context-resolver.js";
|
|
3
|
+
import type { ShopInterface, ShopRepositoryInterface } from "../repository.js";
|
|
4
|
+
interface MiddlewareConfig {
|
|
5
|
+
appName: string | ((c: HonoContext<DataTypes>) => string);
|
|
6
|
+
appSecret: string | ((c: HonoContext<DataTypes>) => string);
|
|
7
|
+
appUrl?: string | null;
|
|
8
|
+
registrationUrl?: string | null;
|
|
9
|
+
registerConfirmationUrl?: string | null;
|
|
10
|
+
appPath?: string | null;
|
|
11
|
+
shopRepository: ShopRepositoryInterface | ((c: HonoContext<DataTypes>) => ShopRepositoryInterface);
|
|
12
|
+
}
|
|
13
|
+
interface DataTypes {
|
|
14
|
+
app: AppServer;
|
|
15
|
+
context: Context<ShopInterface, unknown>;
|
|
16
|
+
shop: ShopInterface;
|
|
17
|
+
}
|
|
18
|
+
interface HonoContext<DataTypes> {
|
|
19
|
+
env: any;
|
|
20
|
+
req: {
|
|
21
|
+
path: string;
|
|
22
|
+
method: string;
|
|
23
|
+
url: string;
|
|
24
|
+
raw: Request;
|
|
25
|
+
};
|
|
26
|
+
header: (key: string, value: string) => void;
|
|
27
|
+
res: Response;
|
|
28
|
+
get<K extends keyof DataTypes>(key: K): DataTypes[K];
|
|
29
|
+
set<K extends keyof DataTypes>(key: K, value: DataTypes[K]): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configure the Hono server to handle the app registration and context resolution
|
|
33
|
+
*/
|
|
34
|
+
export declare function configureAppServer(honoExternal: unknown, cfg: MiddlewareConfig): void;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../../src/framework/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,UAAU,gBAAgB;IACzB,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,CAAC;IAC1D,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,CAAC;IAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EACX,uBAAuB,GACvB,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,uBAAuB,CAAC,CAAC;CAC5D;AAED,UAAU,SAAS;IAClB,GAAG,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,aAAa,CAAC;CACpB;AAED,UAAU,WAAW,CAAC,SAAS;IAC9B,GAAG,EAAE,GAAG,CAAC;IACT,GAAG,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,OAAO,CAAC;KACb,CAAC;IACF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,GAAG,EAAE,QAAQ,CAAC;IACd,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;CAClE;AAmBD;;GAEG;AACH,wBAAgB,kBAAkB,CACjC,YAAY,EAAE,OAAO,EACrB,GAAG,EAAE,gBAAgB,QA0FrB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureAppServer = configureAppServer;
|
|
4
|
+
const app_js_1 = require("../app.js");
|
|
5
|
+
let app = null;
|
|
6
|
+
/**
|
|
7
|
+
* Configure the Hono server to handle the app registration and context resolution
|
|
8
|
+
*/
|
|
9
|
+
function configureAppServer(honoExternal, cfg) {
|
|
10
|
+
const hono = honoExternal;
|
|
11
|
+
cfg.registrationUrl = cfg.registrationUrl || "/app/register";
|
|
12
|
+
cfg.registerConfirmationUrl =
|
|
13
|
+
cfg.registerConfirmationUrl || "/app/register/confirm";
|
|
14
|
+
cfg.appPath = cfg.appPath || "/app/*";
|
|
15
|
+
hono.use("*", async (ctx, next) => {
|
|
16
|
+
if (app === null) {
|
|
17
|
+
const appUrl = cfg.appUrl || buildBaseUrl(ctx.req.url);
|
|
18
|
+
if (typeof cfg.shopRepository === "function") {
|
|
19
|
+
cfg.shopRepository = cfg.shopRepository(ctx);
|
|
20
|
+
}
|
|
21
|
+
if (typeof cfg.appName === "function") {
|
|
22
|
+
cfg.appName = cfg.appName(ctx);
|
|
23
|
+
}
|
|
24
|
+
if (typeof cfg.appSecret === "function") {
|
|
25
|
+
cfg.appSecret = cfg.appSecret(ctx);
|
|
26
|
+
}
|
|
27
|
+
app = new app_js_1.AppServer({
|
|
28
|
+
appName: cfg.appName,
|
|
29
|
+
appSecret: cfg.appSecret,
|
|
30
|
+
authorizeCallbackUrl: appUrl + cfg.registerConfirmationUrl,
|
|
31
|
+
}, cfg.shopRepository);
|
|
32
|
+
}
|
|
33
|
+
ctx.set("app", app);
|
|
34
|
+
await next();
|
|
35
|
+
});
|
|
36
|
+
hono.use(cfg.appPath, async (ctx, next) => {
|
|
37
|
+
const app = ctx.get("app");
|
|
38
|
+
// Don't validate signature for registration
|
|
39
|
+
if (ctx.req.path === cfg.registrationUrl ||
|
|
40
|
+
ctx.req.path === cfg.registerConfirmationUrl) {
|
|
41
|
+
await next();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let context;
|
|
45
|
+
try {
|
|
46
|
+
context =
|
|
47
|
+
ctx.req.method === "GET"
|
|
48
|
+
? await app.contextResolver.fromBrowser(ctx.req.raw)
|
|
49
|
+
: await app.contextResolver.fromAPI(ctx.req.raw);
|
|
50
|
+
}
|
|
51
|
+
catch (_e) {
|
|
52
|
+
return jsonResponse({ message: "Invalid request" }, 400);
|
|
53
|
+
}
|
|
54
|
+
ctx.set("shop", context.shop);
|
|
55
|
+
ctx.set("context", context);
|
|
56
|
+
await next();
|
|
57
|
+
const cloned = ctx.res.clone();
|
|
58
|
+
await ctx
|
|
59
|
+
.get("app")
|
|
60
|
+
.signer.signResponse(cloned, ctx.get("shop").getShopSecret());
|
|
61
|
+
ctx.header("shopware-app-signature", cloned.headers.get("shopware-app-signature"));
|
|
62
|
+
});
|
|
63
|
+
hono.get(cfg.registrationUrl, async (ctx) => {
|
|
64
|
+
const app = ctx.get("app");
|
|
65
|
+
return await app.registration.authorize(ctx.req.raw);
|
|
66
|
+
});
|
|
67
|
+
hono.post(cfg.registerConfirmationUrl, async (ctx) => {
|
|
68
|
+
const app = ctx.get("app");
|
|
69
|
+
return await app.registration.authorizeCallback(ctx.req.raw);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function jsonResponse(body, status = 200) {
|
|
73
|
+
return new Response(JSON.stringify(body), {
|
|
74
|
+
status,
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function buildBaseUrl(url) {
|
|
81
|
+
const u = new URL(url);
|
|
82
|
+
return `${u.protocol}//${u.host}`;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../../src/framework/hono.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppServer } from \"../app.js\";\nimport type { Context } from \"../context-resolver.js\";\nimport type { ShopInterface, ShopRepositoryInterface } from \"../repository.js\";\n\ninterface MiddlewareConfig {\n\tappName: string | ((c: HonoContext<DataTypes>) => string);\n\tappSecret: string | ((c: HonoContext<DataTypes>) => string);\n\tappUrl?: string | null;\n\tregistrationUrl?: string | null;\n\tregisterConfirmationUrl?: string | null;\n\tappPath?: string | null;\n\tshopRepository:\n\t\t| ShopRepositoryInterface\n\t\t| ((c: HonoContext<DataTypes>) => ShopRepositoryInterface);\n}\n\ninterface DataTypes {\n\tapp: AppServer;\n\tcontext: Context<ShopInterface, unknown>;\n\tshop: ShopInterface;\n}\n\ninterface HonoContext<DataTypes> {\n\tenv: any;\n\treq: {\n\t\tpath: string;\n\t\tmethod: string;\n\t\turl: string;\n\t\traw: Request;\n\t};\n\theader: (key: string, value: string) => void;\n\tres: Response;\n\tget<K extends keyof DataTypes>(key: K): DataTypes[K];\n\tset<K extends keyof DataTypes>(key: K, value: DataTypes[K]): void;\n}\n\ninterface Hono {\n\tuse: (\n\t\tpath: string,\n\t\thandler: (ctx: HonoContext<DataTypes>, next: () => Promise<void>) => void,\n\t) => void;\n\tget: (\n\t\tpath: string,\n\t\thandler: (ctx: HonoContext<DataTypes>, next: () => void) => void,\n\t) => void;\n\tpost: (\n\t\tpath: string,\n\t\thandler: (ctx: HonoContext<DataTypes>, next: () => void) => void,\n\t) => void;\n}\n\nlet app: AppServer | null = null;\n\n/**\n * Configure the Hono server to handle the app registration and context resolution\n */\nexport function configureAppServer(\n\thonoExternal: unknown,\n\tcfg: MiddlewareConfig,\n) {\n\tconst hono = honoExternal as Hono;\n\n\tcfg.registrationUrl = cfg.registrationUrl || \"/app/register\";\n\tcfg.registerConfirmationUrl =\n\t\tcfg.registerConfirmationUrl || \"/app/register/confirm\";\n\tcfg.appPath = cfg.appPath || \"/app/*\";\n\n\thono.use(\"*\", async (ctx, next) => {\n\t\tif (app === null) {\n\t\t\tconst appUrl = cfg.appUrl || buildBaseUrl(ctx.req.url);\n\n\t\t\tif (typeof cfg.shopRepository === \"function\") {\n\t\t\t\tcfg.shopRepository = cfg.shopRepository(ctx);\n\t\t\t}\n\n\t\t\tif (typeof cfg.appName === \"function\") {\n\t\t\t\tcfg.appName = cfg.appName(ctx);\n\t\t\t}\n\n\t\t\tif (typeof cfg.appSecret === \"function\") {\n\t\t\t\tcfg.appSecret = cfg.appSecret(ctx);\n\t\t\t}\n\n\t\t\tapp = new AppServer(\n\t\t\t\t{\n\t\t\t\t\tappName: cfg.appName,\n\t\t\t\t\tappSecret: cfg.appSecret,\n\t\t\t\t\tauthorizeCallbackUrl: appUrl + cfg.registerConfirmationUrl,\n\t\t\t\t},\n\t\t\t\tcfg.shopRepository,\n\t\t\t);\n\t\t}\n\n\t\tctx.set(\"app\", app);\n\n\t\tawait next();\n\t});\n\n\thono.use(cfg.appPath, async (ctx, next) => {\n\t\tconst app = ctx.get(\"app\") as AppServer;\n\n\t\t// Don't validate signature for registration\n\t\tif (\n\t\t\tctx.req.path === cfg.registrationUrl ||\n\t\t\tctx.req.path === cfg.registerConfirmationUrl\n\t\t) {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\n\t\tlet context: Context<ShopInterface, unknown>;\n\t\ttry {\n\t\t\tcontext =\n\t\t\t\tctx.req.method === \"GET\"\n\t\t\t\t\t? await app.contextResolver.fromBrowser(ctx.req.raw)\n\t\t\t\t\t: await app.contextResolver.fromAPI(ctx.req.raw);\n\t\t} catch (_e) {\n\t\t\treturn jsonResponse({ message: \"Invalid request\" }, 400);\n\t\t}\n\n\t\tctx.set(\"shop\", context.shop);\n\t\tctx.set(\"context\", context);\n\n\t\tawait next();\n\n\t\tconst cloned = ctx.res.clone();\n\n\t\tawait ctx\n\t\t\t.get(\"app\")\n\t\t\t.signer.signResponse(cloned, ctx.get(\"shop\").getShopSecret());\n\n\t\tctx.header(\n\t\t\t\"shopware-app-signature\",\n\t\t\tcloned.headers.get(\"shopware-app-signature\") as string,\n\t\t);\n\t});\n\n\thono.get(cfg.registrationUrl, async (ctx) => {\n\t\tconst app = ctx.get(\"app\");\n\n\t\treturn await app.registration.authorize(ctx.req.raw);\n\t});\n\n\thono.post(cfg.registerConfirmationUrl, async (ctx) => {\n\t\tconst app = ctx.get(\"app\");\n\n\t\treturn await app.registration.authorizeCallback(ctx.req.raw);\n\t});\n}\n\nfunction jsonResponse(body: object, status = 200): Response {\n\treturn new Response(JSON.stringify(body), {\n\t\tstatus,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t});\n}\n\nfunction buildBaseUrl(url: string): string {\n\tconst u = new URL(url);\n\n\treturn `${u.protocol}//${u.host}`;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opens in the Administration a new tab to the given URL and adds the shop-id as query parameter with the signature
|
|
3
|
+
*/
|
|
4
|
+
export declare function createNewTabResponse(redirectUrl: string): Response;
|
|
5
|
+
/**
|
|
6
|
+
* Shows in the Administration a new notification with the given status and message
|
|
7
|
+
*/
|
|
8
|
+
export declare function createNotificationResponse(status: "success" | "error" | "info" | "warning", message: string): Response;
|
|
9
|
+
/**
|
|
10
|
+
* Opens in the Administration a new modal with the given iframe URL
|
|
11
|
+
*/
|
|
12
|
+
export declare function createModalResponse(iframeUrl: string, size?: "small" | "medium" | "large" | "fullscreen", expand?: boolean): Response;
|
|
13
|
+
//# sourceMappingURL=app-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-actions.d.ts","sourceRoot":"","sources":["../../../src/helper/app-actions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAclE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACzC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,EAChD,OAAO,EAAE,MAAM,GACb,QAAQ,CAeV;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAuB,EAC5D,MAAM,UAAQ,GACZ,QAAQ,CAgBV"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNewTabResponse = createNewTabResponse;
|
|
4
|
+
exports.createNotificationResponse = createNotificationResponse;
|
|
5
|
+
exports.createModalResponse = createModalResponse;
|
|
6
|
+
/**
|
|
7
|
+
* Opens in the Administration a new tab to the given URL and adds the shop-id as query parameter with the signature
|
|
8
|
+
*/
|
|
9
|
+
function createNewTabResponse(redirectUrl) {
|
|
10
|
+
return new Response(JSON.stringify({
|
|
11
|
+
actionType: "openNewTab",
|
|
12
|
+
payload: {
|
|
13
|
+
redirectUrl,
|
|
14
|
+
},
|
|
15
|
+
}), {
|
|
16
|
+
headers: {
|
|
17
|
+
"content-type": "application/json",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Shows in the Administration a new notification with the given status and message
|
|
23
|
+
*/
|
|
24
|
+
function createNotificationResponse(status, message) {
|
|
25
|
+
return new Response(JSON.stringify({
|
|
26
|
+
actionType: "notification",
|
|
27
|
+
payload: {
|
|
28
|
+
status,
|
|
29
|
+
message,
|
|
30
|
+
},
|
|
31
|
+
}), {
|
|
32
|
+
headers: {
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Opens in the Administration a new modal with the given iframe URL
|
|
39
|
+
*/
|
|
40
|
+
function createModalResponse(iframeUrl, size = "medium", expand = false) {
|
|
41
|
+
return new Response(JSON.stringify({
|
|
42
|
+
actionType: "openModal",
|
|
43
|
+
payload: {
|
|
44
|
+
iframeUrl,
|
|
45
|
+
size,
|
|
46
|
+
expand,
|
|
47
|
+
},
|
|
48
|
+
}), {
|
|
49
|
+
headers: {
|
|
50
|
+
"content-type": "application/json",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=app-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-actions.js","sourceRoot":"","sources":["../../../src/helper/app-actions.ts"],"names":[],"mappings":";;AAGA,oDAcC;AAKD,gEAkBC;AAKD,kDAoBC;AAjED;;GAEG;AACH,SAAgB,oBAAoB,CAAC,WAAmB;IACvD,OAAO,IAAI,QAAQ,CAClB,IAAI,CAAC,SAAS,CAAC;QACd,UAAU,EAAE,YAAY;QACxB,OAAO,EAAE;YACR,WAAW;SACX;KACD,CAAC,EACF;QACC,OAAO,EAAE;YACR,cAAc,EAAE,kBAAkB;SAClC;KACD,CACD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,0BAA0B,CACzC,MAAgD,EAChD,OAAe;IAEf,OAAO,IAAI,QAAQ,CAClB,IAAI,CAAC,SAAS,CAAC;QACd,UAAU,EAAE,cAAc;QAC1B,OAAO,EAAE;YACR,MAAM;YACN,OAAO;SACP;KACD,CAAC,EACF;QACC,OAAO,EAAE;YACR,cAAc,EAAE,kBAAkB;SAClC;KACD,CACD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,mBAAmB,CAClC,SAAiB,EACjB,OAAoD,QAAQ,EAC5D,MAAM,GAAG,KAAK;IAEd,OAAO,IAAI,QAAQ,CAClB,IAAI,CAAC,SAAS,CAAC;QACd,UAAU,EAAE,WAAW;QACvB,OAAO,EAAE;YACR,SAAS;YACT,IAAI;YACJ,MAAM;SACN;KACD,CAAC,EACF;QACC,OAAO,EAAE;YACR,cAAc,EAAE,kBAAkB;SAClC;KACD,CACD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Opens in the Administration a new tab to the given URL and adds the shop-id as query parameter with the signature\n */\nexport function createNewTabResponse(redirectUrl: string): Response {\n\treturn new Response(\n\t\tJSON.stringify({\n\t\t\tactionType: \"openNewTab\",\n\t\t\tpayload: {\n\t\t\t\tredirectUrl,\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * Shows in the Administration a new notification with the given status and message\n */\nexport function createNotificationResponse(\n\tstatus: \"success\" | \"error\" | \"info\" | \"warning\",\n\tmessage: string,\n): Response {\n\treturn new Response(\n\t\tJSON.stringify({\n\t\t\tactionType: \"notification\",\n\t\t\tpayload: {\n\t\t\t\tstatus,\n\t\t\t\tmessage,\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * Opens in the Administration a new modal with the given iframe URL\n */\nexport function createModalResponse(\n\tiframeUrl: string,\n\tsize: \"small\" | \"medium\" | \"large\" | \"fullscreen\" = \"medium\",\n\texpand = false,\n): Response {\n\treturn new Response(\n\t\tJSON.stringify({\n\t\t\tactionType: \"openModal\",\n\t\t\tpayload: {\n\t\t\t\tiframeUrl,\n\t\t\t\tsize,\n\t\t\t\texpand,\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t},\n\t\t},\n\t);\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ShopInterface } from "./repository.js";
|
|
2
|
+
/**
|
|
3
|
+
* HttpClient is a simple wrapper around the fetch API, pre-configured with the shop's URL and access token
|
|
4
|
+
*/
|
|
5
|
+
export declare class HttpClient {
|
|
6
|
+
private shop;
|
|
7
|
+
private storage;
|
|
8
|
+
constructor(shop: ShopInterface);
|
|
9
|
+
/**
|
|
10
|
+
* Permform a GET request
|
|
11
|
+
*/
|
|
12
|
+
get<ResponseType>(url: string, headers?: Record<string, string>): Promise<HttpClientResponse<ResponseType>>;
|
|
13
|
+
/**
|
|
14
|
+
* Permform a POST request
|
|
15
|
+
*/
|
|
16
|
+
post<ResponseType>(url: string, json?: object, headers?: Record<string, string>): Promise<HttpClientResponse<ResponseType>>;
|
|
17
|
+
/**
|
|
18
|
+
* Permform a PUT request
|
|
19
|
+
*/
|
|
20
|
+
put<ResponseType>(url: string, json?: object, headers?: Record<string, string>): Promise<HttpClientResponse<ResponseType>>;
|
|
21
|
+
/**
|
|
22
|
+
* Permform a PATCH request
|
|
23
|
+
*/
|
|
24
|
+
patch<ResponseType>(url: string, json?: object, headers?: Record<string, string>): Promise<HttpClientResponse<ResponseType>>;
|
|
25
|
+
/**
|
|
26
|
+
* Permform a DELETE request
|
|
27
|
+
*/
|
|
28
|
+
delete<ResponseType>(url: string, json?: object, headers?: Record<string, string>): Promise<HttpClientResponse<ResponseType>>;
|
|
29
|
+
private request;
|
|
30
|
+
/**
|
|
31
|
+
* Obtain a valid bearer token
|
|
32
|
+
*/
|
|
33
|
+
getToken(): Promise<string>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* HttpClientResponse is the response object of the HttpClient
|
|
37
|
+
*/
|
|
38
|
+
export declare class HttpClientResponse<ResponseType> {
|
|
39
|
+
statusCode: number;
|
|
40
|
+
body: ResponseType;
|
|
41
|
+
headers: Headers;
|
|
42
|
+
constructor(statusCode: number, body: ResponseType, headers: Headers);
|
|
43
|
+
}
|
|
44
|
+
type ShopwareErrorResponse = {
|
|
45
|
+
errors: {
|
|
46
|
+
code: string;
|
|
47
|
+
status: string;
|
|
48
|
+
title: string;
|
|
49
|
+
detail: string;
|
|
50
|
+
}[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* ApiClientAuthenticationFailed is thrown when the authentication to the shop's API fails
|
|
54
|
+
*/
|
|
55
|
+
export declare class ApiClientAuthenticationFailed extends Error {
|
|
56
|
+
response: HttpClientResponse<string>;
|
|
57
|
+
constructor(shopId: string, response: HttpClientResponse<string>);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ApiClientRequestFailed is thrown when the request to the shop's API fails
|
|
61
|
+
*/
|
|
62
|
+
export declare class ApiClientRequestFailed extends Error {
|
|
63
|
+
response: HttpClientResponse<ShopwareErrorResponse>;
|
|
64
|
+
constructor(shopId: string, response: HttpClientResponse<ShopwareErrorResponse>);
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;GAEG;AACH,qBAAa,UAAU;IAGV,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,OAAO,CAAmD;gBAE9C,IAAI,EAAE,aAAa;IAOvC;;OAEG;IACG,GAAG,CAAC,YAAY,EACrB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAI5C;;OAEG;IACG,IAAI,CAAC,YAAY,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAW,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAO5C;;OAEG;IACG,GAAG,CAAC,YAAY,EACrB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAW,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAO5C;;OAEG;IACG,KAAK,CAAC,YAAY,EACvB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAW,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAO5C;;OAEG;IACG,MAAM,CAAC,YAAY,EACxB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAW,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;YAO9B,OAAO;IAyCrB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;CAuDjC;AAED;;GAEG;AACH,qBAAa,kBAAkB,CAAC,YAAY;IAEnC,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;gBAFhB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,OAAO;CAExB;AAED,KAAK,qBAAqB,GAAG;IAC5B,MAAM,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;IAG/C,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC;gBAD3C,MAAM,EAAE,MAAM,EACP,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC;CAM5C;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAGxC,QAAQ,EAAE,kBAAkB,CAAC,qBAAqB,CAAC;gBAD1D,MAAM,EAAE,MAAM,EACP,QAAQ,EAAE,kBAAkB,CAAC,qBAAqB,CAAC;CAM3D"}
|