@faremeter/middleware 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/common.d.ts +52 -0
- package/dist/src/common.d.ts.map +1 -0
- package/dist/src/common.js +96 -0
- package/dist/src/express.d.ts +3 -6
- package/dist/src/express.d.ts.map +1 -1
- package/dist/src/express.js +8 -64
- package/dist/src/hono.d.ts +2 -5
- package/dist/src/hono.d.ts.map +1 -1
- package/dist/src/hono.js +10 -61
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -2
- package/dist/src/utils.d.ts +0 -17
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js +0 -28
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type x402PaymentRequirements, type x402PaymentPayload } from "@faremeter/types/x402";
|
|
2
|
+
export declare function findMatchingPaymentRequirements(accepts: x402PaymentRequirements[], payload: x402PaymentPayload): {
|
|
3
|
+
scheme: string;
|
|
4
|
+
network: string;
|
|
5
|
+
maxAmountRequired: string;
|
|
6
|
+
resource: string;
|
|
7
|
+
description: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
payTo: string;
|
|
10
|
+
maxTimeoutSeconds: number;
|
|
11
|
+
asset: string;
|
|
12
|
+
outputSchema?: object;
|
|
13
|
+
extra?: object;
|
|
14
|
+
} | undefined;
|
|
15
|
+
export declare function gateGetPaymentRequiredResponse(res: Response): void;
|
|
16
|
+
export type RelaxedRequirements = Partial<x402PaymentRequirements>;
|
|
17
|
+
type getPaymentRequiredResponseArgs = {
|
|
18
|
+
facilitatorURL: string;
|
|
19
|
+
accepts: RelaxedRequirements[];
|
|
20
|
+
resource: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function getPaymentRequiredResponse(args: getPaymentRequiredResponseArgs): Promise<{
|
|
23
|
+
x402Version: number;
|
|
24
|
+
accepts: {
|
|
25
|
+
scheme: string;
|
|
26
|
+
network: string;
|
|
27
|
+
maxAmountRequired: string;
|
|
28
|
+
resource: string;
|
|
29
|
+
description: string;
|
|
30
|
+
mimeType: string;
|
|
31
|
+
payTo: string;
|
|
32
|
+
maxTimeoutSeconds: number;
|
|
33
|
+
asset: string;
|
|
34
|
+
outputSchema?: object;
|
|
35
|
+
extra?: object;
|
|
36
|
+
}[];
|
|
37
|
+
error?: string;
|
|
38
|
+
}>;
|
|
39
|
+
type PossibleStatusCodes = 402;
|
|
40
|
+
type PossibleJSONResponse = object;
|
|
41
|
+
export type CommonMiddlewareArgs = {
|
|
42
|
+
facilitatorURL: string;
|
|
43
|
+
accepts: RelaxedRequirements[];
|
|
44
|
+
};
|
|
45
|
+
export type HandleMiddlewareRequestArgs<MiddlewareResponse = unknown> = CommonMiddlewareArgs & {
|
|
46
|
+
resource: string;
|
|
47
|
+
getHeader: (key: string) => string | undefined;
|
|
48
|
+
sendJSONResponse: (status: PossibleStatusCodes, obj: PossibleJSONResponse) => MiddlewareResponse;
|
|
49
|
+
};
|
|
50
|
+
export declare function handleMiddlewareRequest<MiddlewareResponse>(args: HandleMiddlewareRequestArgs<MiddlewareResponse>): Promise<MiddlewareResponse | undefined>;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=common.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/common.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAKxB,MAAM,uBAAuB,CAAC;AAI/B,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;cA6B5B;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,QAAQ,QAS3D;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,KAAK,8BAA8B,GAAG;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,8BAA8B;;;;;;;;;;;;oBApCkC,CAAC;aAIhE,CAAC;;;GA8DT;AAED,KAAK,mBAAmB,GAAG,GAAG,CAAC;AAC/B,KAAK,oBAAoB,GAAG,MAAM,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,kBAAkB,GAAG,OAAO,IAClE,oBAAoB,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/C,gBAAgB,EAAE,CAChB,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,oBAAoB,KACtB,kBAAkB,CAAC;CACzB,CAAC;AAEJ,wBAAsB,uBAAuB,CAAC,kBAAkB,EAC9D,IAAI,EAAE,2BAA2B,CAAC,kBAAkB,CAAC,2CA6DtD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isValidationError } from "@faremeter/types";
|
|
2
|
+
import { x402PaymentRequiredResponse, x402PaymentHeaderToPayload, x402SettleRequest, x402SettleResponse, } from "@faremeter/types/x402";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
export function findMatchingPaymentRequirements(accepts, payload) {
|
|
5
|
+
let possible;
|
|
6
|
+
if (payload.asset !== undefined) {
|
|
7
|
+
// Narrow based on the asset if available.
|
|
8
|
+
possible = accepts.filter((x) => x.network === payload.network &&
|
|
9
|
+
x.scheme === payload.scheme &&
|
|
10
|
+
x.asset === payload.asset);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
// Otherwise fall back to the behavior in coinbase/x402.
|
|
14
|
+
possible = accepts.filter((x) => x.network === payload.network && x.scheme === payload.scheme);
|
|
15
|
+
}
|
|
16
|
+
if (possible.length > 1) {
|
|
17
|
+
logger.warning(`found ${possible.length} ambiguous matching requirements for client payment: {*}`, payload);
|
|
18
|
+
}
|
|
19
|
+
// XXX - If there are more than one, this really should be an error.
|
|
20
|
+
// For now, err on the side of potential compatibility.
|
|
21
|
+
return possible[0];
|
|
22
|
+
}
|
|
23
|
+
export function gateGetPaymentRequiredResponse(res) {
|
|
24
|
+
if (res.status === 200) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const msg = `received a non success response to requirements request from facilitator: ${res.statusText} (${res.status})`;
|
|
28
|
+
logger.error(msg);
|
|
29
|
+
throw new Error(msg);
|
|
30
|
+
}
|
|
31
|
+
export async function getPaymentRequiredResponse(args) {
|
|
32
|
+
const accepts = args.accepts.map((x) => ({
|
|
33
|
+
...x,
|
|
34
|
+
resource: x.resource ?? args.resource,
|
|
35
|
+
}));
|
|
36
|
+
const t = await fetch(`${args.facilitatorURL}/accepts`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
Accept: "application/json",
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
x402Version: 1,
|
|
44
|
+
accepts,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
gateGetPaymentRequiredResponse(t);
|
|
48
|
+
const response = x402PaymentRequiredResponse(await t.json());
|
|
49
|
+
if (isValidationError(response)) {
|
|
50
|
+
throw new Error(`invalid payment requirements from facilitator: ${response.summary}`);
|
|
51
|
+
}
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
export async function handleMiddlewareRequest(args) {
|
|
55
|
+
// XXX - Temporarily request this every time. This will be
|
|
56
|
+
// cached in future.
|
|
57
|
+
const paymentRequiredResponse = await getPaymentRequiredResponse(args);
|
|
58
|
+
const sendPaymentRequired = () => args.sendJSONResponse(402, paymentRequiredResponse);
|
|
59
|
+
const paymentHeader = args.getHeader("X-PAYMENT");
|
|
60
|
+
if (!paymentHeader) {
|
|
61
|
+
return sendPaymentRequired();
|
|
62
|
+
}
|
|
63
|
+
const payload = x402PaymentHeaderToPayload(paymentHeader);
|
|
64
|
+
if (isValidationError(payload)) {
|
|
65
|
+
logger.debug(`couldn't validate client payload: ${payload.summary}`);
|
|
66
|
+
return sendPaymentRequired();
|
|
67
|
+
}
|
|
68
|
+
const paymentRequirements = findMatchingPaymentRequirements(paymentRequiredResponse.accepts, payload);
|
|
69
|
+
if (!paymentRequirements) {
|
|
70
|
+
logger.warning(`couldn't find matching payment requirements for payload`, payload);
|
|
71
|
+
return sendPaymentRequired();
|
|
72
|
+
}
|
|
73
|
+
const settleRequest = {
|
|
74
|
+
x402Version: 1,
|
|
75
|
+
paymentHeader,
|
|
76
|
+
paymentRequirements,
|
|
77
|
+
};
|
|
78
|
+
const t = await fetch(`${args.facilitatorURL}/settle`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
Accept: "application/json",
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify(settleRequest),
|
|
85
|
+
});
|
|
86
|
+
const settlementResponse = x402SettleResponse(await t.json());
|
|
87
|
+
if (isValidationError(settlementResponse)) {
|
|
88
|
+
const msg = `error getting response from facilitator for settlement: ${settlementResponse.summary}`;
|
|
89
|
+
logger.error(msg);
|
|
90
|
+
throw new Error(msg);
|
|
91
|
+
}
|
|
92
|
+
if (!settlementResponse.success) {
|
|
93
|
+
logger.warning("failed to settle payment: {error}", settlementResponse);
|
|
94
|
+
return sendPaymentRequired();
|
|
95
|
+
}
|
|
96
|
+
}
|
package/dist/src/express.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CommonMiddlewareArgs } from "./common.js";
|
|
2
2
|
import type { NextFunction, Request, Response } from "express";
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
facilitatorURL: string;
|
|
6
|
-
};
|
|
7
|
-
export declare function createMiddleware(args: CreateMiddlewareArgs): Promise<(req: Request, res: Response, next: NextFunction) => Promise<void>>;
|
|
3
|
+
type createMiddlewareArgs = CommonMiddlewareArgs;
|
|
4
|
+
export declare function createMiddleware(args: createMiddlewareArgs): Promise<(req: Request, res: Response, next: NextFunction) => Promise<void>>;
|
|
8
5
|
export {};
|
|
9
6
|
//# sourceMappingURL=express.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,KAAK,oBAAoB,GAAG,oBAAoB,CAAC;AAEjD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,iBAC5C,OAAO,OAAO,QAAQ,QAAQ,YAAY,oBAc9D"}
|
package/dist/src/express.js
CHANGED
|
@@ -1,70 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { findMatchingPaymentRequirements, gateGetPaymentRequiredResponse, } from "./utils.js";
|
|
3
|
-
async function getPaymentRequiredResponse(args, req) {
|
|
4
|
-
// XXX - This is tracking the behavior of coinbase/x402, but is
|
|
5
|
-
// probably too naive of a URL creator for the long run.
|
|
6
|
-
const resource = `${req.protocol}://${req.headers.host}${req.path}`;
|
|
7
|
-
const accepts = args.accepts.map((x) => ({
|
|
8
|
-
...x,
|
|
9
|
-
resource: x.resource ?? resource,
|
|
10
|
-
}));
|
|
11
|
-
const t = await fetch(`${args.facilitatorURL}/accepts`, {
|
|
12
|
-
method: "POST",
|
|
13
|
-
headers: {
|
|
14
|
-
Accept: "application/json",
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
},
|
|
17
|
-
body: JSON.stringify({
|
|
18
|
-
x402Version: 1,
|
|
19
|
-
accepts,
|
|
20
|
-
}),
|
|
21
|
-
});
|
|
22
|
-
gateGetPaymentRequiredResponse(t);
|
|
23
|
-
const response = x402PaymentRequiredResponse(await t.json());
|
|
24
|
-
if (isValidationError(response)) {
|
|
25
|
-
throw new Error(`invalid payment requirements from facilitator: ${response.summary}`);
|
|
26
|
-
}
|
|
27
|
-
return response;
|
|
28
|
-
}
|
|
1
|
+
import { handleMiddlewareRequest } from "./common.js";
|
|
29
2
|
export async function createMiddleware(args) {
|
|
30
3
|
return async (req, res, next) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
res.status(
|
|
36
|
-
};
|
|
37
|
-
const paymentHeader = req.header("X-PAYMENT");
|
|
38
|
-
if (!paymentHeader) {
|
|
39
|
-
return sendPaymentRequired(res);
|
|
40
|
-
}
|
|
41
|
-
const payload = x402PaymentHeaderToPayload(paymentHeader);
|
|
42
|
-
if (isValidationError(payload)) {
|
|
43
|
-
return sendPaymentRequired(res);
|
|
44
|
-
}
|
|
45
|
-
const paymentRequirements = findMatchingPaymentRequirements(paymentRequiredResponse.accepts, payload);
|
|
46
|
-
if (!paymentRequirements) {
|
|
47
|
-
return sendPaymentRequired(res);
|
|
48
|
-
}
|
|
49
|
-
const settleRequest = {
|
|
50
|
-
x402Version: 1,
|
|
51
|
-
paymentHeader,
|
|
52
|
-
paymentRequirements,
|
|
53
|
-
};
|
|
54
|
-
const t = await fetch(`${args.facilitatorURL}/settle`, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: {
|
|
57
|
-
Accept: "application/json",
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify(settleRequest),
|
|
4
|
+
const response = await handleMiddlewareRequest({
|
|
5
|
+
...args,
|
|
6
|
+
resource: `${req.protocol}://${req.headers.host}${req.path}`,
|
|
7
|
+
getHeader: (key) => req.header(key),
|
|
8
|
+
sendJSONResponse: (status, body) => res.status(status).json(body),
|
|
61
9
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
throw new Error(`error getting response from facilitator for settlement: ${settlementResponse.summary}`);
|
|
65
|
-
}
|
|
66
|
-
if (!settlementResponse.success) {
|
|
67
|
-
return sendPaymentRequired(res);
|
|
10
|
+
if (response) {
|
|
11
|
+
return;
|
|
68
12
|
}
|
|
69
13
|
next();
|
|
70
14
|
};
|
package/dist/src/hono.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CommonMiddlewareArgs } from "./common.js";
|
|
2
2
|
import type { MiddlewareHandler } from "hono";
|
|
3
|
-
type CreateMiddlewareArgs =
|
|
4
|
-
accepts: RelaxedRequirements[];
|
|
5
|
-
facilitatorURL: string;
|
|
6
|
-
};
|
|
3
|
+
type CreateMiddlewareArgs = CommonMiddlewareArgs;
|
|
7
4
|
export declare function createMiddleware(args: CreateMiddlewareArgs): Promise<MiddlewareHandler>;
|
|
8
5
|
export {};
|
|
9
6
|
//# sourceMappingURL=hono.d.ts.map
|
package/dist/src/hono.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/hono.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,KAAK,oBAAoB,GAAG,oBAAoB,CAAC;AAEjD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAkB5B"}
|
package/dist/src/hono.js
CHANGED
|
@@ -1,68 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { findMatchingPaymentRequirements, gateGetPaymentRequiredResponse, } from "./utils.js";
|
|
1
|
+
import { handleMiddlewareRequest } from "./common.js";
|
|
3
2
|
export async function createMiddleware(args) {
|
|
4
|
-
async function getPaymentRequiredResponse(args, c) {
|
|
5
|
-
const accepts = args.accepts.map((x) => ({
|
|
6
|
-
...x,
|
|
7
|
-
resource: x.resource ?? c.req.url,
|
|
8
|
-
}));
|
|
9
|
-
const t = await fetch(`${args.facilitatorURL}/accepts`, {
|
|
10
|
-
method: "POST",
|
|
11
|
-
headers: {
|
|
12
|
-
Accept: "application/json",
|
|
13
|
-
"Content-Type": "application/json",
|
|
14
|
-
},
|
|
15
|
-
body: JSON.stringify({
|
|
16
|
-
x402Version: 1,
|
|
17
|
-
accepts,
|
|
18
|
-
}),
|
|
19
|
-
});
|
|
20
|
-
gateGetPaymentRequiredResponse(t);
|
|
21
|
-
const response = x402PaymentRequiredResponse(await t.json());
|
|
22
|
-
if (isValidationError(response)) {
|
|
23
|
-
throw new Error(`invalid payment requirements from facilitator: ${response.summary}`);
|
|
24
|
-
}
|
|
25
|
-
return response;
|
|
26
|
-
}
|
|
27
3
|
return async (c, next) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const paymentHeader = c.req.header("X-PAYMENT");
|
|
36
|
-
if (!paymentHeader) {
|
|
37
|
-
return sendPaymentRequired();
|
|
38
|
-
}
|
|
39
|
-
const payload = x402PaymentHeaderToPayload(paymentHeader);
|
|
40
|
-
if (isValidationError(payload)) {
|
|
41
|
-
return sendPaymentRequired();
|
|
42
|
-
}
|
|
43
|
-
const paymentRequirements = findMatchingPaymentRequirements(paymentRequiredResponse.accepts, payload);
|
|
44
|
-
if (!paymentRequirements) {
|
|
45
|
-
return sendPaymentRequired();
|
|
46
|
-
}
|
|
47
|
-
const settleRequest = {
|
|
48
|
-
x402Version: 1,
|
|
49
|
-
paymentHeader,
|
|
50
|
-
paymentRequirements,
|
|
51
|
-
};
|
|
52
|
-
const t = await fetch(`${args.facilitatorURL}/settle`, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: {
|
|
55
|
-
Accept: "application/json",
|
|
56
|
-
"Content-Type": "application/json",
|
|
4
|
+
const response = await handleMiddlewareRequest({
|
|
5
|
+
...args,
|
|
6
|
+
resource: c.req.url,
|
|
7
|
+
getHeader: (key) => c.req.header(key),
|
|
8
|
+
sendJSONResponse: (status, body) => {
|
|
9
|
+
c.status(status);
|
|
10
|
+
return c.json(body);
|
|
57
11
|
},
|
|
58
|
-
body: JSON.stringify(settleRequest),
|
|
59
12
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
throw new Error(`error getting response from facilitator for settlement: ${settlementResponse.summary}`);
|
|
63
|
-
}
|
|
64
|
-
if (!settlementResponse.success) {
|
|
65
|
-
return sendPaymentRequired();
|
|
13
|
+
if (response) {
|
|
14
|
+
return response;
|
|
66
15
|
}
|
|
67
16
|
await next();
|
|
68
17
|
};
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC/B,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC"}
|
package/dist/src/index.js
CHANGED