svix 2.2.0 → 2.4.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/index.d.mts +1039 -604
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4410 -3642
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/destination.ts +133 -0
- package/src/api/destinationTransformation.ts +55 -0
- package/src/api_internal/destination.ts +12 -0
- package/src/api_internal/destinationAutoconfig.ts +27 -0
- package/src/api_internal/endpoint.ts +8 -3
- package/src/api_internal/{endpointAutoConfig.ts → endpointAutoConfigDeprecated.ts} +1 -1
- package/src/api_internal/endpointAutoconfig.ts +50 -0
- package/src/autoconfig.test.ts +169 -4
- package/src/autoconfig.ts +89 -26
- package/src/autoconfigConsumer.ts +90 -16
- package/src/index.ts +5 -0
- package/src/models/autoConfigSubscriptionOut.ts +38 -0
- package/src/models/destinationIn.ts +303 -0
- package/src/models/destinationOut.ts +295 -0
- package/src/models/destinationPatch.ts +302 -0
- package/src/models/destinationStatus.ts +18 -0
- package/src/models/destinationStatusIn.ts +16 -0
- package/src/models/destinationTransformIn.ts +19 -0
- package/src/models/destinationTransformationOut.ts +22 -0
- package/src/models/fifoEndpointConfigIn.ts +25 -0
- package/src/models/index.ts +14 -0
- package/src/models/ingestSourceIn.ts +12 -0
- package/src/models/ingestSourceOut.ts +12 -0
- package/src/models/listResponseDestinationOut.ts +31 -0
- package/src/models/mergeConfig.ts +19 -0
- package/src/models/mergeConfigOut.ts +15 -0
- package/src/models/postgresConfigIn.ts +44 -0
- package/src/models/postgresConfigOut.ts +25 -0
- package/src/models/postgresConfigPatch.ts +28 -0
- package/src/models/s3ConfigIn.ts +15 -3
- package/src/models/s3ConfigOut.ts +7 -1
- package/src/models/s3ConfigPatch.ts +6 -0
- package/src/models/status.ts +16 -0
- package/src/models/streamSinkIn.ts +12 -0
- package/src/models/streamSinkOut.ts +27 -0
- package/src/models/streamSinkPatch.ts +15 -0
- package/src/request.ts +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// this file is @generated
|
|
2
|
+
|
|
3
|
+
import { type DestinationIn, DestinationInSerializer } from "../models/destinationIn";
|
|
4
|
+
import { type DestinationOut, DestinationOutSerializer } from "../models/destinationOut";
|
|
5
|
+
import {
|
|
6
|
+
type DestinationPatch,
|
|
7
|
+
DestinationPatchSerializer,
|
|
8
|
+
} from "../models/destinationPatch";
|
|
9
|
+
import {
|
|
10
|
+
type ListResponseDestinationOut,
|
|
11
|
+
ListResponseDestinationOutSerializer,
|
|
12
|
+
} from "../models/listResponseDestinationOut";
|
|
13
|
+
import type { Ordering } from "../models/ordering";
|
|
14
|
+
import { DestinationTransformation } from "./destinationTransformation";
|
|
15
|
+
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
16
|
+
|
|
17
|
+
export interface DestinationListOptions {
|
|
18
|
+
/** Limit the number of returned items */
|
|
19
|
+
limit?: number;
|
|
20
|
+
/** The iterator returned from a prior invocation */
|
|
21
|
+
iterator?: string | null;
|
|
22
|
+
/** The sorting order of the returned items */
|
|
23
|
+
order?: Ordering;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DestinationCreateOptions {
|
|
27
|
+
idempotencyKey?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class Destination {
|
|
31
|
+
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
32
|
+
|
|
33
|
+
public get transformation() {
|
|
34
|
+
return new DestinationTransformation(this.requestCtx);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** List of all the application's destinations. */
|
|
38
|
+
public async list(
|
|
39
|
+
appId: string,
|
|
40
|
+
options?: DestinationListOptions
|
|
41
|
+
): Promise<ListResponseDestinationOut> {
|
|
42
|
+
const request = new SvixRequest(HttpMethod.GET, "/api/v1/app/{app_id}/destination");
|
|
43
|
+
|
|
44
|
+
request.setPathParam("app_id", appId);
|
|
45
|
+
request.setQueryParams({
|
|
46
|
+
limit: options?.limit,
|
|
47
|
+
iterator: options?.iterator,
|
|
48
|
+
order: options?.order,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return await request.send(
|
|
52
|
+
this.requestCtx,
|
|
53
|
+
ListResponseDestinationOutSerializer._fromJsonObject
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Creates a new destination. */
|
|
58
|
+
public async create(
|
|
59
|
+
appId: string,
|
|
60
|
+
destinationIn: DestinationIn,
|
|
61
|
+
options?: DestinationCreateOptions
|
|
62
|
+
): Promise<DestinationOut> {
|
|
63
|
+
const request = new SvixRequest(HttpMethod.POST, "/api/v1/app/{app_id}/destination");
|
|
64
|
+
|
|
65
|
+
request.setPathParam("app_id", appId);
|
|
66
|
+
request.setHeaderParam("idempotency-key", options?.idempotencyKey);
|
|
67
|
+
request.setBody(DestinationInSerializer._toJsonObject(destinationIn));
|
|
68
|
+
|
|
69
|
+
return await request.send(this.requestCtx, DestinationOutSerializer._fromJsonObject);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Get a destination by id or uid. */
|
|
73
|
+
public async get(appId: string, destinationId: string): Promise<DestinationOut> {
|
|
74
|
+
const request = new SvixRequest(
|
|
75
|
+
HttpMethod.GET,
|
|
76
|
+
"/api/v1/app/{app_id}/destination/{destination_id}"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
request.setPathParam("app_id", appId);
|
|
80
|
+
request.setPathParam("destination_id", destinationId);
|
|
81
|
+
|
|
82
|
+
return await request.send(this.requestCtx, DestinationOutSerializer._fromJsonObject);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Create or update a destination. */
|
|
86
|
+
public async upsert(
|
|
87
|
+
appId: string,
|
|
88
|
+
destinationId: string,
|
|
89
|
+
destinationIn: DestinationIn
|
|
90
|
+
): Promise<DestinationOut> {
|
|
91
|
+
const request = new SvixRequest(
|
|
92
|
+
HttpMethod.PUT,
|
|
93
|
+
"/api/v1/app/{app_id}/destination/{destination_id}"
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
request.setPathParam("app_id", appId);
|
|
97
|
+
request.setPathParam("destination_id", destinationId);
|
|
98
|
+
request.setBody(DestinationInSerializer._toJsonObject(destinationIn));
|
|
99
|
+
|
|
100
|
+
return await request.send(this.requestCtx, DestinationOutSerializer._fromJsonObject);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Delete a destination. */
|
|
104
|
+
public async delete(appId: string, destinationId: string): Promise<void> {
|
|
105
|
+
const request = new SvixRequest(
|
|
106
|
+
HttpMethod.DELETE,
|
|
107
|
+
"/api/v1/app/{app_id}/destination/{destination_id}"
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
request.setPathParam("app_id", appId);
|
|
111
|
+
request.setPathParam("destination_id", destinationId);
|
|
112
|
+
|
|
113
|
+
return await request.sendNoResponseBody(this.requestCtx);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Partially update a destination. */
|
|
117
|
+
public async patch(
|
|
118
|
+
appId: string,
|
|
119
|
+
destinationId: string,
|
|
120
|
+
destinationPatch: DestinationPatch
|
|
121
|
+
): Promise<DestinationOut> {
|
|
122
|
+
const request = new SvixRequest(
|
|
123
|
+
HttpMethod.PATCH,
|
|
124
|
+
"/api/v1/app/{app_id}/destination/{destination_id}"
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
request.setPathParam("app_id", appId);
|
|
128
|
+
request.setPathParam("destination_id", destinationId);
|
|
129
|
+
request.setBody(DestinationPatchSerializer._toJsonObject(destinationPatch));
|
|
130
|
+
|
|
131
|
+
return await request.send(this.requestCtx, DestinationOutSerializer._fromJsonObject);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// this file is @generated
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type DestinationTransformIn,
|
|
5
|
+
DestinationTransformInSerializer,
|
|
6
|
+
} from "../models/destinationTransformIn";
|
|
7
|
+
import {
|
|
8
|
+
type DestinationTransformationOut,
|
|
9
|
+
DestinationTransformationOutSerializer,
|
|
10
|
+
} from "../models/destinationTransformationOut";
|
|
11
|
+
import { type EmptyResponse, EmptyResponseSerializer } from "../models/emptyResponse";
|
|
12
|
+
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
13
|
+
|
|
14
|
+
export class DestinationTransformation {
|
|
15
|
+
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
16
|
+
|
|
17
|
+
/** Get the transformation code associated with this destination. */
|
|
18
|
+
public async get(
|
|
19
|
+
appId: string,
|
|
20
|
+
destinationId: string
|
|
21
|
+
): Promise<DestinationTransformationOut> {
|
|
22
|
+
const request = new SvixRequest(
|
|
23
|
+
HttpMethod.GET,
|
|
24
|
+
"/api/v1/app/{app_id}/destination/{destination_id}/transformation"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
request.setPathParam("app_id", appId);
|
|
28
|
+
request.setPathParam("destination_id", destinationId);
|
|
29
|
+
|
|
30
|
+
return await request.send(
|
|
31
|
+
this.requestCtx,
|
|
32
|
+
DestinationTransformationOutSerializer._fromJsonObject
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Set or unset the transformation code associated with this destination. */
|
|
37
|
+
public async patch(
|
|
38
|
+
appId: string,
|
|
39
|
+
destinationId: string,
|
|
40
|
+
destinationTransformIn: DestinationTransformIn = {}
|
|
41
|
+
): Promise<EmptyResponse> {
|
|
42
|
+
const request = new SvixRequest(
|
|
43
|
+
HttpMethod.PATCH,
|
|
44
|
+
"/api/v1/app/{app_id}/destination/{destination_id}/transformation"
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
request.setPathParam("app_id", appId);
|
|
48
|
+
request.setPathParam("destination_id", destinationId);
|
|
49
|
+
request.setBody(
|
|
50
|
+
DestinationTransformInSerializer._toJsonObject(destinationTransformIn)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return await request.send(this.requestCtx, EmptyResponseSerializer._fromJsonObject);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// this file is @generated
|
|
2
|
+
|
|
3
|
+
import { DestinationAutoconfig } from "./destinationAutoconfig";
|
|
4
|
+
import type { SvixRequestContext } from "../request";
|
|
5
|
+
|
|
6
|
+
export class Destination {
|
|
7
|
+
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
8
|
+
|
|
9
|
+
public get autoconfig() {
|
|
10
|
+
return new DestinationAutoconfig(this.requestCtx);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// this file is @generated
|
|
2
|
+
|
|
3
|
+
import { type DestinationIn, DestinationInSerializer } from "../models/destinationIn";
|
|
4
|
+
import { type DestinationOut, DestinationOutSerializer } from "../models/destinationOut";
|
|
5
|
+
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
6
|
+
|
|
7
|
+
export class DestinationAutoconfig {
|
|
8
|
+
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
9
|
+
|
|
10
|
+
/** Create or update the destination for an AutoConfig subscription. */
|
|
11
|
+
public async subscribe(
|
|
12
|
+
appId: string,
|
|
13
|
+
autoconfigId: string,
|
|
14
|
+
destinationIn: DestinationIn
|
|
15
|
+
): Promise<DestinationOut> {
|
|
16
|
+
const request = new SvixRequest(
|
|
17
|
+
HttpMethod.PUT,
|
|
18
|
+
"/api/v1/app/{app_id}/autoconfig/{autoconfig_id}/destination"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
request.setPathParam("app_id", appId);
|
|
22
|
+
request.setPathParam("autoconfig_id", autoconfigId);
|
|
23
|
+
request.setBody(DestinationInSerializer._toJsonObject(destinationIn));
|
|
24
|
+
|
|
25
|
+
return await request.send(this.requestCtx, DestinationOutSerializer._fromJsonObject);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -4,14 +4,19 @@ import {
|
|
|
4
4
|
type EndpointTransformationIn,
|
|
5
5
|
EndpointTransformationInSerializer,
|
|
6
6
|
} from "../models/endpointTransformationIn";
|
|
7
|
-
import {
|
|
7
|
+
import { EndpointAutoConfigDeprecated } from "./endpointAutoConfigDeprecated";
|
|
8
|
+
import { EndpointAutoconfig } from "./endpointAutoconfig";
|
|
8
9
|
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
9
10
|
|
|
10
11
|
export class Endpoint {
|
|
11
12
|
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
12
13
|
|
|
13
|
-
public get
|
|
14
|
-
return new
|
|
14
|
+
public get autoConfigDeprecated() {
|
|
15
|
+
return new EndpointAutoConfigDeprecated(this.requestCtx);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get autoconfig() {
|
|
19
|
+
return new EndpointAutoconfig(this.requestCtx);
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -4,7 +4,7 @@ import { type EndpointOut, EndpointOutSerializer } from "../models/endpointOut";
|
|
|
4
4
|
import { type SubscribeIn, SubscribeInSerializer } from "../models/subscribeIn";
|
|
5
5
|
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
6
6
|
|
|
7
|
-
export class
|
|
7
|
+
export class EndpointAutoConfigDeprecated {
|
|
8
8
|
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
9
9
|
|
|
10
10
|
/** Update an auto-config endpoint by providing endpoint details. */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// this file is @generated
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AutoConfigSubscriptionOut,
|
|
5
|
+
AutoConfigSubscriptionOutSerializer,
|
|
6
|
+
} from "../models/autoConfigSubscriptionOut";
|
|
7
|
+
import { type EndpointIn, EndpointInSerializer } from "../models/endpointIn";
|
|
8
|
+
import { type EndpointOut, EndpointOutSerializer } from "../models/endpointOut";
|
|
9
|
+
import { HttpMethod, SvixRequest, type SvixRequestContext } from "../request";
|
|
10
|
+
|
|
11
|
+
export class EndpointAutoconfig {
|
|
12
|
+
public constructor(private readonly requestCtx: SvixRequestContext) {}
|
|
13
|
+
|
|
14
|
+
/** Get an AutoConfig subscription, including the bound endpoint or destination if any. */
|
|
15
|
+
public async get(
|
|
16
|
+
appId: string,
|
|
17
|
+
autoconfigId: string
|
|
18
|
+
): Promise<AutoConfigSubscriptionOut> {
|
|
19
|
+
const request = new SvixRequest(
|
|
20
|
+
HttpMethod.GET,
|
|
21
|
+
"/api/v1/app/{app_id}/autoconfig/{autoconfig_id}"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
request.setPathParam("app_id", appId);
|
|
25
|
+
request.setPathParam("autoconfig_id", autoconfigId);
|
|
26
|
+
|
|
27
|
+
return await request.send(
|
|
28
|
+
this.requestCtx,
|
|
29
|
+
AutoConfigSubscriptionOutSerializer._fromJsonObject
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Create or update the HTTP endpoint for an AutoConfig subscription. */
|
|
34
|
+
public async subscribe(
|
|
35
|
+
appId: string,
|
|
36
|
+
autoconfigId: string,
|
|
37
|
+
endpointIn: EndpointIn
|
|
38
|
+
): Promise<EndpointOut> {
|
|
39
|
+
const request = new SvixRequest(
|
|
40
|
+
HttpMethod.PUT,
|
|
41
|
+
"/api/v1/app/{app_id}/autoconfig/{autoconfig_id}/endpoint"
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
request.setPathParam("app_id", appId);
|
|
45
|
+
request.setPathParam("autoconfig_id", autoconfigId);
|
|
46
|
+
request.setBody(EndpointInSerializer._toJsonObject(endpointIn));
|
|
47
|
+
|
|
48
|
+
return await request.send(this.requestCtx, EndpointOutSerializer._fromJsonObject);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/autoconfig.test.ts
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import { strict as assert } from "node:assert/strict";
|
|
3
|
+
import * as mockttp from "mockttp";
|
|
3
4
|
|
|
4
|
-
import { AutoConfig, AutoConfigError, Webhook } from "./index";
|
|
5
|
+
import { AutoConfig, AutoConfigConsumer, AutoConfigError, Webhook } from "./index";
|
|
5
6
|
|
|
6
|
-
function
|
|
7
|
+
function makeToken(
|
|
8
|
+
prefix: "auto_v1_" | "auto_v2_",
|
|
9
|
+
payload: Record<string, string>
|
|
10
|
+
): string {
|
|
7
11
|
const json = JSON.stringify(payload);
|
|
8
|
-
return
|
|
12
|
+
return `${prefix}${Buffer.from(json, "utf8").toString("base64")}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeTokenV1(payload: Record<string, string>): string {
|
|
16
|
+
return makeToken("auto_v1_", payload);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeTokenV2(payload: Record<string, string>): string {
|
|
20
|
+
return makeToken("auto_v2_", payload);
|
|
9
21
|
}
|
|
10
22
|
|
|
23
|
+
const endpointOut = `{"id":"ep_2","metadata":{},"url":"https://consumer.example/webhook","description":"","createdAt":"2019-08-24T14:15:22Z","updatedAt":"2019-08-24T14:15:22Z"}`;
|
|
24
|
+
const destinationOut = `{"type":"pollingEndpoint","config":{},"id":"dst_123","status":"enabled","currentIterator":"0","createdAt":"2019-08-24T14:15:22Z","updatedAt":"2019-08-24T14:15:22Z","batchSize":100,"maxWaitSecs":10,"metadata":{}}`;
|
|
25
|
+
const pollOut = `{"data":[],"done":true}`;
|
|
26
|
+
|
|
11
27
|
test("AutoConfig accepts valid auto_v1 token and verify matches Webhook", () => {
|
|
12
28
|
const esec = "whsec_Zm9v";
|
|
13
29
|
const token = makeTokenV1({
|
|
@@ -42,10 +58,159 @@ test("AutoConfig rejects wrong prefix", () => {
|
|
|
42
58
|
tok: "t",
|
|
43
59
|
});
|
|
44
60
|
const token = `wrong_${Buffer.from(json, "utf8").toString("base64")}`;
|
|
45
|
-
assert.throws(
|
|
61
|
+
assert.throws(
|
|
62
|
+
() => new AutoConfig(token, { url: "https://x" }),
|
|
63
|
+
(err: unknown) =>
|
|
64
|
+
err instanceof AutoConfigError &&
|
|
65
|
+
err.message ===
|
|
66
|
+
"Unsupported token version. You might need to update the Svix SDK to use this token"
|
|
67
|
+
);
|
|
46
68
|
});
|
|
47
69
|
|
|
48
70
|
test("AutoConfig rejects invalid JSON payload", () => {
|
|
49
71
|
const token = `auto_v1_${Buffer.from("not json", "utf8").toString("base64")}`;
|
|
50
72
|
assert.throws(() => new AutoConfig(token, { url: "https://x" }), AutoConfigError);
|
|
51
73
|
});
|
|
74
|
+
|
|
75
|
+
test("AutoConfig accepts valid auto_v2 token and verify matches Webhook", () => {
|
|
76
|
+
const esec = "whsec_Zm9v";
|
|
77
|
+
const token = makeTokenV2({
|
|
78
|
+
aid: "app_1",
|
|
79
|
+
sid: "acfg_2",
|
|
80
|
+
surl: "https://api.example.test",
|
|
81
|
+
esec,
|
|
82
|
+
tok: "sk_test_xyz",
|
|
83
|
+
});
|
|
84
|
+
const ac = new AutoConfig(token, { url: "https://consumer.example/webhook" });
|
|
85
|
+
|
|
86
|
+
const payload = '{"hello":"world"}';
|
|
87
|
+
const id = "msg_test_autoconfig_v2";
|
|
88
|
+
const ts = new Date();
|
|
89
|
+
const wh = new Webhook(esec);
|
|
90
|
+
const sig = wh.sign(id, ts, payload);
|
|
91
|
+
const headers = {
|
|
92
|
+
"svix-id": id,
|
|
93
|
+
"svix-timestamp": Math.floor(ts.getTime() / 1000).toString(),
|
|
94
|
+
"svix-signature": sig,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
ac.verify(payload, headers);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("subscribe", async (t) => {
|
|
101
|
+
const mockServer = mockttp.getLocal();
|
|
102
|
+
t.beforeEach(async () => await mockServer.start(0));
|
|
103
|
+
t.afterEach(async () => await mockServer.stop());
|
|
104
|
+
|
|
105
|
+
await t.test("v1 AutoConfig", async () => {
|
|
106
|
+
const mock = await mockServer
|
|
107
|
+
.forPut("/api/v1/app/app_1/endpoint/ep_2/auto-config")
|
|
108
|
+
.thenReply(200, endpointOut);
|
|
109
|
+
const token = makeTokenV1({
|
|
110
|
+
aid: "app_1",
|
|
111
|
+
eid: "ep_2",
|
|
112
|
+
surl: mockServer.url,
|
|
113
|
+
esec: "whsec_Zm9v",
|
|
114
|
+
tok: "sk_test_xyz",
|
|
115
|
+
});
|
|
116
|
+
const ac = new AutoConfig(token, { url: "https://consumer.example/webhook" });
|
|
117
|
+
const out = await ac.subscribe();
|
|
118
|
+
assert.equal(out.id, "ep_2");
|
|
119
|
+
const requests = await mock.getSeenRequests();
|
|
120
|
+
assert.equal(requests.length, 1);
|
|
121
|
+
assert.equal(
|
|
122
|
+
await requests[0].body.getText(),
|
|
123
|
+
`{"endpoint":{"url":"https://consumer.example/webhook"}}`
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await t.test("v2 AutoConfig", async () => {
|
|
128
|
+
const mock = await mockServer
|
|
129
|
+
.forPut("/api/v1/app/app_1/autoconfig/acfg_2/endpoint")
|
|
130
|
+
.thenReply(200, endpointOut);
|
|
131
|
+
const token = makeTokenV2({
|
|
132
|
+
aid: "app_1",
|
|
133
|
+
sid: "acfg_2",
|
|
134
|
+
surl: mockServer.url,
|
|
135
|
+
esec: "whsec_Zm9v",
|
|
136
|
+
tok: "sk_test_xyz",
|
|
137
|
+
});
|
|
138
|
+
const ac = new AutoConfig(token, { url: "https://consumer.example/webhook" });
|
|
139
|
+
const out = await ac.subscribe();
|
|
140
|
+
assert.equal(out.id, "ep_2");
|
|
141
|
+
const requests = await mock.getSeenRequests();
|
|
142
|
+
assert.equal(requests.length, 1);
|
|
143
|
+
assert.equal(
|
|
144
|
+
await requests[0].body.getText(),
|
|
145
|
+
`{"url":"https://consumer.example/webhook"}`
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await t.test("v1 AutoConfigConsumer", async () => {
|
|
150
|
+
const mock = await mockServer
|
|
151
|
+
.forPut("/api/v1/app/app_1/endpoint/ep_2/auto-config")
|
|
152
|
+
.thenReply(200, endpointOut);
|
|
153
|
+
const token = makeTokenV1({
|
|
154
|
+
aid: "app_1",
|
|
155
|
+
eid: "ep_2",
|
|
156
|
+
surl: mockServer.url,
|
|
157
|
+
esec: "whsec_Zm9v",
|
|
158
|
+
tok: "sk_test_xyz",
|
|
159
|
+
});
|
|
160
|
+
const consumer = new AutoConfigConsumer(token, {
|
|
161
|
+
eventTypes: ["issue.opened"],
|
|
162
|
+
});
|
|
163
|
+
await consumer.subscribe();
|
|
164
|
+
const requests = await mock.getSeenRequests();
|
|
165
|
+
assert.equal(requests.length, 1);
|
|
166
|
+
assert.equal(
|
|
167
|
+
await requests[0].body.getText(),
|
|
168
|
+
`{"sink":{"type":"poller","config":{"eventTypes":["issue.opened"]}}}`
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await t.test("v2 AutoConfigConsumer", async () => {
|
|
173
|
+
const mock = await mockServer
|
|
174
|
+
.forPut("/api/v1/app/app_1/autoconfig/acfg_2/destination")
|
|
175
|
+
.thenReply(200, destinationOut);
|
|
176
|
+
const token = makeTokenV2({
|
|
177
|
+
aid: "app_1",
|
|
178
|
+
sid: "acfg_2",
|
|
179
|
+
surl: mockServer.url,
|
|
180
|
+
esec: "whsec_Zm9v",
|
|
181
|
+
tok: "sk_test_xyz",
|
|
182
|
+
});
|
|
183
|
+
const consumer = new AutoConfigConsumer(token, {
|
|
184
|
+
channels: ["ch1"],
|
|
185
|
+
});
|
|
186
|
+
const out = await consumer.subscribe();
|
|
187
|
+
assert.equal(out.id, "dst_123");
|
|
188
|
+
const requests = await mock.getSeenRequests();
|
|
189
|
+
assert.equal(requests.length, 1);
|
|
190
|
+
assert.equal(
|
|
191
|
+
await requests[0].body.getText(),
|
|
192
|
+
`{"type":"pollingEndpoint","config":{},"channels":["ch1"]}`
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await t.test("v2 receive gets subscription", async () => {
|
|
197
|
+
const subscriptionOut = `{"id":"auto_1srOrx2ZWZBpBUvZwXKQmoEYga2","tokenCensored":"***","createdAt":"2019-08-24T14:15:22Z","status":"active","destId":"dst_123"}`;
|
|
198
|
+
await mockServer
|
|
199
|
+
.forGet("/api/v1/app/app_1/autoconfig/acfg_2")
|
|
200
|
+
.thenReply(200, subscriptionOut);
|
|
201
|
+
const pollMock = await mockServer
|
|
202
|
+
.forGet("/api/v1/app/app_1/polling-endpoint/dst_123/consumer/c1")
|
|
203
|
+
.thenReply(200, pollOut);
|
|
204
|
+
const token = makeTokenV2({
|
|
205
|
+
aid: "app_1",
|
|
206
|
+
sid: "acfg_2",
|
|
207
|
+
surl: mockServer.url,
|
|
208
|
+
esec: "whsec_Zm9v",
|
|
209
|
+
tok: "sk_test_xyz",
|
|
210
|
+
});
|
|
211
|
+
const consumer = new AutoConfigConsumer(token, {});
|
|
212
|
+
await consumer.receive("c1");
|
|
213
|
+
const requests = await pollMock.getSeenRequests();
|
|
214
|
+
assert.equal(requests.length, 1);
|
|
215
|
+
});
|
|
216
|
+
});
|
package/src/autoconfig.ts
CHANGED
|
@@ -10,12 +10,34 @@ import {
|
|
|
10
10
|
} from "./webhook";
|
|
11
11
|
|
|
12
12
|
const AUTOCONFIG_TOKEN_PREFIX_V1 = "auto_v1_";
|
|
13
|
+
const AUTOCONFIG_TOKEN_PREFIX_V2 = "auto_v2_";
|
|
14
|
+
|
|
15
|
+
const UNSUPPORTED_TOKEN_VERSION =
|
|
16
|
+
"Unsupported token version. You might need to update the Svix SDK to use this token";
|
|
13
17
|
|
|
14
18
|
export interface AutoConfigTokenContentV1 {
|
|
19
|
+
// Application ID
|
|
15
20
|
aid: string;
|
|
21
|
+
// Endpoint ID
|
|
16
22
|
eid: string;
|
|
23
|
+
// Server URL
|
|
24
|
+
surl: string;
|
|
25
|
+
// Endpoint secret
|
|
26
|
+
esec: string;
|
|
27
|
+
// Token
|
|
28
|
+
tok: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AutoConfigTokenContentV2 {
|
|
32
|
+
// Application ID
|
|
33
|
+
aid: string;
|
|
34
|
+
// Autoconfig ID
|
|
35
|
+
sid: string;
|
|
36
|
+
// Server URL
|
|
17
37
|
surl: string;
|
|
38
|
+
// Endpoint secret
|
|
18
39
|
esec: string;
|
|
40
|
+
// Token
|
|
19
41
|
tok: string;
|
|
20
42
|
}
|
|
21
43
|
|
|
@@ -43,66 +65,107 @@ export function isAutoConfigTokenContentV1(
|
|
|
43
65
|
);
|
|
44
66
|
}
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
68
|
+
function isAutoConfigTokenContentV2(value: unknown): value is AutoConfigTokenContentV2 {
|
|
69
|
+
if (typeof value !== "object" || value === null) {
|
|
70
|
+
return false;
|
|
49
71
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
72
|
+
const { aid, sid, surl, esec, tok } = value as AutoConfigTokenContentV2;
|
|
73
|
+
return (
|
|
74
|
+
typeof aid === "string" &&
|
|
75
|
+
typeof sid === "string" &&
|
|
76
|
+
typeof surl === "string" &&
|
|
77
|
+
typeof esec === "string" &&
|
|
78
|
+
typeof tok === "string"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw new AutoConfigError();
|
|
82
|
+
function parseTokenPayload(token: string, prefix: string): unknown {
|
|
83
|
+
if (!token.startsWith(prefix)) {
|
|
84
|
+
throw new AutoConfigError(UNSUPPORTED_TOKEN_VERSION);
|
|
57
85
|
}
|
|
58
|
-
|
|
59
|
-
let parsed: unknown;
|
|
60
86
|
try {
|
|
61
|
-
|
|
87
|
+
return JSON.parse(Buffer.from(token.slice(prefix.length), "base64").toString("utf8"));
|
|
62
88
|
} catch {
|
|
63
89
|
throw new AutoConfigError();
|
|
64
90
|
}
|
|
91
|
+
}
|
|
65
92
|
|
|
93
|
+
export function decodeAutoconfigTokenV1(token: string): AutoConfigTokenContentV1 {
|
|
94
|
+
const parsed = parseTokenPayload(token, AUTOCONFIG_TOKEN_PREFIX_V1);
|
|
66
95
|
if (!isAutoConfigTokenContentV1(parsed)) {
|
|
67
96
|
throw new AutoConfigError();
|
|
68
97
|
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
69
100
|
|
|
101
|
+
export function decodeAutoconfigTokenV2(token: string): AutoConfigTokenContentV2 {
|
|
102
|
+
const parsed = parseTokenPayload(token, AUTOCONFIG_TOKEN_PREFIX_V2);
|
|
103
|
+
if (!isAutoConfigTokenContentV2(parsed)) {
|
|
104
|
+
throw new AutoConfigError();
|
|
105
|
+
}
|
|
70
106
|
return parsed;
|
|
71
107
|
}
|
|
72
108
|
|
|
109
|
+
export function decodeAutoconfigToken(
|
|
110
|
+
token: string
|
|
111
|
+
):
|
|
112
|
+
| { version: "v1"; content: AutoConfigTokenContentV1 }
|
|
113
|
+
| { version: "v2"; content: AutoConfigTokenContentV2 } {
|
|
114
|
+
if (token.startsWith(AUTOCONFIG_TOKEN_PREFIX_V1)) {
|
|
115
|
+
return { version: "v1", content: decodeAutoconfigTokenV1(token) };
|
|
116
|
+
}
|
|
117
|
+
if (token.startsWith(AUTOCONFIG_TOKEN_PREFIX_V2)) {
|
|
118
|
+
return { version: "v2", content: decodeAutoconfigTokenV2(token) };
|
|
119
|
+
}
|
|
120
|
+
throw new AutoConfigError(UNSUPPORTED_TOKEN_VERSION);
|
|
121
|
+
}
|
|
122
|
+
|
|
73
123
|
export class AutoConfig {
|
|
74
124
|
private readonly appId: string;
|
|
75
|
-
private readonly endpointId: string;
|
|
76
|
-
private readonly endpointIn: EndpointIn;
|
|
77
125
|
private readonly webhook: Webhook;
|
|
78
126
|
private readonly requestCtx: SvixRequestContext;
|
|
127
|
+
private readonly endpointIn: EndpointIn;
|
|
128
|
+
private readonly endpointId?: string;
|
|
129
|
+
private readonly autoconfigId?: string;
|
|
79
130
|
|
|
80
131
|
public constructor(token: string, endpoint: EndpointIn) {
|
|
81
|
-
const
|
|
132
|
+
const decoded = decodeAutoconfigToken(token);
|
|
133
|
+
|
|
82
134
|
let webhook: Webhook;
|
|
83
135
|
try {
|
|
84
|
-
webhook = new Webhook(content.esec);
|
|
136
|
+
webhook = new Webhook(decoded.content.esec);
|
|
85
137
|
} catch {
|
|
86
138
|
throw new AutoConfigError();
|
|
87
139
|
}
|
|
88
140
|
|
|
89
|
-
this.appId = content.aid;
|
|
90
|
-
this.endpointId = content.eid;
|
|
141
|
+
this.appId = decoded.content.aid;
|
|
91
142
|
this.endpointIn = endpoint;
|
|
92
143
|
this.webhook = webhook;
|
|
93
144
|
|
|
94
|
-
|
|
145
|
+
if (decoded.version === "v1") {
|
|
146
|
+
this.endpointId = decoded.content.eid;
|
|
147
|
+
} else {
|
|
148
|
+
this.autoconfigId = decoded.content.sid;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const svix = new SvixInternal(decoded.content.tok, {
|
|
152
|
+
serverUrl: decoded.content.surl,
|
|
153
|
+
});
|
|
95
154
|
this.requestCtx = svix.getRequestCtx();
|
|
96
155
|
}
|
|
97
156
|
|
|
98
157
|
public subscribe(): Promise<EndpointOut> {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
158
|
+
const endpoint = new InternalEndpoint(this.requestCtx);
|
|
159
|
+
if (this.autoconfigId != null) {
|
|
160
|
+
return endpoint.autoconfig.subscribe(
|
|
161
|
+
this.appId,
|
|
162
|
+
this.autoconfigId,
|
|
163
|
+
this.endpointIn
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return endpoint.autoConfigDeprecated.update(this.appId, this.endpointId as string, {
|
|
167
|
+
endpoint: this.endpointIn,
|
|
168
|
+
});
|
|
106
169
|
}
|
|
107
170
|
|
|
108
171
|
public verify(
|