@geekmidas/events 10.0.0-alpha.39 → 10.0.0-alpha.40
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.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +22 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/pgboss/index.cjs +47 -7
- package/dist/pgboss/index.cjs.map +1 -1
- package/dist/pgboss/index.d.cts +29 -3
- package/dist/pgboss/index.d.cts.map +1 -1
- package/dist/pgboss/index.d.mts +29 -3
- package/dist/pgboss/index.d.mts.map +1 -1
- package/dist/pgboss/index.mjs +46 -8
- package/dist/pgboss/index.mjs.map +1 -1
- package/dist/sns/index.cjs +265 -0
- package/dist/sns/index.cjs.map +1 -1
- package/dist/sns/index.d.cts +154 -1
- package/dist/sns/index.d.cts.map +1 -1
- package/dist/sns/index.d.mts +154 -1
- package/dist/sns/index.d.mts.map +1 -1
- package/dist/sns/index.mjs +254 -2
- package/dist/sns/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/sns/index.d.cts
CHANGED
|
@@ -36,6 +36,159 @@ export declare class SNSConnection implements EventConnection {
|
|
|
36
36
|
get topicArn(): string;
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region src/sns/push.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* SNS delivering to an HTTP(S) endpoint, rather than to a queue that is polled.
|
|
42
|
+
*
|
|
43
|
+
* SNS POSTs each message to the subscribed URL as JSON. That is the whole
|
|
44
|
+
* transport: no queue in between, no poller, and the endpoint is handed the
|
|
45
|
+
* same envelope a Lambda subscription receives inside `Records[].Sns` — so the
|
|
46
|
+
* handler that runs deployed can be the one that runs here.
|
|
47
|
+
*
|
|
48
|
+
* The endpoint is public, so a message is trusted only once its signature
|
|
49
|
+
* checks out against a certificate SNS itself serves. Anything else that POSTs
|
|
50
|
+
* there is somebody's guess at the shape.
|
|
51
|
+
*/
|
|
52
|
+
/** A message SNS POSTs to a subscribed HTTP endpoint. */
|
|
53
|
+
interface SnsHttpMessage {
|
|
54
|
+
Type: 'Notification' | 'SubscriptionConfirmation' | 'UnsubscribeConfirmation';
|
|
55
|
+
MessageId: string;
|
|
56
|
+
TopicArn: string;
|
|
57
|
+
Message: string;
|
|
58
|
+
Timestamp: string;
|
|
59
|
+
SignatureVersion: string;
|
|
60
|
+
Signature: string;
|
|
61
|
+
SigningCertURL: string;
|
|
62
|
+
Subject?: string;
|
|
63
|
+
/** Confirmation messages only. */
|
|
64
|
+
Token?: string;
|
|
65
|
+
/** Confirmation messages only: GET it to confirm. */
|
|
66
|
+
SubscribeURL?: string;
|
|
67
|
+
/** Notifications only. */
|
|
68
|
+
UnsubscribeURL?: string;
|
|
69
|
+
MessageAttributes?: Record<string, {
|
|
70
|
+
Type: string;
|
|
71
|
+
Value: string;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
interface VerifySnsMessageOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Fetch the PEM at a certificate URL. `fetch` by default; replaceable so a
|
|
77
|
+
* test signs with its own key instead of SNS's.
|
|
78
|
+
*/
|
|
79
|
+
fetchCertificate?: (url: string) => Promise<string>;
|
|
80
|
+
}
|
|
81
|
+
/** The string SNS signed for this message. */
|
|
82
|
+
export declare function snsStringToSign(message: SnsHttpMessage): string;
|
|
83
|
+
/**
|
|
84
|
+
* Check that SNS sent this message: the certificate is SNS's own, and the
|
|
85
|
+
* signature over the message's signed fields verifies against it.
|
|
86
|
+
*
|
|
87
|
+
* Throws rather than returning false, so a caller cannot forget to look.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifySnsMessage(message: SnsHttpMessage, options?: VerifySnsMessageOptions): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Confirm a subscription by visiting the URL SNS sent. Until this happens SNS
|
|
92
|
+
* delivers nothing to the endpoint.
|
|
93
|
+
*/
|
|
94
|
+
export declare function confirmSnsSubscription(message: SnsHttpMessage, request?: (url: string) => Promise<{
|
|
95
|
+
ok: boolean;
|
|
96
|
+
status: number;
|
|
97
|
+
}>): Promise<void>;
|
|
98
|
+
interface SubscribeHttpEndpointOptions {
|
|
99
|
+
/** The URL SNS POSTs to — `http:` or `https:`, which picks the protocol. */
|
|
100
|
+
endpoint: string;
|
|
101
|
+
/**
|
|
102
|
+
* The event types this endpoint takes. Becomes the subscription's filter
|
|
103
|
+
* policy on the `type` attribute `SNSPublisher` sets, so SNS sends nothing
|
|
104
|
+
* else — fan-out to every subscriber, each seeing only what it named.
|
|
105
|
+
*/
|
|
106
|
+
events: readonly string[];
|
|
107
|
+
/**
|
|
108
|
+
* An SQS queue's ARN for what SNS gives up delivering. SNS retries an HTTP
|
|
109
|
+
* endpoint for a limited time and then drops the message, so without one an
|
|
110
|
+
* outage longer than that loses events.
|
|
111
|
+
*/
|
|
112
|
+
deadLetterQueueArn?: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe an HTTP endpoint to the connection's topic, converging on one
|
|
116
|
+
* confirmed subscription with the filter asked for — so it is safe on every
|
|
117
|
+
* start:
|
|
118
|
+
*
|
|
119
|
+
* - **none yet** — subscribed; SNS sends a confirmation to the endpoint.
|
|
120
|
+
* - **confirmed** — its filter and dead-letter queue are brought up to date.
|
|
121
|
+
* Subscribing again with different attributes is an error on SNS, and the
|
|
122
|
+
* events a subscriber names change.
|
|
123
|
+
* - **stuck pending** — a confirmation that never landed (the endpoint was
|
|
124
|
+
* down, or answered wrong). Subscribing the same endpoint again is a no-op
|
|
125
|
+
* on an emulator, so it is dropped and subscribed afresh, which sends a new
|
|
126
|
+
* confirmation.
|
|
127
|
+
*/
|
|
128
|
+
export declare function subscribeHttpEndpoint(connection: SNSConnection, options: SubscribeHttpEndpointOptions): Promise<string | undefined>;
|
|
129
|
+
/**
|
|
130
|
+
* A pushed notification as the Lambda event SNS would have invoked a function
|
|
131
|
+
* with, so it can be handed to the same adaptor.
|
|
132
|
+
*/
|
|
133
|
+
export declare function toSnsEvent(message: SnsHttpMessage, subscriptionArn?: string): {
|
|
134
|
+
Records: {
|
|
135
|
+
EventSource: string;
|
|
136
|
+
EventVersion: string;
|
|
137
|
+
EventSubscriptionArn: string;
|
|
138
|
+
Sns: {
|
|
139
|
+
Type: "Notification" | "SubscriptionConfirmation" | "UnsubscribeConfirmation";
|
|
140
|
+
MessageId: string;
|
|
141
|
+
TopicArn: string;
|
|
142
|
+
Subject: string | null;
|
|
143
|
+
Message: string;
|
|
144
|
+
Timestamp: string;
|
|
145
|
+
SignatureVersion: string;
|
|
146
|
+
Signature: string;
|
|
147
|
+
SigningCertUrl: string;
|
|
148
|
+
UnsubscribeUrl: string;
|
|
149
|
+
MessageAttributes: Record<string, {
|
|
150
|
+
Type: string;
|
|
151
|
+
Value: string;
|
|
152
|
+
}>;
|
|
153
|
+
};
|
|
154
|
+
}[];
|
|
155
|
+
};
|
|
156
|
+
export declare class SnsCertificateUntrusted extends Error {
|
|
157
|
+
readonly url: string;
|
|
158
|
+
constructor(url: string);
|
|
159
|
+
}
|
|
160
|
+
export declare class SnsCertificateUnavailable extends Error {
|
|
161
|
+
readonly url: string;
|
|
162
|
+
readonly status: number;
|
|
163
|
+
constructor(url: string, status: number);
|
|
164
|
+
}
|
|
165
|
+
export declare class SnsSignatureVersionUnknown extends Error {
|
|
166
|
+
readonly version: string;
|
|
167
|
+
constructor(version: string);
|
|
168
|
+
}
|
|
169
|
+
export declare class SnsSignatureInvalid extends Error {
|
|
170
|
+
readonly messageId: string;
|
|
171
|
+
readonly topicArn: string;
|
|
172
|
+
constructor(messageId: string, topicArn: string);
|
|
173
|
+
}
|
|
174
|
+
export declare class SnsMessageTypeUnknown extends Error {
|
|
175
|
+
readonly type: string;
|
|
176
|
+
constructor(type: string);
|
|
177
|
+
}
|
|
178
|
+
export declare class SnsNotAConfirmation extends Error {
|
|
179
|
+
readonly type: string;
|
|
180
|
+
constructor(type: string);
|
|
181
|
+
}
|
|
182
|
+
export declare class SnsConfirmationFailed extends Error {
|
|
183
|
+
readonly topicArn: string;
|
|
184
|
+
readonly status: number;
|
|
185
|
+
constructor(topicArn: string, status: number);
|
|
186
|
+
}
|
|
187
|
+
export declare class SnsEndpointNotHttp extends Error {
|
|
188
|
+
readonly endpoint: string;
|
|
189
|
+
constructor(endpoint: string);
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
39
192
|
//#region src/sns/SNSPublisher.d.ts
|
|
40
193
|
type SNSPublisherOptions = {};
|
|
41
194
|
export declare class SNSPublisher<TMessage extends PublishableMessage<string, any>> implements EventPublisher<TMessage> {
|
|
@@ -108,5 +261,5 @@ export declare class SnsQueueMissing extends Error {
|
|
|
108
261
|
constructor(queueName: string | undefined);
|
|
109
262
|
}
|
|
110
263
|
//#endregion
|
|
111
|
-
export { type SNSConnectionConfig, type SNSPublisherOptions, type SNSSubscriberOptions, type SnsAddress, snsUrl_d_exports as snsUrl };
|
|
264
|
+
export { type SNSConnectionConfig, type SNSPublisherOptions, type SNSSubscriberOptions, type SnsAddress, type SnsHttpMessage, type SubscribeHttpEndpointOptions, type VerifySnsMessageOptions, snsUrl_d_exports as snsUrl };
|
|
112
265
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/sns/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/sns/SNSConnection.ts","../../src/sns/SNSPublisher.ts","../../src/sns/SNSSubscriber.ts"],"mappings":";;;;UAKiB;EAChB;EACA;EACA;EACA;IACC;IACA;IACA;;;qBAIW,yBAAyB;WAC5B,OAAI,mBAAA;UACL;UACA;UACA;EAER,YAAY,QAAQ;;;;;SAgBP,qBACZ,2BACE,QAAQ;EA8BL,WAAW;EAKX,SAAS;EAKf;;;;MAOI,aAAa;;;;MAOb;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/sns/SNSConnection.ts","../../src/sns/push.ts","../../src/sns/SNSPublisher.ts","../../src/sns/SNSSubscriber.ts"],"mappings":";;;;UAKiB;EAChB;EACA;EACA;EACA;IACC;IACA;IACA;;;qBAIW,yBAAyB;WAC5B,OAAI,mBAAA;UACL;UACA;UACA;EAER,YAAY,QAAQ;;;;;SAgBP,qBACZ,2BACE,QAAQ;EA8BL,WAAW;EAKX,SAAS;EAKf;;;;MAOI,aAAa;;;;MAOb;;;;;;;;;;;;;;;;;UCrEY;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;EACA,oBAAoB;IAAiB;IAAc;;;UAwCnC;;;;;EAKhB,oBAAoB,gBAAgB;;;wBAwBrB,gBAAgB,SAAS;;;;;;;wBAgBnB,iBACrB,SAAS,gBACT,UAAS,0BACP;;;;;wBAsCmB,uBACrB,SAAS,gBACT,WAAU,gBAAgB;EAAU;EAAa;KAC/C;UAWc;;EAEhB;;;;;;EAMA;;;;;;EAMA;;;;;;;;;;;;;;;;wBAiBqB,sBACrB,YAAY,eACZ,SAAS,+BACP;;;;;wBAiFa,WAAW,SAAS,gBAAgB;;;;;;MAQ/C;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MAEA;MACA;MACA,mBAAiB;QAlRsB;QAAe;;;;;qBAyR9C,gCAAgC;WACvB;EAArB,YAAqB;;qBAUT,kCAAkC;WAEpC;WACA;EAFV,YACU,aACA;;qBAUE,mCAAmC;WAC1B;EAArB,YAAqB;;qBAST,4BAA4B;WAE9B;WACA;EAFV,YACU,mBACA;;qBAWE,8BAA8B;WACrB;EAArB,YAAqB;;qBAST,4BAA4B;WACnB;EAArB,YAAqB;;qBAST,8BAA8B;WAEhC;WACA;EAFV,YACU,kBACA;;qBAWE,2BAA2B;WAClB;EAArB,YAAqB;;;;KCjZV;qBAEC,aAAa,iBAAiB,4CAC/B,eAAe;UAGjB;EADT,YACS,YAAY,eACpB,WAAU;;;;;SAOE,qBACZ,iBAAiB,iCAChB,2BAA2B,QAAQ,aAAa;EAQ5C,QAAQ,UAAU,aAAa;UAWvB;EAkBR,SAAS;;;;UC5CC;EAChB;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;qBAoBY,cAAc,iBAAiB,4CAChC,gBAAgB;UAUlB;UARD;UACA;UACA;UACA;UACA;UACA;EAER,YACS,YAAY,eACpB,UAAS;;;;;SAwBG,qBACZ,iBAAiB,iCAChB,2BAA2B,QAAQ,cAAc;EAoB7C,UACL,UAAU,oBACV,WAAW,SAAS,aAAa,gBAC/B;;;;EA2BG,QAAQ;UA6BA;UAwBA;UA4BA;;;;;;qBAgBF,wBAAwB;WACf;EAArB,YAAqB"}
|
package/dist/sns/index.d.mts
CHANGED
|
@@ -36,6 +36,159 @@ export declare class SNSConnection implements EventConnection {
|
|
|
36
36
|
get topicArn(): string;
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region src/sns/push.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* SNS delivering to an HTTP(S) endpoint, rather than to a queue that is polled.
|
|
42
|
+
*
|
|
43
|
+
* SNS POSTs each message to the subscribed URL as JSON. That is the whole
|
|
44
|
+
* transport: no queue in between, no poller, and the endpoint is handed the
|
|
45
|
+
* same envelope a Lambda subscription receives inside `Records[].Sns` — so the
|
|
46
|
+
* handler that runs deployed can be the one that runs here.
|
|
47
|
+
*
|
|
48
|
+
* The endpoint is public, so a message is trusted only once its signature
|
|
49
|
+
* checks out against a certificate SNS itself serves. Anything else that POSTs
|
|
50
|
+
* there is somebody's guess at the shape.
|
|
51
|
+
*/
|
|
52
|
+
/** A message SNS POSTs to a subscribed HTTP endpoint. */
|
|
53
|
+
interface SnsHttpMessage {
|
|
54
|
+
Type: 'Notification' | 'SubscriptionConfirmation' | 'UnsubscribeConfirmation';
|
|
55
|
+
MessageId: string;
|
|
56
|
+
TopicArn: string;
|
|
57
|
+
Message: string;
|
|
58
|
+
Timestamp: string;
|
|
59
|
+
SignatureVersion: string;
|
|
60
|
+
Signature: string;
|
|
61
|
+
SigningCertURL: string;
|
|
62
|
+
Subject?: string;
|
|
63
|
+
/** Confirmation messages only. */
|
|
64
|
+
Token?: string;
|
|
65
|
+
/** Confirmation messages only: GET it to confirm. */
|
|
66
|
+
SubscribeURL?: string;
|
|
67
|
+
/** Notifications only. */
|
|
68
|
+
UnsubscribeURL?: string;
|
|
69
|
+
MessageAttributes?: Record<string, {
|
|
70
|
+
Type: string;
|
|
71
|
+
Value: string;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
interface VerifySnsMessageOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Fetch the PEM at a certificate URL. `fetch` by default; replaceable so a
|
|
77
|
+
* test signs with its own key instead of SNS's.
|
|
78
|
+
*/
|
|
79
|
+
fetchCertificate?: (url: string) => Promise<string>;
|
|
80
|
+
}
|
|
81
|
+
/** The string SNS signed for this message. */
|
|
82
|
+
export declare function snsStringToSign(message: SnsHttpMessage): string;
|
|
83
|
+
/**
|
|
84
|
+
* Check that SNS sent this message: the certificate is SNS's own, and the
|
|
85
|
+
* signature over the message's signed fields verifies against it.
|
|
86
|
+
*
|
|
87
|
+
* Throws rather than returning false, so a caller cannot forget to look.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifySnsMessage(message: SnsHttpMessage, options?: VerifySnsMessageOptions): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Confirm a subscription by visiting the URL SNS sent. Until this happens SNS
|
|
92
|
+
* delivers nothing to the endpoint.
|
|
93
|
+
*/
|
|
94
|
+
export declare function confirmSnsSubscription(message: SnsHttpMessage, request?: (url: string) => Promise<{
|
|
95
|
+
ok: boolean;
|
|
96
|
+
status: number;
|
|
97
|
+
}>): Promise<void>;
|
|
98
|
+
interface SubscribeHttpEndpointOptions {
|
|
99
|
+
/** The URL SNS POSTs to — `http:` or `https:`, which picks the protocol. */
|
|
100
|
+
endpoint: string;
|
|
101
|
+
/**
|
|
102
|
+
* The event types this endpoint takes. Becomes the subscription's filter
|
|
103
|
+
* policy on the `type` attribute `SNSPublisher` sets, so SNS sends nothing
|
|
104
|
+
* else — fan-out to every subscriber, each seeing only what it named.
|
|
105
|
+
*/
|
|
106
|
+
events: readonly string[];
|
|
107
|
+
/**
|
|
108
|
+
* An SQS queue's ARN for what SNS gives up delivering. SNS retries an HTTP
|
|
109
|
+
* endpoint for a limited time and then drops the message, so without one an
|
|
110
|
+
* outage longer than that loses events.
|
|
111
|
+
*/
|
|
112
|
+
deadLetterQueueArn?: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe an HTTP endpoint to the connection's topic, converging on one
|
|
116
|
+
* confirmed subscription with the filter asked for — so it is safe on every
|
|
117
|
+
* start:
|
|
118
|
+
*
|
|
119
|
+
* - **none yet** — subscribed; SNS sends a confirmation to the endpoint.
|
|
120
|
+
* - **confirmed** — its filter and dead-letter queue are brought up to date.
|
|
121
|
+
* Subscribing again with different attributes is an error on SNS, and the
|
|
122
|
+
* events a subscriber names change.
|
|
123
|
+
* - **stuck pending** — a confirmation that never landed (the endpoint was
|
|
124
|
+
* down, or answered wrong). Subscribing the same endpoint again is a no-op
|
|
125
|
+
* on an emulator, so it is dropped and subscribed afresh, which sends a new
|
|
126
|
+
* confirmation.
|
|
127
|
+
*/
|
|
128
|
+
export declare function subscribeHttpEndpoint(connection: SNSConnection, options: SubscribeHttpEndpointOptions): Promise<string | undefined>;
|
|
129
|
+
/**
|
|
130
|
+
* A pushed notification as the Lambda event SNS would have invoked a function
|
|
131
|
+
* with, so it can be handed to the same adaptor.
|
|
132
|
+
*/
|
|
133
|
+
export declare function toSnsEvent(message: SnsHttpMessage, subscriptionArn?: string): {
|
|
134
|
+
Records: {
|
|
135
|
+
EventSource: string;
|
|
136
|
+
EventVersion: string;
|
|
137
|
+
EventSubscriptionArn: string;
|
|
138
|
+
Sns: {
|
|
139
|
+
Type: "Notification" | "SubscriptionConfirmation" | "UnsubscribeConfirmation";
|
|
140
|
+
MessageId: string;
|
|
141
|
+
TopicArn: string;
|
|
142
|
+
Subject: string | null;
|
|
143
|
+
Message: string;
|
|
144
|
+
Timestamp: string;
|
|
145
|
+
SignatureVersion: string;
|
|
146
|
+
Signature: string;
|
|
147
|
+
SigningCertUrl: string;
|
|
148
|
+
UnsubscribeUrl: string;
|
|
149
|
+
MessageAttributes: Record<string, {
|
|
150
|
+
Type: string;
|
|
151
|
+
Value: string;
|
|
152
|
+
}>;
|
|
153
|
+
};
|
|
154
|
+
}[];
|
|
155
|
+
};
|
|
156
|
+
export declare class SnsCertificateUntrusted extends Error {
|
|
157
|
+
readonly url: string;
|
|
158
|
+
constructor(url: string);
|
|
159
|
+
}
|
|
160
|
+
export declare class SnsCertificateUnavailable extends Error {
|
|
161
|
+
readonly url: string;
|
|
162
|
+
readonly status: number;
|
|
163
|
+
constructor(url: string, status: number);
|
|
164
|
+
}
|
|
165
|
+
export declare class SnsSignatureVersionUnknown extends Error {
|
|
166
|
+
readonly version: string;
|
|
167
|
+
constructor(version: string);
|
|
168
|
+
}
|
|
169
|
+
export declare class SnsSignatureInvalid extends Error {
|
|
170
|
+
readonly messageId: string;
|
|
171
|
+
readonly topicArn: string;
|
|
172
|
+
constructor(messageId: string, topicArn: string);
|
|
173
|
+
}
|
|
174
|
+
export declare class SnsMessageTypeUnknown extends Error {
|
|
175
|
+
readonly type: string;
|
|
176
|
+
constructor(type: string);
|
|
177
|
+
}
|
|
178
|
+
export declare class SnsNotAConfirmation extends Error {
|
|
179
|
+
readonly type: string;
|
|
180
|
+
constructor(type: string);
|
|
181
|
+
}
|
|
182
|
+
export declare class SnsConfirmationFailed extends Error {
|
|
183
|
+
readonly topicArn: string;
|
|
184
|
+
readonly status: number;
|
|
185
|
+
constructor(topicArn: string, status: number);
|
|
186
|
+
}
|
|
187
|
+
export declare class SnsEndpointNotHttp extends Error {
|
|
188
|
+
readonly endpoint: string;
|
|
189
|
+
constructor(endpoint: string);
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
39
192
|
//#region src/sns/SNSPublisher.d.ts
|
|
40
193
|
type SNSPublisherOptions = {};
|
|
41
194
|
export declare class SNSPublisher<TMessage extends PublishableMessage<string, any>> implements EventPublisher<TMessage> {
|
|
@@ -108,5 +261,5 @@ export declare class SnsQueueMissing extends Error {
|
|
|
108
261
|
constructor(queueName: string | undefined);
|
|
109
262
|
}
|
|
110
263
|
//#endregion
|
|
111
|
-
export { type SNSConnectionConfig, type SNSPublisherOptions, type SNSSubscriberOptions, type SnsAddress, snsUrl_d_exports as snsUrl };
|
|
264
|
+
export { type SNSConnectionConfig, type SNSPublisherOptions, type SNSSubscriberOptions, type SnsAddress, type SnsHttpMessage, type SubscribeHttpEndpointOptions, type VerifySnsMessageOptions, snsUrl_d_exports as snsUrl };
|
|
112
265
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/sns/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/sns/SNSConnection.ts","../../src/sns/SNSPublisher.ts","../../src/sns/SNSSubscriber.ts"],"mappings":";;;;UAKiB;EAChB;EACA;EACA;EACA;IACC;IACA;IACA;;;qBAIW,yBAAyB;WAC5B,OAAI,mBAAA;UACL;UACA;UACA;EAER,YAAY,QAAQ;;;;;SAgBP,qBACZ,2BACE,QAAQ;EA8BL,WAAW;EAKX,SAAS;EAKf;;;;MAOI,aAAa;;;;MAOb;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/sns/SNSConnection.ts","../../src/sns/push.ts","../../src/sns/SNSPublisher.ts","../../src/sns/SNSSubscriber.ts"],"mappings":";;;;UAKiB;EAChB;EACA;EACA;EACA;IACC;IACA;IACA;;;qBAIW,yBAAyB;WAC5B,OAAI,mBAAA;UACL;UACA;UACA;EAER,YAAY,QAAQ;;;;;SAgBP,qBACZ,2BACE,QAAQ;EA8BL,WAAW;EAKX,SAAS;EAKf;;;;MAOI,aAAa;;;;MAOb;;;;;;;;;;;;;;;;;UCrEY;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;EACA,oBAAoB;IAAiB;IAAc;;;UAwCnC;;;;;EAKhB,oBAAoB,gBAAgB;;;wBAwBrB,gBAAgB,SAAS;;;;;;;wBAgBnB,iBACrB,SAAS,gBACT,UAAS,0BACP;;;;;wBAsCmB,uBACrB,SAAS,gBACT,WAAU,gBAAgB;EAAU;EAAa;KAC/C;UAWc;;EAEhB;;;;;;EAMA;;;;;;EAMA;;;;;;;;;;;;;;;;wBAiBqB,sBACrB,YAAY,eACZ,SAAS,+BACP;;;;;wBAiFa,WAAW,SAAS,gBAAgB;;;;;;MAQ/C;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MAEA;MACA;MACA,mBAAiB;QAlRsB;QAAe;;;;;qBAyR9C,gCAAgC;WACvB;EAArB,YAAqB;;qBAUT,kCAAkC;WAEpC;WACA;EAFV,YACU,aACA;;qBAUE,mCAAmC;WAC1B;EAArB,YAAqB;;qBAST,4BAA4B;WAE9B;WACA;EAFV,YACU,mBACA;;qBAWE,8BAA8B;WACrB;EAArB,YAAqB;;qBAST,4BAA4B;WACnB;EAArB,YAAqB;;qBAST,8BAA8B;WAEhC;WACA;EAFV,YACU,kBACA;;qBAWE,2BAA2B;WAClB;EAArB,YAAqB;;;;KCjZV;qBAEC,aAAa,iBAAiB,4CAC/B,eAAe;UAGjB;EADT,YACS,YAAY,eACpB,WAAU;;;;;SAOE,qBACZ,iBAAiB,iCAChB,2BAA2B,QAAQ,aAAa;EAQ5C,QAAQ,UAAU,aAAa;UAWvB;EAkBR,SAAS;;;;UC5CC;EAChB;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;qBAoBY,cAAc,iBAAiB,4CAChC,gBAAgB;UAUlB;UARD;UACA;UACA;UACA;UACA;UACA;EAER,YACS,YAAY,eACpB,UAAS;;;;;SAwBG,qBACZ,iBAAiB,iCAChB,2BAA2B,QAAQ,cAAc;EAoB7C,UACL,UAAU,oBACV,WAAW,SAAS,aAAa,gBAC/B;;;;EA2BG,QAAQ;UA6BA;UAwBA;UA4BA;;;;;;qBAgBF,wBAAwB;WACf;EAArB,YAAqB"}
|
package/dist/sns/index.mjs
CHANGED
|
@@ -2,7 +2,259 @@ import "../types-Bk5JM6MP.mjs";
|
|
|
2
2
|
import { n as SQSConnection, t as SQSSubscriber } from "../SQSSubscriber-BXLE_1L3.mjs";
|
|
3
3
|
import { parse, t as snsUrl_exports } from "./snsUrl.mjs";
|
|
4
4
|
import { CreateQueueCommand, DeleteQueueCommand, GetQueueAttributesCommand, SQSClient, SetQueueAttributesCommand } from "@aws-sdk/client-sqs";
|
|
5
|
-
import {
|
|
5
|
+
import { createVerify } from "node:crypto";
|
|
6
|
+
import { GetSubscriptionAttributesCommand, ListSubscriptionsByTopicCommand, PublishCommand, SNSClient, SetSubscriptionAttributesCommand, SubscribeCommand, UnsubscribeCommand } from "@aws-sdk/client-sns";
|
|
7
|
+
//#region src/sns/push.ts
|
|
8
|
+
/** The fields signed, in the order SNS signs them, per message type. */
|
|
9
|
+
const SIGNED_FIELDS = {
|
|
10
|
+
Notification: [
|
|
11
|
+
"Message",
|
|
12
|
+
"MessageId",
|
|
13
|
+
"Subject",
|
|
14
|
+
"Timestamp",
|
|
15
|
+
"TopicArn",
|
|
16
|
+
"Type"
|
|
17
|
+
],
|
|
18
|
+
SubscriptionConfirmation: [
|
|
19
|
+
"Message",
|
|
20
|
+
"MessageId",
|
|
21
|
+
"SubscribeURL",
|
|
22
|
+
"Timestamp",
|
|
23
|
+
"Token",
|
|
24
|
+
"TopicArn",
|
|
25
|
+
"Type"
|
|
26
|
+
],
|
|
27
|
+
UnsubscribeConfirmation: [
|
|
28
|
+
"Message",
|
|
29
|
+
"MessageId",
|
|
30
|
+
"SubscribeURL",
|
|
31
|
+
"Timestamp",
|
|
32
|
+
"Token",
|
|
33
|
+
"TopicArn",
|
|
34
|
+
"Type"
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Where SNS serves its signing certificates. Anywhere else is a certificate
|
|
39
|
+
* the sender chose, which would let the sender sign its own forgeries.
|
|
40
|
+
*/
|
|
41
|
+
const SNS_CERTIFICATE_HOST = /^sns\.[a-z0-9-]+\.amazonaws\.com(\.cn)?$/;
|
|
42
|
+
const certificates = /* @__PURE__ */ new Map();
|
|
43
|
+
async function defaultFetchCertificate(url) {
|
|
44
|
+
const response = await fetch(url);
|
|
45
|
+
if (!response.ok) throw new SnsCertificateUnavailable(url, response.status);
|
|
46
|
+
return response.text();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A URL, or undefined for a value that is not one at all — what an emulator
|
|
50
|
+
* sends — so it is untrusted rather than a crash. (`URL.parse` is Node 22.1+.)
|
|
51
|
+
*/
|
|
52
|
+
function parseUrl(value) {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(String(value));
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** The string SNS signed for this message. */
|
|
60
|
+
function snsStringToSign(message) {
|
|
61
|
+
const fields = SIGNED_FIELDS[message.Type];
|
|
62
|
+
if (!fields) throw new SnsMessageTypeUnknown(String(message.Type));
|
|
63
|
+
return fields.filter((field) => message[field] !== void 0).map((field) => `${field}\n${message[field]}\n`).join("");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check that SNS sent this message: the certificate is SNS's own, and the
|
|
67
|
+
* signature over the message's signed fields verifies against it.
|
|
68
|
+
*
|
|
69
|
+
* Throws rather than returning false, so a caller cannot forget to look.
|
|
70
|
+
*/
|
|
71
|
+
async function verifySnsMessage(message, options = {}) {
|
|
72
|
+
const url = parseUrl(message.SigningCertURL);
|
|
73
|
+
if (url?.protocol !== "https:" || !SNS_CERTIFICATE_HOST.test(url.hostname)) throw new SnsCertificateUntrusted(message.SigningCertURL);
|
|
74
|
+
const algorithm = message.SignatureVersion === "1" ? "RSA-SHA1" : message.SignatureVersion === "2" ? "RSA-SHA256" : void 0;
|
|
75
|
+
if (!algorithm) throw new SnsSignatureVersionUnknown(message.SignatureVersion);
|
|
76
|
+
const fetchCertificate = options.fetchCertificate ?? defaultFetchCertificate;
|
|
77
|
+
let certificate = certificates.get(message.SigningCertURL);
|
|
78
|
+
if (!certificate || options.fetchCertificate) {
|
|
79
|
+
certificate = fetchCertificate(message.SigningCertURL);
|
|
80
|
+
if (!options.fetchCertificate) {
|
|
81
|
+
certificates.set(message.SigningCertURL, certificate);
|
|
82
|
+
certificate.catch(() => certificates.delete(message.SigningCertURL));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const verifier = createVerify(algorithm);
|
|
86
|
+
verifier.update(snsStringToSign(message), "utf8");
|
|
87
|
+
if (!verifier.verify(await certificate, message.Signature, "base64")) throw new SnsSignatureInvalid(message.MessageId, message.TopicArn);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Confirm a subscription by visiting the URL SNS sent. Until this happens SNS
|
|
91
|
+
* delivers nothing to the endpoint.
|
|
92
|
+
*/
|
|
93
|
+
async function confirmSnsSubscription(message, request = fetch) {
|
|
94
|
+
if (message.Type !== "SubscriptionConfirmation" || !message.SubscribeURL) throw new SnsNotAConfirmation(message.Type);
|
|
95
|
+
const response = await request(message.SubscribeURL);
|
|
96
|
+
if (!response.ok) throw new SnsConfirmationFailed(message.TopicArn, response.status);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Subscribe an HTTP endpoint to the connection's topic, converging on one
|
|
100
|
+
* confirmed subscription with the filter asked for — so it is safe on every
|
|
101
|
+
* start:
|
|
102
|
+
*
|
|
103
|
+
* - **none yet** — subscribed; SNS sends a confirmation to the endpoint.
|
|
104
|
+
* - **confirmed** — its filter and dead-letter queue are brought up to date.
|
|
105
|
+
* Subscribing again with different attributes is an error on SNS, and the
|
|
106
|
+
* events a subscriber names change.
|
|
107
|
+
* - **stuck pending** — a confirmation that never landed (the endpoint was
|
|
108
|
+
* down, or answered wrong). Subscribing the same endpoint again is a no-op
|
|
109
|
+
* on an emulator, so it is dropped and subscribed afresh, which sends a new
|
|
110
|
+
* confirmation.
|
|
111
|
+
*/
|
|
112
|
+
async function subscribeHttpEndpoint(connection, options) {
|
|
113
|
+
const protocol = new URL(options.endpoint).protocol.replace(":", "");
|
|
114
|
+
if (protocol !== "http" && protocol !== "https") throw new SnsEndpointNotHttp(options.endpoint);
|
|
115
|
+
const attributes = {
|
|
116
|
+
FilterPolicyScope: "MessageAttributes",
|
|
117
|
+
FilterPolicy: JSON.stringify({ type: [...options.events] })
|
|
118
|
+
};
|
|
119
|
+
if (options.deadLetterQueueArn) attributes.RedrivePolicy = JSON.stringify({ deadLetterTargetArn: options.deadLetterQueueArn });
|
|
120
|
+
const client = connection.snsClient;
|
|
121
|
+
const existing = await existingSubscription(connection, options.endpoint);
|
|
122
|
+
if (existing) {
|
|
123
|
+
const { Attributes = {} } = await client.send(new GetSubscriptionAttributesCommand({ SubscriptionArn: existing }));
|
|
124
|
+
if (Attributes.PendingConfirmation === "true") await client.send(new UnsubscribeCommand({ SubscriptionArn: existing }));
|
|
125
|
+
else {
|
|
126
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
127
|
+
if (Attributes[name] === value) continue;
|
|
128
|
+
await client.send(new SetSubscriptionAttributesCommand({
|
|
129
|
+
SubscriptionArn: existing,
|
|
130
|
+
AttributeName: name,
|
|
131
|
+
AttributeValue: value
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
return existing;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const { SubscriptionArn } = await client.send(new SubscribeCommand({
|
|
138
|
+
TopicArn: connection.topicArn,
|
|
139
|
+
Protocol: protocol,
|
|
140
|
+
Endpoint: options.endpoint,
|
|
141
|
+
Attributes: attributes,
|
|
142
|
+
ReturnSubscriptionArn: true
|
|
143
|
+
}));
|
|
144
|
+
return SubscriptionArn;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The ARN of the endpoint's subscription to the topic, if it has one SNS will
|
|
148
|
+
* name. (Real SNS lists a pending one as `PendingConfirmation`, with no ARN to
|
|
149
|
+
* act on — and resends its confirmation on a new Subscribe anyway.)
|
|
150
|
+
*/
|
|
151
|
+
async function existingSubscription(connection, endpoint) {
|
|
152
|
+
let NextToken;
|
|
153
|
+
do {
|
|
154
|
+
const page = await connection.snsClient.send(new ListSubscriptionsByTopicCommand({
|
|
155
|
+
TopicArn: connection.topicArn,
|
|
156
|
+
NextToken
|
|
157
|
+
}));
|
|
158
|
+
const match = page.Subscriptions?.find((s) => s.Endpoint === endpoint && s.SubscriptionArn?.startsWith("arn:"));
|
|
159
|
+
if (match) return match.SubscriptionArn;
|
|
160
|
+
NextToken = page.NextToken;
|
|
161
|
+
} while (NextToken);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A pushed notification as the Lambda event SNS would have invoked a function
|
|
165
|
+
* with, so it can be handed to the same adaptor.
|
|
166
|
+
*/
|
|
167
|
+
function toSnsEvent(message, subscriptionArn = "") {
|
|
168
|
+
return { Records: [{
|
|
169
|
+
EventSource: "aws:sns",
|
|
170
|
+
EventVersion: "1.0",
|
|
171
|
+
EventSubscriptionArn: subscriptionArn,
|
|
172
|
+
Sns: {
|
|
173
|
+
Type: message.Type,
|
|
174
|
+
MessageId: message.MessageId,
|
|
175
|
+
TopicArn: message.TopicArn,
|
|
176
|
+
Subject: message.Subject ?? null,
|
|
177
|
+
Message: message.Message,
|
|
178
|
+
Timestamp: message.Timestamp,
|
|
179
|
+
SignatureVersion: message.SignatureVersion,
|
|
180
|
+
Signature: message.Signature,
|
|
181
|
+
SigningCertUrl: message.SigningCertURL,
|
|
182
|
+
UnsubscribeUrl: message.UnsubscribeURL ?? "",
|
|
183
|
+
MessageAttributes: message.MessageAttributes ?? {}
|
|
184
|
+
}
|
|
185
|
+
}] };
|
|
186
|
+
}
|
|
187
|
+
var SnsCertificateUntrusted = class extends Error {
|
|
188
|
+
url;
|
|
189
|
+
constructor(url) {
|
|
190
|
+
super(`The SNS message names a signing certificate at '${url}', which is not an https URL on sns.<region>.amazonaws.com. Only SNS can send to this endpoint; reject the request.`);
|
|
191
|
+
this.url = url;
|
|
192
|
+
this.name = "SnsCertificateUntrusted";
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var SnsCertificateUnavailable = class extends Error {
|
|
196
|
+
url;
|
|
197
|
+
status;
|
|
198
|
+
constructor(url, status) {
|
|
199
|
+
super(`Fetching the SNS signing certificate at '${url}' answered ${status}. The message cannot be verified, so it is not handled; SNS will retry.`);
|
|
200
|
+
this.url = url;
|
|
201
|
+
this.status = status;
|
|
202
|
+
this.name = "SnsCertificateUnavailable";
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var SnsSignatureVersionUnknown = class extends Error {
|
|
206
|
+
version;
|
|
207
|
+
constructor(version) {
|
|
208
|
+
super(`SNS signature version '${version}' is not one this endpoint verifies (it knows '1' and '2'). Reject the request.`);
|
|
209
|
+
this.version = version;
|
|
210
|
+
this.name = "SnsSignatureVersionUnknown";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var SnsSignatureInvalid = class extends Error {
|
|
214
|
+
messageId;
|
|
215
|
+
topicArn;
|
|
216
|
+
constructor(messageId, topicArn) {
|
|
217
|
+
super(`SNS message '${messageId}' from '${topicArn}' does not verify against its signing certificate. It was not sent by SNS, or was altered on the way; reject it.`);
|
|
218
|
+
this.messageId = messageId;
|
|
219
|
+
this.topicArn = topicArn;
|
|
220
|
+
this.name = "SnsSignatureInvalid";
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var SnsMessageTypeUnknown = class extends Error {
|
|
224
|
+
type;
|
|
225
|
+
constructor(type) {
|
|
226
|
+
super(`'${type}' is not an SNS message type (Notification, SubscriptionConfirmation, UnsubscribeConfirmation). Reject it.`);
|
|
227
|
+
this.type = type;
|
|
228
|
+
this.name = "SnsMessageTypeUnknown";
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var SnsNotAConfirmation = class extends Error {
|
|
232
|
+
type;
|
|
233
|
+
constructor(type) {
|
|
234
|
+
super(`Only a SubscriptionConfirmation carries a URL to confirm; this is a '${type}'.`);
|
|
235
|
+
this.type = type;
|
|
236
|
+
this.name = "SnsNotAConfirmation";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var SnsConfirmationFailed = class extends Error {
|
|
240
|
+
topicArn;
|
|
241
|
+
status;
|
|
242
|
+
constructor(topicArn, status) {
|
|
243
|
+
super(`Confirming the subscription to '${topicArn}' answered ${status}. SNS will deliver nothing until it is confirmed; it resends the confirmation, or subscribe the endpoint again.`);
|
|
244
|
+
this.topicArn = topicArn;
|
|
245
|
+
this.status = status;
|
|
246
|
+
this.name = "SnsConfirmationFailed";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var SnsEndpointNotHttp = class extends Error {
|
|
250
|
+
endpoint;
|
|
251
|
+
constructor(endpoint) {
|
|
252
|
+
super(`'${endpoint}' is not an http(s) URL. SNS pushes to an HTTP endpoint; subscribe a queue for anything else.`);
|
|
253
|
+
this.endpoint = endpoint;
|
|
254
|
+
this.name = "SnsEndpointNotHttp";
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
//#endregion
|
|
6
258
|
//#region src/sns/SNSConnection.ts
|
|
7
259
|
var SNSConnection = class SNSConnection {
|
|
8
260
|
type = "sns";
|
|
@@ -245,6 +497,6 @@ var SnsQueueMissing = class extends Error {
|
|
|
245
497
|
}
|
|
246
498
|
};
|
|
247
499
|
//#endregion
|
|
248
|
-
export { SNSConnection, SNSPublisher, SNSSubscriber, SnsQueueMissing, snsUrl_exports as snsUrl };
|
|
500
|
+
export { SNSConnection, SNSPublisher, SNSSubscriber, SnsCertificateUnavailable, SnsCertificateUntrusted, SnsConfirmationFailed, SnsEndpointNotHttp, SnsMessageTypeUnknown, SnsNotAConfirmation, SnsQueueMissing, SnsSignatureInvalid, SnsSignatureVersionUnknown, confirmSnsSubscription, snsStringToSign, snsUrl_exports as snsUrl, subscribeHttpEndpoint, toSnsEvent, verifySnsMessage };
|
|
249
501
|
|
|
250
502
|
//# sourceMappingURL=index.mjs.map
|