@aws-amplify/backend-notifications 0.0.0-test-20260708085627
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 +235 -0
- package/lib/campaign-association-asset/index.js +48413 -0
- package/lib/constants.d.ts +122 -0
- package/lib/constants.js +125 -0
- package/lib/construct.d.ts +283 -0
- package/lib/construct.js +824 -0
- package/lib/factory.d.ts +53 -0
- package/lib/factory.js +158 -0
- package/lib/handler-asset/index.js +530 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +5 -0
- package/lib/lambda/campaign-association/handler.d.ts +29 -0
- package/lib/lambda/campaign-association/handler.js +80 -0
- package/lib/lambda/campaign-association/integration.d.ts +59 -0
- package/lib/lambda/campaign-association/integration.js +108 -0
- package/lib/lambda/campaign-association/onboarding.d.ts +25 -0
- package/lib/lambda/campaign-association/onboarding.js +74 -0
- package/lib/lambda/identify/device_resolver.d.ts +18 -0
- package/lib/lambda/identify/device_resolver.js +56 -0
- package/lib/lambda/identify/handler.d.ts +17 -0
- package/lib/lambda/identify/handler.js +124 -0
- package/lib/lambda/identify/mapping.d.ts +61 -0
- package/lib/lambda/identify/mapping.js +171 -0
- package/lib/lambda/identify/merge_resolver.d.ts +26 -0
- package/lib/lambda/identify/merge_resolver.js +36 -0
- package/lib/lambda/identify/principal.d.ts +68 -0
- package/lib/lambda/identify/principal.js +48 -0
- package/lib/lambda/identify/profile_resolver.d.ts +35 -0
- package/lib/lambda/identify/profile_resolver.js +65 -0
- package/lib/lambda/identify/types.d.ts +90 -0
- package/lib/lambda/identify/types.js +10 -0
- package/lib/lambda/identify/validation.d.ts +15 -0
- package/lib/lambda/identify/validation.js +99 -0
- package/lib/lambda/push/delivery.d.ts +49 -0
- package/lib/lambda/push/delivery.js +120 -0
- package/lib/lambda/push/device_lookup.d.ts +35 -0
- package/lib/lambda/push/device_lookup.js +88 -0
- package/lib/lambda/push/eum_client.d.ts +31 -0
- package/lib/lambda/push/eum_client.js +140 -0
- package/lib/lambda/push/event.d.ts +32 -0
- package/lib/lambda/push/event.js +122 -0
- package/lib/lambda/push/fixtures/real_journey_event.d.ts +19 -0
- package/lib/lambda/push/fixtures/real_journey_event.js +22 -0
- package/lib/lambda/push/handler.d.ts +15 -0
- package/lib/lambda/push/handler.js +86 -0
- package/lib/lambda/push/message_template.d.ts +72 -0
- package/lib/lambda/push/message_template.js +270 -0
- package/lib/lambda/push/payload.d.ts +25 -0
- package/lib/lambda/push/payload.js +53 -0
- package/lib/lambda/push/types.d.ts +153 -0
- package/lib/lambda/push/types.js +4 -0
- package/lib/lambda/shared/retry.d.ts +18 -0
- package/lib/lambda/shared/retry.js +45 -0
- package/lib/object_types.d.ts +63 -0
- package/lib/object_types.js +131 -0
- package/lib/push-handler-asset/index.js +75517 -0
- package/lib/types.d.ts +125 -0
- package/lib/types.js +2 -0
- package/package.json +49 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lambda/identify/handler.ts
|
|
21
|
+
var handler_exports = {};
|
|
22
|
+
__export(handler_exports, {
|
|
23
|
+
handler: () => handler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(handler_exports);
|
|
26
|
+
var import_client_customer_profiles4 = require("@aws-sdk/client-customer-profiles");
|
|
27
|
+
|
|
28
|
+
// src/lambda/identify/validation.ts
|
|
29
|
+
var CHANNEL_TYPES = [
|
|
30
|
+
"GCM",
|
|
31
|
+
"APNS",
|
|
32
|
+
"APNS_SANDBOX",
|
|
33
|
+
"IN_APP"
|
|
34
|
+
];
|
|
35
|
+
var OPT_OUTS = ["ALL", "NONE"];
|
|
36
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
37
|
+
var isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
38
|
+
var isStringMap = (v) => isPlainObject(v) && Object.values(v).every((x) => isStringArray(x));
|
|
39
|
+
var validateBody = (body) => {
|
|
40
|
+
if (!isPlainObject(body)) {
|
|
41
|
+
return { ok: false, error: "Request body must be a JSON object" };
|
|
42
|
+
}
|
|
43
|
+
if (body.userId !== void 0 && typeof body.userId !== "string") {
|
|
44
|
+
return { ok: false, error: "userId must be a string when present" };
|
|
45
|
+
}
|
|
46
|
+
const userProfile = body.userProfile;
|
|
47
|
+
if (!isPlainObject(userProfile)) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: "userProfile is required and must be an object"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
for (const key of ["name", "email", "plan"]) {
|
|
54
|
+
if (userProfile[key] !== void 0 && typeof userProfile[key] !== "string") {
|
|
55
|
+
return { ok: false, error: `userProfile.${key} must be a string` };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (userProfile.customProperties !== void 0 && !isStringMap(userProfile.customProperties)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: "userProfile.customProperties must be a map of string arrays"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (userProfile.metrics !== void 0) {
|
|
65
|
+
if (!isPlainObject(userProfile.metrics) || !Object.values(userProfile.metrics).every((n) => typeof n === "number")) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: "userProfile.metrics must be a map of numbers"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const options = body.options;
|
|
73
|
+
if (options !== void 0) {
|
|
74
|
+
if (!isPlainObject(options)) {
|
|
75
|
+
return { ok: false, error: "options must be an object when present" };
|
|
76
|
+
}
|
|
77
|
+
if (options.address !== void 0 && typeof options.address !== "string") {
|
|
78
|
+
return { ok: false, error: "options.address must be a string" };
|
|
79
|
+
}
|
|
80
|
+
if (options.deviceId !== void 0 && typeof options.deviceId !== "string") {
|
|
81
|
+
return { ok: false, error: "options.deviceId must be a string" };
|
|
82
|
+
}
|
|
83
|
+
if (options.previousGuestIdentityId !== void 0 && (typeof options.previousGuestIdentityId !== "string" || options.previousGuestIdentityId.trim().length === 0 || options.previousGuestIdentityId.length > 128)) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
error: "options.previousGuestIdentityId must be a non-empty string"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (options.channelType !== void 0 && !CHANNEL_TYPES.includes(options.channelType)) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
error: `options.channelType must be one of ${CHANNEL_TYPES.join(", ")}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (options.optOut !== void 0 && !OPT_OUTS.includes(options.optOut)) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: `options.optOut must be one of ${OPT_OUTS.join(", ")}`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (options.userAttributes !== void 0 && !isStringMap(options.userAttributes)) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
error: "options.userAttributes must be a map of string arrays"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, value: body };
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/constants.ts
|
|
112
|
+
var OBJECT_TYPE_PROFILE = "AmplifyProfile";
|
|
113
|
+
var OBJECT_TYPE_GUEST_PROFILE = "AmplifyGuestProfile";
|
|
114
|
+
var OBJECT_TYPE_DEVICE = "AmplifyDevice";
|
|
115
|
+
var COGNITO_USER_KEY = "cognitoUserKey";
|
|
116
|
+
var COGNITO_IDENTITY_KEY = "cognitoIdentityKey";
|
|
117
|
+
var COGNITO_IDENTITY_FIELD = "cognitoIdentityId";
|
|
118
|
+
var COGNITO_SUB_FIELD = "cognitoSub";
|
|
119
|
+
var MAX_ATTRIBUTE_LENGTH = 255;
|
|
120
|
+
var ENV_DOMAIN_NAME = "PROFILES_DOMAIN_NAME";
|
|
121
|
+
|
|
122
|
+
// src/lambda/identify/mapping.ts
|
|
123
|
+
var flatten = (values) => {
|
|
124
|
+
let out;
|
|
125
|
+
if (!values || values.length === 0) {
|
|
126
|
+
out = "";
|
|
127
|
+
} else if (values.length === 1) {
|
|
128
|
+
out = values[0] ?? "";
|
|
129
|
+
} else {
|
|
130
|
+
out = JSON.stringify(values);
|
|
131
|
+
}
|
|
132
|
+
return out.length > MAX_ATTRIBUTE_LENGTH ? out.slice(0, MAX_ATTRIBUTE_LENGTH) : out;
|
|
133
|
+
};
|
|
134
|
+
var serializeMultiValueMap = (map) => {
|
|
135
|
+
if (!map) {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
const keys = Object.keys(map);
|
|
139
|
+
if (keys.length === 0) {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
const flattened = {};
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
flattened[key] = flatten(map[key]);
|
|
145
|
+
}
|
|
146
|
+
const out = JSON.stringify(flattened);
|
|
147
|
+
return out.length > MAX_ATTRIBUTE_LENGTH ? out.slice(0, MAX_ATTRIBUTE_LENGTH) : out;
|
|
148
|
+
};
|
|
149
|
+
var splitName = (name) => {
|
|
150
|
+
const trimmed = name.trim();
|
|
151
|
+
const idx = trimmed.indexOf(" ");
|
|
152
|
+
if (idx === -1) {
|
|
153
|
+
return { firstName: trimmed };
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
firstName: trimmed.slice(0, idx),
|
|
157
|
+
lastName: trimmed.slice(idx + 1).trim() || void 0
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
var compact = (obj) => {
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
163
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
164
|
+
out[k] = v;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
};
|
|
169
|
+
var targetingFlagsForChannel = (channelType) => {
|
|
170
|
+
switch (channelType) {
|
|
171
|
+
case "GCM":
|
|
172
|
+
return { hasGCM: "true" };
|
|
173
|
+
case "APNS":
|
|
174
|
+
case "APNS_SANDBOX":
|
|
175
|
+
return { hasAPNS: "true" };
|
|
176
|
+
default:
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var hasDeviceData = (req) => !!req.options?.deviceId;
|
|
181
|
+
var buildProfileUpdate = (principal, req) => {
|
|
182
|
+
const profile = req.userProfile ?? {};
|
|
183
|
+
const demographic = profile.demographic;
|
|
184
|
+
const location = profile.location;
|
|
185
|
+
const name = profile.name ? splitName(profile.name) : void 0;
|
|
186
|
+
const flags = targetingFlagsForChannel(req.options?.channelType);
|
|
187
|
+
const attributes = compact({
|
|
188
|
+
// Identity trail (searchable key is separate; this is for reference only).
|
|
189
|
+
[principal.keyName]: principal.value,
|
|
190
|
+
appUserId: req.userId,
|
|
191
|
+
// Targeting / custom attributes for Connect segmentation.
|
|
192
|
+
plan: profile.plan,
|
|
193
|
+
locale: demographic?.locale,
|
|
194
|
+
platform: demographic?.platform,
|
|
195
|
+
appVersion: demographic?.appVersion,
|
|
196
|
+
make: demographic?.make,
|
|
197
|
+
model: demographic?.model,
|
|
198
|
+
modelVersion: demographic?.modelVersion,
|
|
199
|
+
platformVersion: demographic?.platformVersion,
|
|
200
|
+
timezone: demographic?.timezone,
|
|
201
|
+
latitude: location?.latitude !== void 0 ? String(location.latitude) : void 0,
|
|
202
|
+
longitude: location?.longitude !== void 0 ? String(location.longitude) : void 0,
|
|
203
|
+
customProperties: serializeMultiValueMap(profile.customProperties),
|
|
204
|
+
userAttributes: serializeMultiValueMap(req.options?.userAttributes),
|
|
205
|
+
metrics: profile.metrics ? JSON.stringify(profile.metrics) : void 0,
|
|
206
|
+
// Promoted push-capability flags.
|
|
207
|
+
hasGCM: flags.hasGCM,
|
|
208
|
+
hasAPNS: flags.hasAPNS
|
|
209
|
+
});
|
|
210
|
+
const address = compact({
|
|
211
|
+
city: location?.city,
|
|
212
|
+
country: location?.country,
|
|
213
|
+
postalCode: location?.postalCode,
|
|
214
|
+
province: location?.region
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
emailAddress: profile.email,
|
|
218
|
+
firstName: name?.firstName,
|
|
219
|
+
lastName: name?.lastName,
|
|
220
|
+
address: Object.keys(address).length > 0 ? address : void 0,
|
|
221
|
+
attributes
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
var buildDeviceObject = (principal, req, existingCreatedAt) => {
|
|
225
|
+
const options = req.options ?? {};
|
|
226
|
+
const demographic = req.userProfile?.demographic;
|
|
227
|
+
const platform = options.platform ?? demographic?.platform;
|
|
228
|
+
const appVersion = options.appVersion ?? demographic?.appVersion;
|
|
229
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
230
|
+
return compact({
|
|
231
|
+
[principal.objectField]: principal.value,
|
|
232
|
+
deviceId: options.deviceId,
|
|
233
|
+
deviceToken: options.address,
|
|
234
|
+
channelType: options.channelType,
|
|
235
|
+
platform,
|
|
236
|
+
appVersion,
|
|
237
|
+
createdAt: existingCreatedAt ?? now,
|
|
238
|
+
updatedAt: now
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/lambda/identify/profile_resolver.ts
|
|
243
|
+
var import_client_customer_profiles = require("@aws-sdk/client-customer-profiles");
|
|
244
|
+
|
|
245
|
+
// src/lambda/shared/retry.ts
|
|
246
|
+
var isTransientConcurrentUpdate = (err) => {
|
|
247
|
+
const name = err?.name ?? "";
|
|
248
|
+
const msg = err?.message ?? "";
|
|
249
|
+
return (name === "BadRequestException" || name === "ThrottlingException" || name === "TooManyRequestsException") && /concurrent update in-progress|too many requests|rate exceeded/i.test(
|
|
250
|
+
msg
|
|
251
|
+
) || name === "ThrottlingException" || name === "TooManyRequestsException";
|
|
252
|
+
};
|
|
253
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
254
|
+
var withTransientRetry = async (fn, opts = {}) => {
|
|
255
|
+
const attempts = opts.attempts ?? 6;
|
|
256
|
+
const baseDelayMs = opts.baseDelayMs ?? 80;
|
|
257
|
+
let lastErr;
|
|
258
|
+
for (let i = 0; i < attempts; i++) {
|
|
259
|
+
try {
|
|
260
|
+
return await fn();
|
|
261
|
+
} catch (err) {
|
|
262
|
+
lastErr = err;
|
|
263
|
+
if (!isTransientConcurrentUpdate(err) || i === attempts - 1) {
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
const ceiling = baseDelayMs * 2 ** i;
|
|
267
|
+
await sleep(Math.floor(Math.random() * ceiling));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw lastErr;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/lambda/identify/profile_resolver.ts
|
|
274
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
275
|
+
var searchProfileId = async (profiles2, domainName, keyName, value, attempts, baseDelayMs) => {
|
|
276
|
+
for (let i = 0; i < attempts; i++) {
|
|
277
|
+
const search = await profiles2.send(
|
|
278
|
+
new import_client_customer_profiles.SearchProfilesCommand({
|
|
279
|
+
DomainName: domainName,
|
|
280
|
+
KeyName: keyName,
|
|
281
|
+
Values: [value]
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
const found = search.Items?.find((item) => !!item.ProfileId)?.ProfileId;
|
|
285
|
+
if (found) {
|
|
286
|
+
return found;
|
|
287
|
+
}
|
|
288
|
+
if (i < attempts - 1) {
|
|
289
|
+
await sleep2(baseDelayMs * (i + 1));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return void 0;
|
|
293
|
+
};
|
|
294
|
+
var resolveOrCreateProfile = async (profiles2, domainName, principal) => {
|
|
295
|
+
await withTransientRetry(
|
|
296
|
+
() => profiles2.send(
|
|
297
|
+
new import_client_customer_profiles.PutProfileObjectCommand({
|
|
298
|
+
DomainName: domainName,
|
|
299
|
+
ObjectTypeName: principal.profileObjectType,
|
|
300
|
+
Object: JSON.stringify({ [principal.objectField]: principal.value })
|
|
301
|
+
})
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
const profileId = await searchProfileId(
|
|
305
|
+
profiles2,
|
|
306
|
+
domainName,
|
|
307
|
+
principal.keyName,
|
|
308
|
+
principal.value,
|
|
309
|
+
8,
|
|
310
|
+
150
|
|
311
|
+
);
|
|
312
|
+
if (!profileId) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
"PutProfileObject find-or-create did not resolve a ProfileId"
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return { profileId };
|
|
318
|
+
};
|
|
319
|
+
var findProfileId = async (profiles2, domainName, principal) => searchProfileId(
|
|
320
|
+
profiles2,
|
|
321
|
+
domainName,
|
|
322
|
+
principal.keyName,
|
|
323
|
+
principal.value,
|
|
324
|
+
1,
|
|
325
|
+
0
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// src/lambda/identify/device_resolver.ts
|
|
329
|
+
var import_client_customer_profiles2 = require("@aws-sdk/client-customer-profiles");
|
|
330
|
+
var findExistingDeviceCreatedAt = async (profiles2, domainName, profileId, deviceId) => {
|
|
331
|
+
try {
|
|
332
|
+
let nextToken;
|
|
333
|
+
do {
|
|
334
|
+
const res = await withTransientRetry(
|
|
335
|
+
() => profiles2.send(
|
|
336
|
+
new import_client_customer_profiles2.ListProfileObjectsCommand({
|
|
337
|
+
DomainName: domainName,
|
|
338
|
+
ObjectTypeName: OBJECT_TYPE_DEVICE,
|
|
339
|
+
ProfileId: profileId,
|
|
340
|
+
NextToken: nextToken
|
|
341
|
+
})
|
|
342
|
+
)
|
|
343
|
+
);
|
|
344
|
+
for (const item of res.Items ?? []) {
|
|
345
|
+
if (!item.Object) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(item.Object);
|
|
350
|
+
if (parsed.deviceId === deviceId && typeof parsed.createdAt === "string" && parsed.createdAt.length > 0) {
|
|
351
|
+
return parsed.createdAt;
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
nextToken = res.NextToken;
|
|
358
|
+
} while (nextToken);
|
|
359
|
+
} catch {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
return void 0;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/lambda/identify/merge_resolver.ts
|
|
366
|
+
var import_client_customer_profiles3 = require("@aws-sdk/client-customer-profiles");
|
|
367
|
+
|
|
368
|
+
// src/lambda/identify/principal.ts
|
|
369
|
+
var authedPrincipal = (sub) => ({
|
|
370
|
+
kind: "authed",
|
|
371
|
+
keyName: COGNITO_USER_KEY,
|
|
372
|
+
objectField: COGNITO_SUB_FIELD,
|
|
373
|
+
profileObjectType: OBJECT_TYPE_PROFILE,
|
|
374
|
+
value: sub
|
|
375
|
+
});
|
|
376
|
+
var guestPrincipal = (identityId) => ({
|
|
377
|
+
kind: "guest",
|
|
378
|
+
keyName: COGNITO_IDENTITY_KEY,
|
|
379
|
+
objectField: COGNITO_IDENTITY_FIELD,
|
|
380
|
+
profileObjectType: OBJECT_TYPE_GUEST_PROFILE,
|
|
381
|
+
value: identityId
|
|
382
|
+
});
|
|
383
|
+
var resolvePrincipal = (event) => {
|
|
384
|
+
const claims = event.requestContext?.authorizer?.jwt?.claims;
|
|
385
|
+
const sub = claims?.sub;
|
|
386
|
+
if (typeof sub === "string" && sub.length > 0) {
|
|
387
|
+
return authedPrincipal(sub);
|
|
388
|
+
}
|
|
389
|
+
const identity = event.requestContext?.identity;
|
|
390
|
+
const identityId = identity?.cognitoIdentityId;
|
|
391
|
+
if (identity?.cognitoAuthenticationType === "unauthenticated" && typeof identityId === "string" && identityId.length > 0) {
|
|
392
|
+
return guestPrincipal(identityId);
|
|
393
|
+
}
|
|
394
|
+
return void 0;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// src/lambda/identify/merge_resolver.ts
|
|
398
|
+
var mergeGuestIntoAuthed = async (profiles2, domainName, guestIdentityId, authedProfileId) => {
|
|
399
|
+
const guestProfileId = await findProfileId(
|
|
400
|
+
profiles2,
|
|
401
|
+
domainName,
|
|
402
|
+
guestPrincipal(guestIdentityId)
|
|
403
|
+
);
|
|
404
|
+
if (!guestProfileId || guestProfileId === authedProfileId) {
|
|
405
|
+
return { merged: false };
|
|
406
|
+
}
|
|
407
|
+
await withTransientRetry(
|
|
408
|
+
() => profiles2.send(
|
|
409
|
+
new import_client_customer_profiles3.MergeProfilesCommand({
|
|
410
|
+
DomainName: domainName,
|
|
411
|
+
MainProfileId: authedProfileId,
|
|
412
|
+
ProfileIdsToBeMerged: [guestProfileId]
|
|
413
|
+
})
|
|
414
|
+
)
|
|
415
|
+
);
|
|
416
|
+
return { merged: true };
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// src/lambda/identify/handler.ts
|
|
420
|
+
var profiles = new import_client_customer_profiles4.CustomerProfilesClient({});
|
|
421
|
+
var response = (statusCode, body) => ({
|
|
422
|
+
statusCode,
|
|
423
|
+
headers: { "content-type": "application/json" },
|
|
424
|
+
body: JSON.stringify(body)
|
|
425
|
+
});
|
|
426
|
+
var handler = async (event) => {
|
|
427
|
+
const domainName = process.env[ENV_DOMAIN_NAME];
|
|
428
|
+
if (!domainName) {
|
|
429
|
+
console.error(`Missing required env var ${ENV_DOMAIN_NAME}`);
|
|
430
|
+
return response(500, { error: "Server misconfiguration" });
|
|
431
|
+
}
|
|
432
|
+
const principal = resolvePrincipal(event);
|
|
433
|
+
if (!principal) {
|
|
434
|
+
return response(401, {
|
|
435
|
+
error: "Unauthorized: missing or invalid verified caller identity"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
let parsed;
|
|
439
|
+
try {
|
|
440
|
+
parsed = event.body ? JSON.parse(event.body) : void 0;
|
|
441
|
+
} catch {
|
|
442
|
+
return response(400, { error: "Invalid JSON request body" });
|
|
443
|
+
}
|
|
444
|
+
const validation = validateBody(parsed);
|
|
445
|
+
if (!validation.ok || !validation.value) {
|
|
446
|
+
return response(400, { error: validation.error ?? "Invalid request" });
|
|
447
|
+
}
|
|
448
|
+
const request = validation.value;
|
|
449
|
+
try {
|
|
450
|
+
const { profileId } = await resolveOrCreateProfile(
|
|
451
|
+
profiles,
|
|
452
|
+
domainName,
|
|
453
|
+
principal
|
|
454
|
+
);
|
|
455
|
+
if (hasDeviceData(request)) {
|
|
456
|
+
const deviceId = request.options.deviceId;
|
|
457
|
+
const existingCreatedAt = await findExistingDeviceCreatedAt(
|
|
458
|
+
profiles,
|
|
459
|
+
domainName,
|
|
460
|
+
profileId,
|
|
461
|
+
deviceId
|
|
462
|
+
);
|
|
463
|
+
const deviceObject = buildDeviceObject(
|
|
464
|
+
principal,
|
|
465
|
+
request,
|
|
466
|
+
existingCreatedAt
|
|
467
|
+
);
|
|
468
|
+
await withTransientRetry(
|
|
469
|
+
() => profiles.send(
|
|
470
|
+
new import_client_customer_profiles4.PutProfileObjectCommand({
|
|
471
|
+
DomainName: domainName,
|
|
472
|
+
ObjectTypeName: OBJECT_TYPE_DEVICE,
|
|
473
|
+
Object: JSON.stringify(deviceObject)
|
|
474
|
+
})
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
const update = buildProfileUpdate(principal, request);
|
|
479
|
+
await withTransientRetry(
|
|
480
|
+
() => profiles.send(
|
|
481
|
+
new import_client_customer_profiles4.UpdateProfileCommand({
|
|
482
|
+
DomainName: domainName,
|
|
483
|
+
ProfileId: profileId,
|
|
484
|
+
EmailAddress: update.emailAddress,
|
|
485
|
+
FirstName: update.firstName,
|
|
486
|
+
LastName: update.lastName,
|
|
487
|
+
Address: update.address ? {
|
|
488
|
+
City: update.address.city,
|
|
489
|
+
Country: update.address.country,
|
|
490
|
+
PostalCode: update.address.postalCode,
|
|
491
|
+
Province: update.address.province
|
|
492
|
+
} : void 0,
|
|
493
|
+
Attributes: update.attributes
|
|
494
|
+
})
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
const guestIdentityId = request.options?.previousGuestIdentityId;
|
|
498
|
+
if (principal.kind === "authed" && guestIdentityId) {
|
|
499
|
+
try {
|
|
500
|
+
const outcome = await mergeGuestIntoAuthed(
|
|
501
|
+
profiles,
|
|
502
|
+
domainName,
|
|
503
|
+
guestIdentityId,
|
|
504
|
+
profileId
|
|
505
|
+
);
|
|
506
|
+
console.log(
|
|
507
|
+
"[identify] guestMerge",
|
|
508
|
+
JSON.stringify({ merged: outcome.merged })
|
|
509
|
+
);
|
|
510
|
+
} catch (mergeErr) {
|
|
511
|
+
const name = mergeErr instanceof Error ? mergeErr.name : "UnknownError";
|
|
512
|
+
console.error("[identify] guestMergeError", JSON.stringify({ name }));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return response(200, { status: "ok" });
|
|
516
|
+
} catch (err) {
|
|
517
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
518
|
+
const name = err instanceof Error ? err.name : "UnknownError";
|
|
519
|
+
const statusCode = err?.$metadata?.httpStatusCode;
|
|
520
|
+
console.error(
|
|
521
|
+
"[identify] customerProfilesError",
|
|
522
|
+
JSON.stringify({ name, statusCode })
|
|
523
|
+
);
|
|
524
|
+
return response(500, { error: `Customer Profiles error: ${message}` });
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
528
|
+
0 && (module.exports = {
|
|
529
|
+
handler
|
|
530
|
+
});
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defineNotifications } from './factory.js';
|
|
2
|
+
export { AmplifyNotifications, AmplifyNotificationsProps, NotificationsResources, } from './construct.js';
|
|
3
|
+
export { NotificationsFactoryProps, ApnsChannelProps, FcmChannelProps, } from './types.js';
|
|
4
|
+
export { OBJECT_TYPE_PROFILE, OBJECT_TYPE_DEVICE, COGNITO_USER_KEY, OUTPUT_KEY, } from './constants.js';
|
|
5
|
+
export { AMPLIFY_PROFILE_FIELDS, AMPLIFY_PROFILE_KEYS, AMPLIFY_DEVICE_FIELDS, AMPLIFY_DEVICE_KEYS, OBJECT_TYPE_NAMES, } from './object_types.js';
|
|
6
|
+
export type { FieldMap, KeyMap } from './object_types.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { defineNotifications } from './factory.js';
|
|
2
|
+
export { AmplifyNotifications, } from './construct.js';
|
|
3
|
+
export { OBJECT_TYPE_PROFILE, OBJECT_TYPE_DEVICE, COGNITO_USER_KEY, OUTPUT_KEY, } from './constants.js';
|
|
4
|
+
export { AMPLIFY_PROFILE_FIELDS, AMPLIFY_PROFILE_KEYS, AMPLIFY_DEVICE_FIELDS, AMPLIFY_DEVICE_KEYS, OBJECT_TYPE_NAMES, } from './object_types.js';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ25ELE9BQU8sRUFDTCxvQkFBb0IsR0FHckIsTUFBTSxnQkFBZ0IsQ0FBQztBQU14QixPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGtCQUFrQixFQUNsQixnQkFBZ0IsRUFDaEIsVUFBVSxHQUNYLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixvQkFBb0IsRUFDcEIscUJBQXFCLEVBQ3JCLG1CQUFtQixFQUNuQixpQkFBaUIsR0FDbEIsTUFBTSxtQkFBbUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IGRlZmluZU5vdGlmaWNhdGlvbnMgfSBmcm9tICcuL2ZhY3RvcnkuanMnO1xuZXhwb3J0IHtcbiAgQW1wbGlmeU5vdGlmaWNhdGlvbnMsXG4gIEFtcGxpZnlOb3RpZmljYXRpb25zUHJvcHMsXG4gIE5vdGlmaWNhdGlvbnNSZXNvdXJjZXMsXG59IGZyb20gJy4vY29uc3RydWN0LmpzJztcbmV4cG9ydCB7XG4gIE5vdGlmaWNhdGlvbnNGYWN0b3J5UHJvcHMsXG4gIEFwbnNDaGFubmVsUHJvcHMsXG4gIEZjbUNoYW5uZWxQcm9wcyxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5leHBvcnQge1xuICBPQkpFQ1RfVFlQRV9QUk9GSUxFLFxuICBPQkpFQ1RfVFlQRV9ERVZJQ0UsXG4gIENPR05JVE9fVVNFUl9LRVksXG4gIE9VVFBVVF9LRVksXG59IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmV4cG9ydCB7XG4gIEFNUExJRllfUFJPRklMRV9GSUVMRFMsXG4gIEFNUExJRllfUFJPRklMRV9LRVlTLFxuICBBTVBMSUZZX0RFVklDRV9GSUVMRFMsXG4gIEFNUExJRllfREVWSUNFX0tFWVMsXG4gIE9CSkVDVF9UWVBFX05BTUVTLFxufSBmcm9tICcuL29iamVjdF90eXBlcy5qcyc7XG5leHBvcnQgdHlwZSB7IEZpZWxkTWFwLCBLZXlNYXAgfSBmcm9tICcuL29iamVjdF90eXBlcy5qcyc7XG4iXX0=
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CloudFormationCustomResourceEvent } from 'aws-lambda';
|
|
2
|
+
/** Result the Provider framework echoes back to CloudFormation. */
|
|
3
|
+
export type OnEventResult = {
|
|
4
|
+
PhysicalResourceId: string;
|
|
5
|
+
Data?: {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* CloudFormation custom-resource handler (invoked via the CDK
|
|
11
|
+
* `custom-resources.Provider` framework) that associates a from-scratch Customer
|
|
12
|
+
* Profiles domain with its Amazon Connect instance's Outbound Campaigns v2, so
|
|
13
|
+
* Connect Journeys can target the domain's profiles WITHOUT any manual console
|
|
14
|
+
* step.
|
|
15
|
+
*
|
|
16
|
+
* Create / Update (idempotent): onboard the instance to Outbound Campaigns v2
|
|
17
|
+
* (creates the service-linked role + waits for SUCCEEDED), resolve the campaigns
|
|
18
|
+
* service-linked role, then create both sides of the domain <-> campaigns
|
|
19
|
+
* integration.
|
|
20
|
+
*
|
|
21
|
+
* Delete (best-effort): reverse both integrations, swallowing errors so a
|
|
22
|
+
* partial / already-removed association never blocks stack teardown.
|
|
23
|
+
*
|
|
24
|
+
* Only wired by the construct in create-from-scratch mode (the construct owns
|
|
25
|
+
* the instance + domain). In attach mode the existing domain's association is
|
|
26
|
+
* the user's responsibility and this resource is not added.
|
|
27
|
+
*/
|
|
28
|
+
export declare const handler: (event: CloudFormationCustomResourceEvent) => Promise<OnEventResult>;
|
|
29
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { ConnectCampaignsV2Client } from '@aws-sdk/client-connectcampaignsv2';
|
|
4
|
+
import { CustomerProfilesClient } from '@aws-sdk/client-customer-profiles';
|
|
5
|
+
import { IAMClient } from '@aws-sdk/client-iam';
|
|
6
|
+
import { ensureInstanceOnboarded } from './onboarding.js';
|
|
7
|
+
import { associateDomain, disassociateDomain, resolveCampaignsServiceLinkedRoleArn, } from './integration.js';
|
|
8
|
+
/** Module-level clients reused across warm invocations. */
|
|
9
|
+
const campaigns = new ConnectCampaignsV2Client({});
|
|
10
|
+
const profiles = new CustomerProfilesClient({});
|
|
11
|
+
const iam = new IAMClient({});
|
|
12
|
+
const readProps = (event) => {
|
|
13
|
+
const p = event.ResourceProperties;
|
|
14
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
15
|
+
if (!p.ConnectInstanceId || !p.DomainName || !p.Account || !p.Region) {
|
|
16
|
+
throw new Error('Missing required ResourceProperties: ConnectInstanceId, DomainName, Account, Region');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
connectInstanceId: p.ConnectInstanceId,
|
|
20
|
+
domainName: p.DomainName,
|
|
21
|
+
account: p.Account,
|
|
22
|
+
region: p.Region,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const physicalId = (props) => `campaign-assoc:${props.connectInstanceId}:${props.domainName}`;
|
|
26
|
+
/**
|
|
27
|
+
* CloudFormation custom-resource handler (invoked via the CDK
|
|
28
|
+
* `custom-resources.Provider` framework) that associates a from-scratch Customer
|
|
29
|
+
* Profiles domain with its Amazon Connect instance's Outbound Campaigns v2, so
|
|
30
|
+
* Connect Journeys can target the domain's profiles WITHOUT any manual console
|
|
31
|
+
* step.
|
|
32
|
+
*
|
|
33
|
+
* Create / Update (idempotent): onboard the instance to Outbound Campaigns v2
|
|
34
|
+
* (creates the service-linked role + waits for SUCCEEDED), resolve the campaigns
|
|
35
|
+
* service-linked role, then create both sides of the domain <-> campaigns
|
|
36
|
+
* integration.
|
|
37
|
+
*
|
|
38
|
+
* Delete (best-effort): reverse both integrations, swallowing errors so a
|
|
39
|
+
* partial / already-removed association never blocks stack teardown.
|
|
40
|
+
*
|
|
41
|
+
* Only wired by the construct in create-from-scratch mode (the construct owns
|
|
42
|
+
* the instance + domain). In attach mode the existing domain's association is
|
|
43
|
+
* the user's responsibility and this resource is not added.
|
|
44
|
+
*/
|
|
45
|
+
export const handler = async (event) => {
|
|
46
|
+
console.log('[campaign-assoc] event', {
|
|
47
|
+
requestType: event.RequestType,
|
|
48
|
+
logicalResourceId: event.LogicalResourceId,
|
|
49
|
+
});
|
|
50
|
+
const props = readProps(event);
|
|
51
|
+
if (event.RequestType === 'Delete') {
|
|
52
|
+
await disassociateDomain({ campaigns, profiles }, {
|
|
53
|
+
connectInstanceId: props.connectInstanceId,
|
|
54
|
+
domainName: props.domainName,
|
|
55
|
+
account: props.account,
|
|
56
|
+
region: props.region,
|
|
57
|
+
});
|
|
58
|
+
return { PhysicalResourceId: event.PhysicalResourceId };
|
|
59
|
+
}
|
|
60
|
+
// Create + Update: onboard (idempotent) then (re)create both integrations.
|
|
61
|
+
await ensureInstanceOnboarded(campaigns, {
|
|
62
|
+
connectInstanceId: props.connectInstanceId,
|
|
63
|
+
});
|
|
64
|
+
const roleArn = await resolveCampaignsServiceLinkedRoleArn(iam);
|
|
65
|
+
await associateDomain({ campaigns, profiles, iam }, {
|
|
66
|
+
connectInstanceId: props.connectInstanceId,
|
|
67
|
+
domainName: props.domainName,
|
|
68
|
+
roleArn,
|
|
69
|
+
account: props.account,
|
|
70
|
+
region: props.region,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
PhysicalResourceId: physicalId(props),
|
|
74
|
+
Data: {
|
|
75
|
+
connectInstanceId: props.connectInstanceId,
|
|
76
|
+
domainName: props.domainName,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9sYW1iZGEvY2FtcGFpZ24tYXNzb2NpYXRpb24vaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxRUFBcUU7QUFDckUsc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQzlFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQzNFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVoRCxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMxRCxPQUFPLEVBQ0wsZUFBZSxFQUNmLGtCQUFrQixFQUNsQixvQ0FBb0MsR0FDckMsTUFBTSxrQkFBa0IsQ0FBQztBQUUxQiwyREFBMkQ7QUFDM0QsTUFBTSxTQUFTLEdBQUcsSUFBSSx3QkFBd0IsQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUNuRCxNQUFNLFFBQVEsR0FBRyxJQUFJLHNCQUFzQixDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ2hELE1BQU0sR0FBRyxHQUFHLElBQUksU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBaUI5QixNQUFNLFNBQVMsR0FBRyxDQUFDLEtBQXdDLEVBQVMsRUFBRTtJQUNwRSxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUMsa0JBS2YsQ0FBQztJQUNGLHdEQUF3RDtJQUN4RCxJQUFJLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixJQUFJLENBQUMsQ0FBQyxDQUFDLFVBQVUsSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckUsTUFBTSxJQUFJLEtBQUssQ0FDYixxRkFBcUYsQ0FDdEYsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPO1FBQ0wsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDLGlCQUFpQjtRQUN0QyxVQUFVLEVBQUUsQ0FBQyxDQUFDLFVBQVU7UUFDeEIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPO1FBQ2xCLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTTtLQUNqQixDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBRUYsTUFBTSxVQUFVLEdBQUcsQ0FBQyxLQUFZLEVBQVUsRUFBRSxDQUMxQyxrQkFBa0IsS0FBSyxDQUFDLGlCQUFpQixJQUFJLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztBQUVsRTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sT0FBTyxHQUFHLEtBQUssRUFDMUIsS0FBd0MsRUFDaEIsRUFBRTtJQUMxQixPQUFPLENBQUMsR0FBRyxDQUFDLHdCQUF3QixFQUFFO1FBQ3BDLFdBQVcsRUFBRSxLQUFLLENBQUMsV0FBVztRQUM5QixpQkFBaUIsRUFBRSxLQUFLLENBQUMsaUJBQWlCO0tBQzNDLENBQUMsQ0FBQztJQUVILE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUUvQixJQUFJLEtBQUssQ0FBQyxXQUFXLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDbkMsTUFBTSxrQkFBa0IsQ0FDdEIsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLEVBQ3ZCO1lBQ0UsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtZQUMxQyxVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVU7WUFDNUIsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPO1lBQ3RCLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTTtTQUNyQixDQUNGLENBQUM7UUFDRixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsS0FBSyxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFDMUQsQ0FBQztJQUVELDJFQUEyRTtJQUMzRSxNQUFNLHVCQUF1QixDQUFDLFNBQVMsRUFBRTtRQUN2QyxpQkFBaUIsRUFBRSxLQUFLLENBQUMsaUJBQWlCO0tBQzNDLENBQUMsQ0FBQztJQUNILE1BQU0sT0FBTyxHQUFHLE1BQU0sb0NBQW9DLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDaEUsTUFBTSxlQUFlLENBQ25CLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsRUFDNUI7UUFDRSxpQkFBaUIsRUFBRSxLQUFLLENBQUMsaUJBQWlCO1FBQzFDLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtRQUM1QixPQUFPO1FBQ1AsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPO1FBQ3RCLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTTtLQUNyQixDQUNGLENBQUM7SUFFRixPQUFPO1FBQ0wsa0JBQWtCLEVBQUUsVUFBVSxDQUFDLEtBQUssQ0FBQztRQUNyQyxJQUFJLEVBQUU7WUFDSixpQkFBaUIsRUFBRSxLQUFLLENBQUMsaUJBQWlCO1lBQzFDLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtTQUM3QjtLQUNGLENBQUM7QUFDSixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgQW1hem9uLmNvbSwgSW5jLiBvciBpdHMgYWZmaWxpYXRlcy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50IH0gZnJvbSAnYXdzLWxhbWJkYSc7XG5pbXBvcnQgeyBDb25uZWN0Q2FtcGFpZ25zVjJDbGllbnQgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtY29ubmVjdGNhbXBhaWduc3YyJztcbmltcG9ydCB7IEN1c3RvbWVyUHJvZmlsZXNDbGllbnQgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtY3VzdG9tZXItcHJvZmlsZXMnO1xuaW1wb3J0IHsgSUFNQ2xpZW50IH0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LWlhbSc7XG5cbmltcG9ydCB7IGVuc3VyZUluc3RhbmNlT25ib2FyZGVkIH0gZnJvbSAnLi9vbmJvYXJkaW5nLmpzJztcbmltcG9ydCB7XG4gIGFzc29jaWF0ZURvbWFpbixcbiAgZGlzYXNzb2NpYXRlRG9tYWluLFxuICByZXNvbHZlQ2FtcGFpZ25zU2VydmljZUxpbmtlZFJvbGVBcm4sXG59IGZyb20gJy4vaW50ZWdyYXRpb24uanMnO1xuXG4vKiogTW9kdWxlLWxldmVsIGNsaWVudHMgcmV1c2VkIGFjcm9zcyB3YXJtIGludm9jYXRpb25zLiAqL1xuY29uc3QgY2FtcGFpZ25zID0gbmV3IENvbm5lY3RDYW1wYWlnbnNWMkNsaWVudCh7fSk7XG5jb25zdCBwcm9maWxlcyA9IG5ldyBDdXN0b21lclByb2ZpbGVzQ2xpZW50KHt9KTtcbmNvbnN0IGlhbSA9IG5ldyBJQU1DbGllbnQoe30pO1xuXG4vKiogUmVzdWx0IHRoZSBQcm92aWRlciBmcmFtZXdvcmsgZWNob2VzIGJhY2sgdG8gQ2xvdWRGb3JtYXRpb24uICovXG4vKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbmFtaW5nLWNvbnZlbnRpb24gLS0gQ0ZOIGN1c3RvbS1yZXNvdXJjZVxuICAgcHJvdG9jb2wgKyBSZXNvdXJjZVByb3BlcnRpZXMgYXJlIFBhc2NhbENhc2UgYnkgY29udHJhY3QuICovXG5leHBvcnQgdHlwZSBPbkV2ZW50UmVzdWx0ID0ge1xuICBQaHlzaWNhbFJlc291cmNlSWQ6IHN0cmluZztcbiAgRGF0YT86IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH07XG59O1xuXG50eXBlIFByb3BzID0ge1xuICBjb25uZWN0SW5zdGFuY2VJZDogc3RyaW5nO1xuICBkb21haW5OYW1lOiBzdHJpbmc7XG4gIGFjY291bnQ6IHN0cmluZztcbiAgcmVnaW9uOiBzdHJpbmc7XG59O1xuXG5jb25zdCByZWFkUHJvcHMgPSAoZXZlbnQ6IENsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCk6IFByb3BzID0+IHtcbiAgY29uc3QgcCA9IGV2ZW50LlJlc291cmNlUHJvcGVydGllcyBhcyB1bmtub3duIGFzIHtcbiAgICBDb25uZWN0SW5zdGFuY2VJZD86IHN0cmluZztcbiAgICBEb21haW5OYW1lPzogc3RyaW5nO1xuICAgIEFjY291bnQ/OiBzdHJpbmc7XG4gICAgUmVnaW9uPzogc3RyaW5nO1xuICB9O1xuICAvKiBlc2xpbnQtZW5hYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uYW1pbmctY29udmVudGlvbiAqL1xuICBpZiAoIXAuQ29ubmVjdEluc3RhbmNlSWQgfHwgIXAuRG9tYWluTmFtZSB8fCAhcC5BY2NvdW50IHx8ICFwLlJlZ2lvbikge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICdNaXNzaW5nIHJlcXVpcmVkIFJlc291cmNlUHJvcGVydGllczogQ29ubmVjdEluc3RhbmNlSWQsIERvbWFpbk5hbWUsIEFjY291bnQsIFJlZ2lvbicsXG4gICAgKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIGNvbm5lY3RJbnN0YW5jZUlkOiBwLkNvbm5lY3RJbnN0YW5jZUlkLFxuICAgIGRvbWFpbk5hbWU6IHAuRG9tYWluTmFtZSxcbiAgICBhY2NvdW50OiBwLkFjY291bnQsXG4gICAgcmVnaW9uOiBwLlJlZ2lvbixcbiAgfTtcbn07XG5cbmNvbnN0IHBoeXNpY2FsSWQgPSAocHJvcHM6IFByb3BzKTogc3RyaW5nID0+XG4gIGBjYW1wYWlnbi1hc3NvYzoke3Byb3BzLmNvbm5lY3RJbnN0YW5jZUlkfToke3Byb3BzLmRvbWFpbk5hbWV9YDtcblxuLyoqXG4gKiBDbG91ZEZvcm1hdGlvbiBjdXN0b20tcmVzb3VyY2UgaGFuZGxlciAoaW52b2tlZCB2aWEgdGhlIENES1xuICogYGN1c3RvbS1yZXNvdXJjZXMuUHJvdmlkZXJgIGZyYW1ld29yaykgdGhhdCBhc3NvY2lhdGVzIGEgZnJvbS1zY3JhdGNoIEN1c3RvbWVyXG4gKiBQcm9maWxlcyBkb21haW4gd2l0aCBpdHMgQW1hem9uIENvbm5lY3QgaW5zdGFuY2UncyBPdXRib3VuZCBDYW1wYWlnbnMgdjIsIHNvXG4gKiBDb25uZWN0IEpvdXJuZXlzIGNhbiB0YXJnZXQgdGhlIGRvbWFpbidzIHByb2ZpbGVzIFdJVEhPVVQgYW55IG1hbnVhbCBjb25zb2xlXG4gKiBzdGVwLlxuICpcbiAqIENyZWF0ZSAvIFVwZGF0ZSAoaWRlbXBvdGVudCk6IG9uYm9hcmQgdGhlIGluc3RhbmNlIHRvIE91dGJvdW5kIENhbXBhaWducyB2MlxuICogKGNyZWF0ZXMgdGhlIHNlcnZpY2UtbGlua2VkIHJvbGUgKyB3YWl0cyBmb3IgU1VDQ0VFREVEKSwgcmVzb2x2ZSB0aGUgY2FtcGFpZ25zXG4gKiBzZXJ2aWNlLWxpbmtlZCByb2xlLCB0aGVuIGNyZWF0ZSBib3RoIHNpZGVzIG9mIHRoZSBkb21haW4gPC0+IGNhbXBhaWduc1xuICogaW50ZWdyYXRpb24uXG4gKlxuICogRGVsZXRlIChiZXN0LWVmZm9ydCk6IHJldmVyc2UgYm90aCBpbnRlZ3JhdGlvbnMsIHN3YWxsb3dpbmcgZXJyb3JzIHNvIGFcbiAqIHBhcnRpYWwgLyBhbHJlYWR5LXJlbW92ZWQgYXNzb2NpYXRpb24gbmV2ZXIgYmxvY2tzIHN0YWNrIHRlYXJkb3duLlxuICpcbiAqIE9ubHkgd2lyZWQgYnkgdGhlIGNvbnN0cnVjdCBpbiBjcmVhdGUtZnJvbS1zY3JhdGNoIG1vZGUgKHRoZSBjb25zdHJ1Y3Qgb3duc1xuICogdGhlIGluc3RhbmNlICsgZG9tYWluKS4gSW4gYXR0YWNoIG1vZGUgdGhlIGV4aXN0aW5nIGRvbWFpbidzIGFzc29jaWF0aW9uIGlzXG4gKiB0aGUgdXNlcidzIHJlc3BvbnNpYmlsaXR5IGFuZCB0aGlzIHJlc291cmNlIGlzIG5vdCBhZGRlZC5cbiAqL1xuZXhwb3J0IGNvbnN0IGhhbmRsZXIgPSBhc3luYyAoXG4gIGV2ZW50OiBDbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsXG4pOiBQcm9taXNlPE9uRXZlbnRSZXN1bHQ+ID0+IHtcbiAgY29uc29sZS5sb2coJ1tjYW1wYWlnbi1hc3NvY10gZXZlbnQnLCB7XG4gICAgcmVxdWVzdFR5cGU6IGV2ZW50LlJlcXVlc3RUeXBlLFxuICAgIGxvZ2ljYWxSZXNvdXJjZUlkOiBldmVudC5Mb2dpY2FsUmVzb3VyY2VJZCxcbiAgfSk7XG5cbiAgY29uc3QgcHJvcHMgPSByZWFkUHJvcHMoZXZlbnQpO1xuXG4gIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScpIHtcbiAgICBhd2FpdCBkaXNhc3NvY2lhdGVEb21haW4oXG4gICAgICB7IGNhbXBhaWducywgcHJvZmlsZXMgfSxcbiAgICAgIHtcbiAgICAgICAgY29ubmVjdEluc3RhbmNlSWQ6IHByb3BzLmNvbm5lY3RJbnN0YW5jZUlkLFxuICAgICAgICBkb21haW5OYW1lOiBwcm9wcy5kb21haW5OYW1lLFxuICAgICAgICBhY2NvdW50OiBwcm9wcy5hY2NvdW50LFxuICAgICAgICByZWdpb246IHByb3BzLnJlZ2lvbixcbiAgICAgIH0sXG4gICAgKTtcbiAgICByZXR1cm4geyBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB9O1xuICB9XG5cbiAgLy8gQ3JlYXRlICsgVXBkYXRlOiBvbmJvYXJkIChpZGVtcG90ZW50KSB0aGVuIChyZSljcmVhdGUgYm90aCBpbnRlZ3JhdGlvbnMuXG4gIGF3YWl0IGVuc3VyZUluc3RhbmNlT25ib2FyZGVkKGNhbXBhaWducywge1xuICAgIGNvbm5lY3RJbnN0YW5jZUlkOiBwcm9wcy5jb25uZWN0SW5zdGFuY2VJZCxcbiAgfSk7XG4gIGNvbnN0IHJvbGVBcm4gPSBhd2FpdCByZXNvbHZlQ2FtcGFpZ25zU2VydmljZUxpbmtlZFJvbGVBcm4oaWFtKTtcbiAgYXdhaXQgYXNzb2NpYXRlRG9tYWluKFxuICAgIHsgY2FtcGFpZ25zLCBwcm9maWxlcywgaWFtIH0sXG4gICAge1xuICAgICAgY29ubmVjdEluc3RhbmNlSWQ6IHByb3BzLmNvbm5lY3RJbnN0YW5jZUlkLFxuICAgICAgZG9tYWluTmFtZTogcHJvcHMuZG9tYWluTmFtZSxcbiAgICAgIHJvbGVBcm4sXG4gICAgICBhY2NvdW50OiBwcm9wcy5hY2NvdW50LFxuICAgICAgcmVnaW9uOiBwcm9wcy5yZWdpb24sXG4gICAgfSxcbiAgKTtcblxuICByZXR1cm4ge1xuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxJZChwcm9wcyksXG4gICAgRGF0YToge1xuICAgICAgY29ubmVjdEluc3RhbmNlSWQ6IHByb3BzLmNvbm5lY3RJbnN0YW5jZUlkLFxuICAgICAgZG9tYWluTmFtZTogcHJvcHMuZG9tYWluTmFtZSxcbiAgICB9LFxuICB9O1xufTtcbiJdfQ==
|