@robono/server 0.15.0 → 0.17.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/NOTIFICATIONS.md +149 -0
  3. package/README.md +8 -1
  4. package/SBOM.spdx.json +8 -8
  5. package/SCREENING.md +73 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/managed-media.d.ts.map +1 -1
  11. package/dist/managed-media.js +2 -0
  12. package/dist/managed-media.js.map +1 -1
  13. package/dist/messaging-notification-postgres.d.ts +7 -0
  14. package/dist/messaging-notification-postgres.d.ts.map +1 -0
  15. package/dist/messaging-notification-postgres.js +32 -0
  16. package/dist/messaging-notification-postgres.js.map +1 -0
  17. package/dist/messaging-notification-providers.d.ts +26 -0
  18. package/dist/messaging-notification-providers.d.ts.map +1 -0
  19. package/dist/messaging-notification-providers.js +107 -0
  20. package/dist/messaging-notification-providers.js.map +1 -0
  21. package/dist/messaging-notifications.d.ts +66 -0
  22. package/dist/messaging-notifications.d.ts.map +1 -0
  23. package/dist/messaging-notifications.js +128 -0
  24. package/dist/messaging-notifications.js.map +1 -0
  25. package/dist/messaging.d.ts +7 -0
  26. package/dist/messaging.d.ts.map +1 -1
  27. package/dist/messaging.js +40 -2
  28. package/dist/messaging.js.map +1 -1
  29. package/dist/messenger.d.ts +53 -0
  30. package/dist/messenger.d.ts.map +1 -0
  31. package/dist/messenger.js +89 -0
  32. package/dist/messenger.js.map +1 -0
  33. package/dist/screening.d.ts +53 -0
  34. package/dist/screening.d.ts.map +1 -0
  35. package/dist/screening.js +52 -0
  36. package/dist/screening.js.map +1 -0
  37. package/package.json +5 -2
  38. package/sql/messaging-notifications.sql +41 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 — 2026-11-03
4
+
5
+ - Add optional photo/video screening for connected networks and return attachment screening status without holding server requests open.
6
+
7
+
8
+ ## 0.16.0 — 2026-11-03
9
+
10
+ Add createRobonoMessengerServer for one authenticated messaging/media route, plus optional signed-event notification delivery using customer Apple/Google credentials and a durable private PostgreSQL queue. Announce supported group controls; keep actor identity server-verified and recheck customer permissions on search results. Include NOTIFICATIONS.md and the notification schema.
11
+
12
+ Requires matching service migrations 181–186 and coordinated package publication. Physical-device notification delivery is a separate acceptance check.
13
+
3
14
  ## 0.15.0
4
15
 
5
16
  Add authenticated social previews, bounded private thumbnail requests, media replacement, per-user mute/delete-chat operations, and source-revision-aware translation caching.
@@ -25,7 +36,6 @@ Add network-triggered call countdowns, selected-recipient or all-participant war
25
36
  Managed messaging and attachments, Maps and location sessions, movement consent, geofence rules, usage reporting and backend adapters. Existing Bridge and calling entry points remain available.
26
37
 
27
38
 
28
- ## Unreleased
29
39
 
30
40
  - Group admission and relationship helpers, separate screen-sharing permission, group webhook verification/push routing, and an authenticated on-demand message transcription/translation adapter. Source content and private media are resolved server-side; access is rechecked after conversion.
31
41
 
@@ -0,0 +1,149 @@
1
+ # Mobile message notifications
2
+
3
+ This installation uses your app's Apple/Google credentials. Robono's own app
4
+ credentials cannot send alerts for a customer's app. Chat in an open screen works
5
+ without push credentials; alerts with the app closed do not.
6
+
7
+ This guide requires `@robono/server` 0.16.0 and messaging packages 0.3.0,
8
+ with the matching Robono notification service. Older packages do not include
9
+ these helpers.
10
+
11
+ ## Required once per app
12
+
13
+ | Platform | App configuration | Private server configuration |
14
+ | --- | --- | --- |
15
+ | iPhone | Enable Push Notifications for the Bundle ID and provision the app accordingly. | Apple Team ID, APNs Key ID, `.p8` private key, Bundle ID, sandbox/production environment. |
16
+ | Android | Firebase project, Android application ID and `google-services.json`; configure the notifications plugin and rebuild. | Google project ID and service account authorized for Firebase Cloud Messaging. Prefer your existing secret manager; never bundle its private key in the app. |
17
+
18
+ Apple setup: https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns
19
+
20
+ Expo native-token setup: https://docs.expo.dev/push-notifications/sending-notifications-custom/
21
+
22
+ Google setup: https://firebase.google.com/docs/cloud-messaging/send/v1-api
23
+
24
+ The supplied Expo connector uses native APNs tokens on iPhone and FCM tokens on
25
+ Android. It does not use Expo Push tokens. Development iPhone builds use the APNs
26
+ sandbox; TestFlight and App Store builds use production. Keep test/live credentials,
27
+ notification scopes and webhook secrets separate.
28
+
29
+ ## Server
30
+
31
+ Apply `@robono/server/sql/messaging-notifications.sql` to your **own PostgreSQL
32
+ database**, using the private server role. The tables have row security enabled and
33
+ no public grants. They must not be exposed through your browser/mobile database API.
34
+ Use your database's encrypted storage and normal backup policy. Device tokens are
35
+ private routing identifiers; the notification signing keys remain in server secrets.
36
+
37
+ ```ts
38
+ import {createRobonoMessengerServer} from '@robono/server';
39
+
40
+ const messenger = createRobonoMessengerServer({
41
+ apiKey: process.env.ROBONO_API_KEY!,
42
+ allowedOrigins: ['https://your-company.com'],
43
+ authenticate: request => accounts.verifySession(request),
44
+ authorize: access => permissions.allowMessaging(access),
45
+ notifications: {
46
+ scope: 'your-app-production',
47
+ webhookSecret: process.env.ROBONO_WEBHOOK_SECRET!,
48
+ workerSecret: process.env.ROBONO_NOTIFICATION_WORKER_SECRET!,
49
+ query: (sql, values) => database.query(sql, values),
50
+ title: 'Your company',
51
+ apple: {
52
+ teamId: process.env.APNS_TEAM_ID!,
53
+ keyId: process.env.APNS_KEY_ID!,
54
+ privateKey: process.env.APNS_PRIVATE_KEY!,
55
+ bundleId: 'com.yourcompany.app',
56
+ sandbox: false,
57
+ },
58
+ google: {
59
+ projectId: process.env.FIREBASE_PROJECT_ID!,
60
+ clientEmail: process.env.FIREBASE_CLIENT_EMAIL!,
61
+ privateKey: process.env.FIREBASE_PRIVATE_KEY!,
62
+ },
63
+ authorizeRecipient: ({userId, conversationId}) =>
64
+ accounts.isActive(userId).then(active => active &&
65
+ permissions.mayReadConversation(userId, conversationId)),
66
+ },
67
+ });
68
+
69
+ // Your framework mounts this single POST route at /api/robono.
70
+ export const POST = (request: Request) => messenger.handle(request);
71
+ ```
72
+
73
+ `accounts`, `permissions`, and `database` are the customer's existing services.
74
+ Their checks must return a verified user ID or an explicit permission decision.
75
+ Do not replace them with unconditional approval. Omit `apple` or `google` only if
76
+ you do not support that platform. Missing credentials produce a registration error.
77
+
78
+ Set your Robono integration's signed event URL to:
79
+ `https://your-company.com/api/robono?service=events`.
80
+ If you already process other Robono events at one endpoint, verify/route those
81
+ events in your existing handler; this notification handler acknowledges unrelated
82
+ events without processing them and must not replace your cleanup/billing handler.
83
+
84
+ Schedule a POST to `/api/robono?service=notification-worker` at your chosen alert
85
+ interval, with `Authorization: Bearer <ROBONO_NOTIFICATION_WORKER_SECRET>`.
86
+ Use an independent random secret of at least 32 characters. A one-minute schedule
87
+ can delay an alert by a minute; use a continuously running worker for prompt alerts.
88
+ The worker processes up to 20 due jobs per invocation. Multiple workers safely
89
+ claim different jobs. Run the SQL file's cleanup statements daily.
90
+
91
+ The signed event is acknowledged only after it is durably queued. Every send
92
+ checks the current account, conversation, message, blocks, mute preference and
93
+ device ownership. Transient failures retry with backoff; permanent failures remain
94
+ in the private `jobs` table with a safe error code. Monitor failed jobs and credential
95
+ errors. Old alerts expire after 24 hours. Apple/Google acceptance does not mean the
96
+ phone displayed the alert; do not use it as a delivered/read receipt. Provider
97
+ timeouts can leave delivery uncertain; stable collapse identifiers reduce repeated
98
+ alerts, but exact-once OS notification display cannot be promised.
99
+
100
+ ## Expo / React Native
101
+
102
+ Install `expo-notifications`, `expo-secure-store` and the package's existing
103
+ `expo-crypto` dependency using `npx expo install`. Configure the Expo notifications
104
+ plugin, then build the app; Expo Go is not the production verification target.
105
+
106
+ ```ts
107
+ import {connectExpoMessagingNotifications} from
108
+ '@robono/messaging-react-native/expo-notifications';
109
+
110
+ const notifications = await connectExpoMessagingNotifications({
111
+ url: 'https://your-company.com/api/robono?service=notifications',
112
+ userId: signedInUser.id,
113
+ getAccessToken: () => accounts.accessToken(),
114
+ authorize: event => messagingClient.canOpenNotification(event),
115
+ open: event => navigation.openConversation(event.conversationId),
116
+ refresh: () => inbox.refresh(),
117
+ onError: error => showNotificationSetupError(error.message),
118
+ });
119
+
120
+ // When the person taps your "Enable notifications" button:
121
+ await notifications.enable();
122
+
123
+ // On logout, before destroying the session:
124
+ await Promise.allSettled([notifications.signOut(), messagingClient.signOut()]);
125
+ await accounts.signOut();
126
+ ```
127
+
128
+ Connect once per restored account. On navigation, keep the connector at app level;
129
+ do not recreate it for every chat. `dispose()` only removes listeners; use
130
+ `signOut()` to unregister the old account's device. If unregistering fails while
131
+ offline, clear local session data anyway and revoke that server session/account
132
+ access according to your existing sign-out policy. Alerts contain no message text,
133
+ media URLs or credentials. New registration moves the native token to the new
134
+ account, and notification opening always rechecks authorization.
135
+
136
+ The helper does not replace your app's global notification presentation handler
137
+ or interfere with calling notifications. Compose
138
+ `withMessagingNotificationPresentation` with that existing handler to suppress an
139
+ alert for the conversation already on screen. Set its active conversation to null
140
+ when the app is backgrounded. Browser background alerts are a separate capability;
141
+ these mobile credentials do not enable them.
142
+
143
+ ## Ready means a device received an alert
144
+
145
+ Check both a foreground and background alert, a tap from a closed app, sign-out,
146
+ account switching, revoked chat access, muted conversations and token rotation.
147
+ Do not label notification setup complete merely because open-chat messages work
148
+ or Apple/Google accepted a request. The local automated tests use simulated provider
149
+ responses; they do not prove that a customer's credentials are provisioned correctly.
package/README.md CHANGED
@@ -341,7 +341,7 @@ See [calling limits, billing rules and recovery](https://robono.com/docs#calling
341
341
  for defaults, alert fields, revision conflicts, retry timing and active-call behavior.
342
342
 
343
343
 
344
- ### Sender message changes (unreleased)
344
+ ### Sender message changes
345
345
 
346
346
  The updated messaging adapter passes `edit`, `delete`, and `consume` through your
347
347
  existing `authenticate` and `authorize` callbacks, always overriding caller-supplied
@@ -373,3 +373,10 @@ synchronizing messages, but do not show a banner, play a sound or send a push wh
373
373
  this flag is true. Its value is refreshed on webhook retries. Do not treat mute as
374
374
  loss of membership or stop delivery. The host owns OS push notifications and must
375
375
  honor this field in its notification sender.
376
+
377
+ ## Optional photo and video screening
378
+
379
+ Connected networks can use Robono-hosted Amazon screening with their existing
380
+ Processing API key, including apps with their own storage. See [screening setup](https://robono.com/docs#screening)
381
+ for the private-upload flow, prices, spending controls and pending-result handling.
382
+ This addition is not in server 0.16.0; update to the screening release before use.
package/SBOM.spdx.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@robono/server@0.15.0",
6
- "documentNamespace": "http://spdx.org/spdxdocs/@robono%2fserver-0.15.0-***",
5
+ "name": "@robono/server@0.17.0",
6
+ "documentNamespace": "http://spdx.org/spdxdocs/@robono%2fserver-0.17.0-***",
7
7
  "creationInfo": {
8
- "created": "2026-11-01T13:29:30.968Z",
8
+ "created": "2026-11-03T15:35:51.400Z",
9
9
  "creators": [
10
10
  "Tool: npm/cli-11.6.2"
11
11
  ]
12
12
  },
13
13
  "documentDescribes": [
14
- "SPDXRef-Package-robono.server-0.15.0"
14
+ "SPDXRef-Package-robono.server-0.17.0"
15
15
  ],
16
16
  "packages": [
17
17
  {
18
18
  "name": "@robono/server",
19
- "SPDXID": "SPDXRef-Package-robono.server-0.15.0",
20
- "versionInfo": "0.15.0",
19
+ "SPDXID": "SPDXRef-Package-robono.server-0.17.0",
20
+ "versionInfo": "0.17.0",
21
21
  "packageFileName": "",
22
22
  "description": "Provider-neutral server SDK for the Robono Bridge API.",
23
23
  "primaryPackagePurpose": "LIBRARY",
@@ -29,7 +29,7 @@
29
29
  {
30
30
  "referenceCategory": "PACKAGE-MANAGER",
31
31
  "referenceType": "purl",
32
- "referenceLocator": "pkg:npm/%40robono/server@0.15.0"
32
+ "referenceLocator": "pkg:npm/%40robono/server@0.17.0"
33
33
  }
34
34
  ]
35
35
  }
@@ -37,7 +37,7 @@
37
37
  "relationships": [
38
38
  {
39
39
  "spdxElementId": "SPDXRef-DOCUMENT",
40
- "relatedSpdxElement": "SPDXRef-Package-robono.server-0.15.0",
40
+ "relatedSpdxElement": "SPDXRef-Package-robono.server-0.17.0",
41
41
  "relationshipType": "DESCRIBES"
42
42
  }
43
43
  ]
package/SCREENING.md ADDED
@@ -0,0 +1,73 @@
1
+ # Photo and video screening for connected networks
2
+
3
+ Available in the upcoming server release (not present in server 0.16.0).
4
+
5
+ A connected network can use Robono's Amazon screening without replacing its own
6
+ chat interface or attachment storage. Do not put your network API key in a phone
7
+ app or website. No customer AWS account, Amazon keys or extra AWS server is needed.
8
+
9
+ 1. In Robono's **Features & preview → Photo and video screening**, choose categories,
10
+ sensitivity and a monthly limit, then accept the prices and processing terms.
11
+ 2. Use an existing backend API key with **Processing** permission.
12
+ 3. Keep attachments private until screening returns `allowed` for the exact bytes.
13
+
14
+ ```ts
15
+ import {RobonoScreening} from '@robono/server';
16
+ const screening = new RobonoScreening({apiKey: process.env.ROBONO_API_KEY!});
17
+ // Authenticate your own user and authorize their upload before this call.
18
+ const job = await screening.upload(stableUploadId, {
19
+ kind: 'image', fileName: 'photo.jpg', mimeType: 'image/jpeg', byteSize: bytes.length,
20
+ }, bytes);
21
+ // Store job.id against your private upload. Return promptly; poll status later.
22
+ const result = await screening.status(job.id);
23
+ if (result.state === 'allowed') {
24
+ await screening.assertAllowed(job.id, bytes); // checks current policy + SHA-256
25
+ // Publish these exact bytes to the recipients your app has authorized.
26
+ }
27
+ ```
28
+
29
+ Videos use `kind: 'video'`, `mimeType: 'video/mp4'` or `video/quicktime`, and
30
+ `durationMs`. Server verification checks duration; a false short duration fails.
31
+ Video analysis can take minutes. Show "Checking attachment" while pending, allow
32
+ canceling, and keep ordinary chat usable. `blocked` and `failed` must never release
33
+ the file. Store a stable request ID across network retries. A changed file needs a
34
+ new request ID. A policy change invalidates approval for future publication.
35
+
36
+ For direct HTTPS integration, POST to `https://api.robono.com/v1/screening` with
37
+ `Authorization: Bearer YOUR_SERVER_KEY`. Operations:
38
+
39
+ - `prepare`: `{operation:'prepare', requestId, file}`. Returns job and private PUT
40
+ upload instructions. Send only the returned storage headers to the upload URL.
41
+ - `submit`: `{operation:'submit', id}` after upload.
42
+ - `status`: `{operation:'status', id}`. Pending is not approval.
43
+
44
+ Responses include `id`, `state`, `categories`, `sha256`, `chargedMicros`,
45
+ `reservedMicros` and timestamps. Amounts are USD millionths. Do not trust a client
46
+ phone's claim that it passed screening; read the result on your backend. Never
47
+ publish a mutable URL whose contents can change after the check.
48
+
49
+ ## Robono-hosted attachments
50
+
51
+ With the updated server and supplied web/mobile packages, existing managed uploads
52
+ are automatically checked before delivery. The database also rejects direct sends
53
+ of unchecked photos/videos. Enabled screening requires managed storage for hosted
54
+ messaging; private external storage can use the standalone flow above. Older SDKs
55
+ must be updated before enabling screening. No extra per-chat switches.
56
+
57
+ ## Formats, costs and privacy
58
+
59
+ JPEG/PNG up to 15 MiB. MP4/MOV up to 32 MiB and 15 minutes. Other formats fail rather
60
+ than bypass screening. These are visual checks, not audio/profanity checks, and
61
+ can produce missed content or mistaken blocks.
62
+
63
+ Release prices: $3/1,000 photos and $0.20 per started video minute, no monthly fee.
64
+ Successful checks count even if blocked. Failed checks are uncharged. The same
65
+ accepted job is not billed twice. New work stops at the network screening cap or
66
+ overall spending cap. Job status expires after 30 days; upload a new copy for a new
67
+ check. Keep the originally screened bytes private while a check is pending.
68
+
69
+ Only selected media goes to Amazon Rekognition in Northern Virginia. Robono opts
70
+ out of AWS using it for AI-service improvement. Private temporary screening copies
71
+ are removed after completion, with a one-day S3 expiry backstop. File names and
72
+ hashes are cleared after 30 days; aggregate usage/billing records remain. AWS
73
+ operational retention follows its terms. See Robono's provider disclosure.
package/dist/index.d.ts CHANGED
@@ -19,4 +19,11 @@ export { createMessagingTransformBackendAdapter } from './messaging-transforms.j
19
19
  export { RobonoLocationBackend, createLocationBackendAdapter } from './location.js';
20
20
  export type { LocationGrantInput } from './location.js';
21
21
  export { RobonoLocationChatBackend } from './location-chat.js';
22
+ export { createRobonoMessengerServer } from './messenger.js';
23
+ export type { RobonoMessengerServerOptions, MessengerAccess } from './messenger.js';
24
+ export * from './messaging-notifications.js';
25
+ export * from './messaging-notification-providers.js';
26
+ export * from './messaging-notification-postgres.js';
27
+ export { RobonoScreening } from './screening.js';
28
+ export type { ScreeningFile, ScreeningResult } from './screening.js';
22
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrI,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtE,mBAAmB,YAAY,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,OAAO,EAAC,kCAAkC,EAAC,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAC,uBAAuB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACtI,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAC,sCAAsC,EAAC,MAAM,2BAA2B,CAAC;AAEjF,OAAO,EAAC,qBAAqB,EAAC,4BAA4B,EAAC,MAAM,eAAe,CAAC;AACjF,YAAY,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACtD,OAAO,EAAC,yBAAyB,EAAC,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrI,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtE,mBAAmB,YAAY,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AACvF,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,OAAO,EAAC,kCAAkC,EAAC,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAC,uBAAuB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACtI,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAC,sCAAsC,EAAC,MAAM,2BAA2B,CAAC;AAEjF,OAAO,EAAC,qBAAqB,EAAC,4BAA4B,EAAC,MAAM,eAAe,CAAC;AACjF,YAAY,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACtD,OAAO,EAAC,yBAAyB,EAAC,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAC,2BAA2B,EAAC,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAC,4BAA4B,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAEjF,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AAErD,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAC,aAAa,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -11,4 +11,9 @@ export { RobonoManagedMedia } from './managed-media.js';
11
11
  export { createMessagingTransformBackendAdapter } from './messaging-transforms.js';
12
12
  export { RobonoLocationBackend, createLocationBackendAdapter } from './location.js';
13
13
  export { RobonoLocationChatBackend } from './location-chat.js';
14
+ export { createRobonoMessengerServer } from './messenger.js';
15
+ export * from './messaging-notifications.js';
16
+ export * from './messaging-notification-providers.js';
17
+ export * from './messaging-notification-postgres.js';
18
+ export { RobonoScreening } from './screening.js';
14
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAM1D,OAAO,EACL,yBAAyB,EACzB,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGtE,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAGvF,OAAO,EAAC,kCAAkC,EAAC,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAC,sCAAsC,EAAC,MAAM,2BAA2B,CAAC;AAEjF,OAAO,EAAC,qBAAqB,EAAC,4BAA4B,EAAC,MAAM,eAAe,CAAC;AAEjF,OAAO,EAAC,yBAAyB,EAAC,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAM1D,OAAO,EACL,yBAAyB,EACzB,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGtE,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAGvF,OAAO,EAAC,kCAAkC,EAAC,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAC,sCAAsC,EAAC,MAAM,2BAA2B,CAAC;AAEjF,OAAO,EAAC,qBAAqB,EAAC,4BAA4B,EAAC,MAAM,eAAe,CAAC;AAEjF,OAAO,EAAC,yBAAyB,EAAC,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAC,2BAA2B,EAAC,MAAM,gBAAgB,CAAC;AAG3D,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AAErD,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"managed-media.d.ts","sourceRoot":"","sources":["../src/managed-media.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACvF,yFAAyF;AACzF,qBAAa,kBAAmB,YAAW,uBAAuB;IAErD,OAAO,CAAC,OAAO;IAD3B,OAAO,CAAC,GAAG,CAAQ;gBACC,OAAO,EAAC;QAAC,MAAM,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC;QAAA,KAAK,CAAC,EAAC,OAAO,KAAK,CAAA;KAAC;YAKjE,OAAO;IAMrB,OAAO,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACzG,QAAQ,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAKxH,QAAQ,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAClH,iBAAiB,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,SAAS,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC;QAAC,GAAG,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC;QAAA,KAAK,EAAC,oBAAoB,CAAA;KAAC,CAAC;IAChK,SAAS,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,SAAS,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC;QAAC,GAAG,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC;QAAA,KAAK,EAAC,oBAAoB,CAAA;KAAC,GAAC,IAAI,CAAC;IACvJ,OAAO,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,OAAO,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC,oBAAoB,GAAC,IAAI,CAAC;CAG7G"}
1
+ {"version":3,"file":"managed-media.d.ts","sourceRoot":"","sources":["../src/managed-media.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACvF,yFAAyF;AACzF,qBAAa,kBAAmB,YAAW,uBAAuB;IAErD,OAAO,CAAC,OAAO;IAD3B,OAAO,CAAC,GAAG,CAAQ;gBACC,OAAO,EAAC;QAAC,MAAM,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC;QAAA,KAAK,CAAC,EAAC,OAAO,KAAK,CAAA;KAAC;YAKjE,OAAO;IAOrB,OAAO,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACzG,QAAQ,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAKxH,QAAQ,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAE,UAAU,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAClH,iBAAiB,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,SAAS,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC;QAAC,GAAG,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC;QAAA,KAAK,EAAC,oBAAoB,CAAA;KAAC,CAAC;IAChK,SAAS,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,SAAS,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC;QAAC,GAAG,EAAC,MAAM,CAAC;QAAA,OAAO,CAAC,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC;QAAA,KAAK,EAAC,oBAAoB,CAAA;KAAC,GAAC,IAAI,CAAC;IACvJ,OAAO,CAAC,KAAK,EAAC,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAK,EAAC;QAAC,OAAO,EAAC,MAAM,CAAC;QAAA,cAAc,EAAC,MAAM,CAAC;QAAA,OAAO,EAAC,MAAM,CAAA;KAAC,GAAE,OAAO,CAAC,oBAAoB,GAAC,IAAI,CAAC;CAG7G"}
@@ -13,6 +13,8 @@ export class RobonoManagedMedia {
13
13
  async request(operation, input) {
14
14
  const response = await (this.options.fetch ?? fetch)(this.url, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(operation === 'complete' ? 120000 : 15000), headers: { authorization: `Bearer ${this.options.apiKey}`, 'content-type': 'application/json' }, body: JSON.stringify({ ...input, operation }) });
15
15
  const data = await response.json().catch(() => null);
16
+ if (response.status === 202 && data?.error?.code === 'screening_pending')
17
+ throw new RobonoError('This attachment is being checked before delivery.', { status: 202, code: 'screening_pending', retryable: true });
16
18
  if (!response.ok)
17
19
  throw new RobonoError(data?.error?.message ?? 'Attachment request failed.', { status: response.status, code: data?.error?.code ?? 'media_unavailable', retryable: response.status === 429 || response.status >= 500 });
18
20
  return data;
@@ -1 +1 @@
1
- {"version":3,"file":"managed-media.js","sourceRoot":"","sources":["../src/managed-media.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"managed-media.js","sourceRoot":"","sources":["../src/managed-media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { MessagingNotificationStore } from './messaging-notifications.js';
2
+ /** Compatible with pg Pool.query; keep this database connection exclusively on your server. */
3
+ export type MessagingNotificationQuery = (sql: string, parameters: unknown[]) => Promise<{
4
+ rows: Record<string, any>[];
5
+ }>;
6
+ export declare function createPostgresMessagingNotificationStore(query: MessagingNotificationQuery): MessagingNotificationStore;
7
+ //# sourceMappingURL=messaging-notification-postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging-notification-postgres.d.ts","sourceRoot":"","sources":["../src/messaging-notification-postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,0BAA0B,EAAsC,MAAM,8BAA8B,CAAC;AAElH,+FAA+F;AAC/F,MAAM,MAAM,0BAA0B,GAAC,CAAC,GAAG,EAAC,MAAM,EAAC,UAAU,EAAC,OAAO,EAAE,KAAG,OAAO,CAAC;IAAC,IAAI,EAAC,MAAM,CAAC,MAAM,EAAC,GAAG,CAAC,EAAE,CAAA;CAAC,CAAE,CAAC;AAChH,wBAAgB,wCAAwC,CAAC,KAAK,EAAC,0BAA0B,GAAE,0BAA0B,CA8BpH"}
@@ -0,0 +1,32 @@
1
+ export function createPostgresMessagingNotificationStore(query) {
2
+ return {
3
+ async register(scope, device) {
4
+ // Serialize registrations for this token; move it away from a previous signed-in account.
5
+ await query(`select robono_notifications.register_device($1,$2,$3,$4,$5)`, [scope, device.id, device.userId, device.provider, device.token]);
6
+ },
7
+ async remove(scope, userId, id, token) { await query('delete from robono_notifications.devices where scope=$1 and user_id=$2 and device_id=$3 and ($4::text is null or token=$4)', [scope, userId, id, token ?? null]); },
8
+ async enqueue(scope, event) {
9
+ await query(`insert into robono_notifications.jobs(scope,event_id,device_id,user_id,provider,token,event)
10
+ select $1,$2,device_id,user_id,provider,token,$4::jsonb from robono_notifications.devices
11
+ where scope=$1 and user_id=$3 and updated_at>now()-interval '90 days'
12
+ on conflict(scope,event_id,device_id,user_id) do nothing`, [scope, event.event_id, event.recipient_external_user_id, JSON.stringify(event)]);
13
+ },
14
+ async claim(scope, limit, lease) {
15
+ const result = await query(`with ready as (
16
+ select id from robono_notifications.jobs where scope=$1 and
17
+ ((state='pending' and next_attempt_at<=now()) or (state='running' and lease_until<=now()))
18
+ order by next_attempt_at,id for update skip locked limit $2
19
+ ) update robono_notifications.jobs j set state='running',lease=$3,lease_until=now()+interval '10 minutes',attempt=attempt+1
20
+ from ready where j.id=ready.id returning j.*`, [scope, limit, lease]);
21
+ return result.rows.map(row => ({ id: String(row.id), lease, event: row.event, attempt: row.attempt,
22
+ device: { id: row.device_id, userId: row.user_id, provider: row.provider, token: row.token } }));
23
+ },
24
+ async current(scope, device) { return (await query(`select 1 from robono_notifications.devices where scope=$1 and device_id=$2 and user_id=$3 and provider=$4 and token=$5 and updated_at>now()-interval '90 days'`, [scope, device.id, device.userId, device.provider, device.token])).rows.length === 1; },
25
+ async finish(scope, job, result) {
26
+ await query(`update robono_notifications.jobs set state=$4,next_attempt_at=coalesce($5,next_attempt_at),lease=null,lease_until=null,
27
+ last_code=$6,finished_at=case when $4 in ('sent','skipped','failed') then now() else null end
28
+ where scope=$1 and id=$2 and lease=$3 and state='running'`, [scope, job.id, job.lease, result.state === 'retry' ? 'pending' : result.state, result.retryAt ?? null, result.code ?? null]);
29
+ },
30
+ };
31
+ }
32
+ //# sourceMappingURL=messaging-notification-postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging-notification-postgres.js","sourceRoot":"","sources":["../src/messaging-notification-postgres.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,wCAAwC,CAAC,KAAgC;IACvF,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAC,MAAM;YACzB,0FAA0F;YAC1F,MAAM,KAAK,CAAC,6DAA6D,EAAC,CAAC,KAAK,EAAC,MAAM,CAAC,EAAE,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,QAAQ,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1I,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE,EAAC,KAAK,IAAE,MAAM,KAAK,CAAC,4HAA4H,EAAC,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE,EAAC,KAAK,IAAE,IAAI,CAAC,CAAC,CAAC,CAAA,CAAC;QAC7M,KAAK,CAAC,OAAO,CAAC,KAAK,EAAC,KAAK;YACvB,MAAM,KAAK,CAAC;;;iEAG+C,EAAC,CAAC,KAAK,EAAC,KAAK,CAAC,QAAQ,EAAC,KAAK,CAAC,0BAA0B,EAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7I,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,KAAK,EAAC,KAAK,EAAC,KAAK;YAC3B,MAAM,MAAM,GAAC,MAAM,KAAK,CAAC;;;;;qDAKsB,EAAC,CAAC,KAAK,EAAC,KAAK,EAAC,KAAK,CAAC,CAAC,CAAC;YACrE,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA,EAAE,CAAA,CAAC,EAAC,EAAE,EAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAC,KAAK,EAAC,KAAK,EAAC,GAAG,CAAC,KAAK,EAAC,OAAO,EAAC,GAAG,CAAC,OAAO;gBACvF,MAAM,EAAC,EAAC,EAAE,EAAC,GAAG,CAAC,SAAS,EAAC,MAAM,EAAC,GAAG,CAAC,OAAO,EAAC,QAAQ,EAAC,GAAG,CAAC,QAAQ,EAAC,KAAK,EAAC,GAAG,CAAC,KAAK,EAAC,EAAC,CAAC,CAAuB,CAAC;QAChH,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,KAAK,EAAC,MAAM,IAAE,OAAO,CAAC,MAAM,KAAK,CAAC,gKAAgK,EAAC,CAAC,KAAK,EAAC,MAAM,CAAC,EAAE,EAAC,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,QAAQ,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAG,CAAC,CAAC,CAAA,CAAC;QACjS,KAAK,CAAC,MAAM,CAAC,KAAK,EAAC,GAAG,EAAC,MAAM;YAC3B,MAAM,KAAK,CAAC;;kEAEgD,EAAC,CAAC,KAAK,EAAC,GAAG,CAAC,EAAE,EAAC,GAAG,CAAC,KAAK,EAAC,MAAM,CAAC,KAAK,KAAG,OAAO,CAAA,CAAC,CAAA,SAAS,CAAA,CAAC,CAAA,MAAM,CAAC,KAAK,EAAC,MAAM,CAAC,OAAO,IAAE,IAAI,EAAC,MAAM,CAAC,IAAI,IAAE,IAAI,CAAC,CAAC,CAAC;QAC9K,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { MessagingPushDevice, MessagingPushData, MessagingNotificationOptions } from './messaging-notifications.js';
2
+ export interface AppleMessagingCredentials {
3
+ teamId: string;
4
+ keyId: string;
5
+ privateKey: string;
6
+ bundleId: string;
7
+ sandbox?: boolean;
8
+ }
9
+ export interface GoogleMessagingCredentials {
10
+ projectId: string;
11
+ clientEmail: string;
12
+ privateKey: string;
13
+ }
14
+ type SendResult = Awaited<ReturnType<MessagingNotificationOptions['send']>>;
15
+ /** Uses only fixed Apple/Google endpoints. Private keys never enter notification payloads. */
16
+ export declare function createMessagingPushSender(options: {
17
+ apple?: AppleMessagingCredentials;
18
+ google?: GoogleMessagingCredentials;
19
+ title?: string;
20
+ fetch?: typeof fetch;
21
+ }): {
22
+ providers: ("apns" | "fcm")[];
23
+ send(device: MessagingPushDevice, data: MessagingPushData): Promise<SendResult>;
24
+ };
25
+ export {};
26
+ //# sourceMappingURL=messaging-notification-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging-notification-providers.d.ts","sourceRoot":"","sources":["../src/messaging-notification-providers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,mBAAmB,EAAC,iBAAiB,EAAC,4BAA4B,EAAC,MAAM,8BAA8B,CAAC;AAErH,MAAM,WAAW,yBAAyB;IAAE,MAAM,EAAC,MAAM,CAAC;IAAA,KAAK,EAAC,MAAM,CAAC;IAAA,UAAU,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAC;IAAA,OAAO,CAAC,EAAC,OAAO,CAAA;CAAC;AAC1H,MAAM,WAAW,0BAA0B;IAAE,SAAS,EAAC,MAAM,CAAC;IAAA,WAAW,EAAC,MAAM,CAAC;IAAA,UAAU,EAAC,MAAM,CAAA;CAAC;AACnG,KAAK,UAAU,GAAC,OAAO,CAAC,UAAU,CAAC,4BAA4B,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAS1E,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,OAAO,EAAC;IAAC,KAAK,CAAC,EAAC,yBAAyB,CAAC;IAAA,MAAM,CAAC,EAAC,0BAA0B,CAAC;IAAA,KAAK,CAAC,EAAC,MAAM,CAAC;IAAA,KAAK,CAAC,EAAC,OAAO,KAAK,CAAA;CAAC;;iBAsDrI,mBAAmB,QAAM,iBAAiB,GAAE,OAAO,CAAC,UAAU,CAAC;EAKlF"}
@@ -0,0 +1,107 @@
1
+ import { createHash, createPrivateKey, sign } from 'node:crypto';
2
+ import { connect } from 'node:http2';
3
+ const b64 = (data) => Buffer.from(JSON.stringify(data)).toString('base64url');
4
+ function jwt(header, claims, key, algorithm, p1363 = false) {
5
+ const body = b64(header) + '.' + b64(claims);
6
+ const signature = sign(algorithm, Buffer.from(body), { key: createPrivateKey(key), ...(p1363 ? { dsaEncoding: 'ieee-p1363' } : {}) });
7
+ return body + '.' + signature.toString('base64url');
8
+ }
9
+ function collapse(data) { return createHash('sha256').update(data.accountScope + ':' + data.conversationId).digest('hex'); }
10
+ /** Uses only fixed Apple/Google endpoints. Private keys never enter notification payloads. */
11
+ export function createMessagingPushSender(options) {
12
+ const title = options.title?.trim() || 'New message';
13
+ if (title.length > 80)
14
+ throw Error('Use a notification title of 80 characters or fewer.');
15
+ const apple = options.apple, google = options.google;
16
+ if (apple && (!/^[A-Z\d]{10}$/.test(apple.teamId) || !/^[A-Z\d]{10}$/.test(apple.keyId) || !/^[-\w]+(?:\.[-\w]+)+$/.test(apple.bundleId)))
17
+ throw Error('Check the Apple Team ID, Key ID and app bundle ID.');
18
+ if (google && (!/^[a-z][a-z\d-]{4,61}[a-z\d]$/.test(google.projectId) || !/^[^\s@]+@[^\s@]+\.gserviceaccount\.com$/.test(google.clientEmail)))
19
+ throw Error('Check the Google project ID and service-account email.');
20
+ if (apple && (createPrivateKey(apple.privateKey).asymmetricKeyType !== 'ec' || createPrivateKey(apple.privateKey).asymmetricKeyDetails?.namedCurve !== 'prime256v1'))
21
+ throw Error('Use the Apple APNs .p8 signing key.');
22
+ if (google && createPrivateKey(google.privateKey).asymmetricKeyType !== 'rsa')
23
+ throw Error('Use the Google service-account private key.');
24
+ let appleToken = { value: '', expires: 0 }, googleToken = { value: '', expires: 0 }, googlePending;
25
+ const request = options.fetch ?? fetch;
26
+ async function googleAccess() {
27
+ if (googleToken.expires > Date.now())
28
+ return googleToken.value;
29
+ if (googlePending)
30
+ return googlePending;
31
+ googlePending = (async () => {
32
+ const now = Math.floor(Date.now() / 1000), assertion = jwt({ alg: 'RS256', typ: 'JWT' }, { iss: google.clientEmail, scope: 'https://www.googleapis.com/auth/firebase.messaging', aud: 'https://oauth2.googleapis.com/token', iat: now, exp: now + 3600 }, google.privateKey, 'RSA-SHA256');
33
+ const response = await request('https://oauth2.googleapis.com/token', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15000), headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion }) });
34
+ const data = await response.json();
35
+ if (!response.ok || !data.access_token || typeof data.expires_in !== 'number')
36
+ throw Error('Google notification authentication failed.');
37
+ googleToken = { value: data.access_token, expires: Date.now() + Math.max(0, Math.min(data.expires_in, 3600) - 60) * 1000 };
38
+ return data.access_token;
39
+ })();
40
+ try {
41
+ return await googlePending;
42
+ }
43
+ finally {
44
+ googlePending = undefined;
45
+ }
46
+ }
47
+ async function sendApple(device, data) {
48
+ if (!apple)
49
+ return { accepted: false, retryable: false, code: 'apple_credentials_missing' };
50
+ if (!/^[a-f\d]{64,200}$/i.test(device.token))
51
+ return { accepted: false, invalidDevice: true, code: 'invalid_token' };
52
+ if (appleToken.expires <= Date.now())
53
+ appleToken = { value: jwt({ alg: 'ES256', kid: apple.keyId }, { iss: apple.teamId, iat: Math.floor(Date.now() / 1000) }, apple.privateKey, 'sha256', true), expires: Date.now() + 45 * 60000 };
54
+ const payload = JSON.stringify({ aps: { alert: { title, body: 'Open the app to read your message.' }, sound: 'default', 'thread-id': collapse(data) }, body: data });
55
+ return new Promise(resolve => {
56
+ const session = connect(apple.sandbox ? 'https://api.sandbox.push.apple.com' : 'https://api.push.apple.com');
57
+ let finished = false;
58
+ const finish = (result) => { if (finished)
59
+ return; finished = true; clearTimeout(timer); session.destroy(); resolve(result); };
60
+ const timer = setTimeout(() => finish({ accepted: false, retryable: true, code: 'apple_timeout' }), 15000);
61
+ session.on('error', () => finish({ accepted: false, retryable: true, code: 'apple_connection_failed' }));
62
+ const stream = session.request({ ':method': 'POST', ':path': '/3/device/' + device.token, authorization: 'bearer ' + appleToken.value, 'apns-topic': apple.bundleId, 'apns-push-type': 'alert', 'apns-priority': '10', 'apns-expiration': String(Math.floor(Date.now() / 1000) + 3600), 'apns-collapse-id': collapse(data), 'content-type': 'application/json' });
63
+ let status = 0, body = '';
64
+ stream.setEncoding('utf8');
65
+ stream.on('response', headers => { status = Number(headers[':status']); });
66
+ stream.on('data', chunk => { if (body.length < 8192)
67
+ body += chunk; });
68
+ stream.on('error', () => finish({ accepted: false, retryable: true, code: 'apple_connection_failed' }));
69
+ stream.on('end', () => {
70
+ let reason = '';
71
+ try {
72
+ reason = JSON.parse(body).reason;
73
+ }
74
+ catch { }
75
+ if (status === 403)
76
+ appleToken = { value: '', expires: 0 };
77
+ finish({ accepted: status === 200, invalidDevice: status === 410 || reason === 'BadDeviceToken', retryable: reason === 'ExpiredProviderToken' || status === 429 || status >= 500, code: status === 200 ? 'accepted' : status === 403 ? 'apple_credentials_rejected' : 'apple_rejected' });
78
+ });
79
+ stream.end(payload);
80
+ });
81
+ }
82
+ async function sendGoogle(device, data) {
83
+ if (!google)
84
+ return { accepted: false, retryable: false, code: 'google_credentials_missing' };
85
+ const response = await request('https://fcm.googleapis.com/v1/projects/' + encodeURIComponent(google.projectId) + '/messages:send', {
86
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15000), headers: { authorization: 'Bearer ' + await googleAccess(), 'content-type': 'application/json' },
87
+ body: JSON.stringify({ message: { token: device.token, notification: { title, body: 'Open the app to read your message.' }, data,
88
+ android: { priority: 'high', ttl: '3600s', collapse_key: collapse(data), notification: { tag: collapse(data), channel_id: 'robono-messages' } } } }),
89
+ });
90
+ const result = await response.json().catch(() => null);
91
+ if (response.status === 401)
92
+ googleToken = { value: '', expires: 0 };
93
+ return { accepted: response.ok, invalidDevice: result?.error?.details?.some(detail => detail.errorCode === 'UNREGISTERED') ?? false,
94
+ retryable: response.status === 401 || response.status === 429 || response.status >= 500, code: response.ok ? 'accepted' : response.status === 401 || response.status === 403 ? 'google_credentials_rejected' : 'google_rejected' };
95
+ }
96
+ return { providers: [...(apple ? ['apns'] : []), ...(google ? ['fcm'] : [])],
97
+ async send(device, data) {
98
+ try {
99
+ return device.provider === 'apns' ? await sendApple(device, data) : await sendGoogle(device, data);
100
+ }
101
+ catch {
102
+ return { accepted: false, retryable: true, code: 'provider_unavailable' };
103
+ }
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=messaging-notification-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging-notification-providers.js","sourceRoot":"","sources":["../src/messaging-notification-providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,gBAAgB,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAC;AAMnC,MAAM,GAAG,GAAC,CAAC,IAAW,EAAC,EAAE,CAAA,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACjF,SAAS,GAAG,CAAC,MAAa,EAAC,MAAa,EAAC,GAAU,EAAC,SAA+B,EAAC,KAAK,GAAC,KAAK;IAC9F,MAAM,IAAI,GAAC,GAAG,CAAC,MAAM,CAAC,GAAC,GAAG,GAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAC,IAAI,CAAC,SAAS,EAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAC,EAAC,GAAG,EAAC,gBAAgB,CAAC,GAAG,CAAC,EAAC,GAAG,CAAC,KAAK,CAAA,CAAC,CAAA,EAAC,WAAW,EAAC,YAAqB,EAAC,CAAA,CAAC,CAAA,EAAE,CAAC,EAAC,CAAC,CAAC;IAChI,OAAO,IAAI,GAAC,GAAG,GAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACjD,CAAC;AACD,SAAS,QAAQ,CAAC,IAAsB,IAAE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,GAAC,GAAG,GAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA,CAAC;AAEvI,8FAA8F;AAC9F,MAAM,UAAU,yBAAyB,CAAC,OAA+G;IACxJ,MAAM,KAAK,GAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAE,aAAa,CAAC;IAAA,IAAG,KAAK,CAAC,MAAM,GAAC,EAAE;QAAC,MAAM,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACvI,MAAM,KAAK,GAAC,OAAO,CAAC,KAAK,EAAC,MAAM,GAAC,OAAO,CAAC,MAAM,CAAC;IAChD,IAAG,KAAK,IAAE,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAE,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAAC,MAAM,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACrM,IAAG,MAAM,IAAE,CAAC,CAAC,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAE,CAAC,yCAAyC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAAC,MAAM,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC/M,IAAG,KAAK,IAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,iBAAiB,KAAG,IAAI,IAAE,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,oBAAoB,EAAE,UAAU,KAAG,YAAY,CAAC;QAAC,MAAM,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC/M,IAAG,MAAM,IAAE,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,iBAAiB,KAAG,KAAK;QAAC,MAAM,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACpI,IAAI,UAAU,GAAC,EAAC,KAAK,EAAC,EAAE,EAAC,OAAO,EAAC,CAAC,EAAC,EAAC,WAAW,GAAC,EAAC,KAAK,EAAC,EAAE,EAAC,OAAO,EAAC,CAAC,EAAC,EAAC,aAAuC,CAAC;IAC7G,MAAM,OAAO,GAAC,OAAO,CAAC,KAAK,IAAE,KAAK,CAAC;IACnC,KAAK,UAAU,YAAY;QAC1B,IAAG,WAAW,CAAC,OAAO,GAAC,IAAI,CAAC,GAAG,EAAE;YAAC,OAAO,WAAW,CAAC,KAAK,CAAC;QAC3D,IAAG,aAAa;YAAC,OAAO,aAAa,CAAC;QACtC,aAAa,GAAC,CAAC,KAAK,IAAE,EAAE;YACvB,MAAM,GAAG,GAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAC,IAAI,CAAC,EAAC,SAAS,GAAC,GAAG,CAAC,EAAC,GAAG,EAAC,OAAO,EAAC,GAAG,EAAC,KAAK,EAAC,EAC1E,EAAC,GAAG,EAAC,MAAO,CAAC,WAAW,EAAC,KAAK,EAAC,oDAAoD,EAAC,GAAG,EAAC,qCAAqC,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,GAAC,IAAI,EAAC,EAAC,MAAO,CAAC,UAAU,EAAC,YAAY,CAAC,CAAC;YACtL,MAAM,QAAQ,GAAC,MAAM,OAAO,CAAC,qCAAqC,EAAC,EAAC,MAAM,EAAC,MAAM,EAAC,QAAQ,EAAC,OAAO,EAAC,MAAM,EAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EAAC,OAAO,EAAC,EAAC,cAAc,EAAC,mCAAmC,EAAC,EAAC,IAAI,EAAC,IAAI,eAAe,CAAC,EAAC,UAAU,EAAC,6CAA6C,EAAC,SAAS,EAAC,CAAC,EAAC,CAAC,CAAC;YACnS,MAAM,IAAI,GAAC,MAAM,QAAQ,CAAC,IAAI,EAA+C,CAAC;YAC9E,IAAG,CAAC,QAAQ,CAAC,EAAE,IAAE,CAAC,IAAI,CAAC,YAAY,IAAE,OAAO,IAAI,CAAC,UAAU,KAAG,QAAQ;gBAAC,MAAM,KAAK,CAAC,4CAA4C,CAAC,CAAC;YACjI,WAAW,GAAC,EAAC,KAAK,EAAC,IAAI,CAAC,YAAY,EAAC,OAAO,EAAC,IAAI,CAAC,GAAG,EAAE,GAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAC,IAAI,CAAC,GAAC,EAAE,CAAC,GAAC,IAAI,EAAC,CAAC;YAAA,OAAO,IAAI,CAAC,YAAY,CAAC;QACtI,CAAC,CAAC,EAAE,CAAC;QACL,IAAG,CAAC;YAAA,OAAO,MAAM,aAAa,CAAC;QAAA,CAAC;gBAAO,CAAC;YAAA,aAAa,GAAC,SAAS,CAAC;QAAA,CAAC;IAClE,CAAC;IACD,KAAK,UAAU,SAAS,CAAC,MAA0B,EAAC,IAAsB;QACzE,IAAG,CAAC,KAAK;YAAC,OAAO,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,KAAK,EAAC,IAAI,EAAC,2BAA2B,EAAC,CAAC;QACnF,IAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAAC,OAAO,EAAC,QAAQ,EAAC,KAAK,EAAC,aAAa,EAAC,IAAI,EAAC,IAAI,EAAC,eAAe,EAAC,CAAC;QAC5G,IAAG,UAAU,CAAC,OAAO,IAAE,IAAI,CAAC,GAAG,EAAE;YAAC,UAAU,GAAC,EAAC,KAAK,EAAC,GAAG,CAAC,EAAC,GAAG,EAAC,OAAO,EAAC,GAAG,EAAC,KAAK,CAAC,KAAK,EAAC,EAAC,EAAC,GAAG,EAAC,KAAK,CAAC,MAAM,EAAC,GAAG,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAC,IAAI,CAAC,EAAC,EAAC,KAAK,CAAC,UAAU,EAAC,QAAQ,EAAC,IAAI,CAAC,EAAC,OAAO,EAAC,IAAI,CAAC,GAAG,EAAE,GAAC,EAAE,GAAC,KAAK,EAAC,CAAC;QACtM,MAAM,OAAO,GAAC,IAAI,CAAC,SAAS,CAAC,EAAC,GAAG,EAAC,EAAC,KAAK,EAAC,EAAC,KAAK,EAAC,IAAI,EAAC,oCAAoC,EAAC,EAAC,KAAK,EAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,CAAC,IAAI,CAAC,EAAC,EAAC,IAAI,EAAC,IAAI,EAAC,CAAC,CAAC;QACnJ,OAAO,IAAI,OAAO,CAAC,OAAO,CAAA,EAAE;YAC3B,MAAM,OAAO,GAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAA,CAAC,CAAA,oCAAoC,CAAA,CAAC,CAAA,4BAA4B,CAAC,CAAC;YAAA,IAAI,QAAQ,GAAC,KAAK,CAAC;YAC1H,MAAM,MAAM,GAAC,CAAC,MAAiB,EAAC,EAAE,GAAC,IAAG,QAAQ;gBAAC,OAAO,CAAA,QAAQ,GAAC,IAAI,CAAC,CAAA,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA,CAAC,CAAC;YAC5H,MAAM,KAAK,GAAC,UAAU,CAAC,GAAE,EAAE,CAAA,MAAM,CAAC,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,eAAe,EAAC,CAAC,EAAC,KAAK,CAAC,CAAC;YAC/F,OAAO,CAAC,EAAE,CAAC,OAAO,EAAC,GAAE,EAAE,CAAA,MAAM,CAAC,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,yBAAyB,EAAC,CAAC,CAAC,CAAC;YAC/F,MAAM,MAAM,GAAC,OAAO,CAAC,OAAO,CAAC,EAAC,SAAS,EAAC,MAAM,EAAC,OAAO,EAAC,YAAY,GAAC,MAAM,CAAC,KAAK,EAAC,aAAa,EAAC,SAAS,GAAC,UAAU,CAAC,KAAK,EAAC,YAAY,EAAC,KAAK,CAAC,QAAQ,EAAC,gBAAgB,EAAC,OAAO,EAAC,eAAe,EAAC,IAAI,EAAC,iBAAiB,EAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAC,IAAI,CAAC,GAAC,IAAI,CAAC,EAAC,kBAAkB,EAAC,QAAQ,CAAC,IAAI,CAAC,EAAC,cAAc,EAAC,kBAAkB,EAAC,CAAC,CAAC;YACrU,IAAI,MAAM,GAAC,CAAC,EAAC,IAAI,GAAC,EAAE,CAAC;YAAA,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAAA,MAAM,CAAC,EAAE,CAAC,UAAU,EAAC,OAAO,CAAA,EAAE,GAAC,MAAM,GAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAC;YACpH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAC,KAAK,CAAA,EAAE,GAAC,IAAG,IAAI,CAAC,MAAM,GAAC,IAAI;gBAAC,IAAI,IAAE,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC;YAAA,MAAM,CAAC,EAAE,CAAC,OAAO,EAAC,GAAE,EAAE,CAAA,MAAM,CAAC,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,yBAAyB,EAAC,CAAC,CAAC,CAAC;YAC1J,MAAM,CAAC,EAAE,CAAC,KAAK,EAAC,GAAE,EAAE;gBAAC,IAAI,MAAM,GAAC,EAAE,CAAC;gBAAA,IAAG,CAAC;oBAAA,MAAM,GAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;gBAAA,CAAC;gBAAA,MAAK,CAAC,CAAA,CAAC;gBAC7E,IAAG,MAAM,KAAG,GAAG;oBAAC,UAAU,GAAC,EAAC,KAAK,EAAC,EAAE,EAAC,OAAO,EAAC,CAAC,EAAC,CAAC;gBAChD,MAAM,CAAC,EAAC,QAAQ,EAAC,MAAM,KAAG,GAAG,EAAC,aAAa,EAAC,MAAM,KAAG,GAAG,IAAE,MAAM,KAAG,gBAAgB,EAAC,SAAS,EAAC,MAAM,KAAG,sBAAsB,IAAE,MAAM,KAAG,GAAG,IAAE,MAAM,IAAE,GAAG,EAAC,IAAI,EAAC,MAAM,KAAG,GAAG,CAAA,CAAC,CAAA,UAAU,CAAA,CAAC,CAAA,MAAM,KAAG,GAAG,CAAA,CAAC,CAAA,4BAA4B,CAAA,CAAC,CAAA,gBAAgB,EAAC,CAAC,CAAC;YACpP,CAAC,CAAC,CAAC;YAAA,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACJ,CAAC;IACD,KAAK,UAAU,UAAU,CAAC,MAA0B,EAAC,IAAsB;QAC1E,IAAG,CAAC,MAAM;YAAC,OAAO,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,KAAK,EAAC,IAAI,EAAC,4BAA4B,EAAC,CAAC;QACrF,MAAM,QAAQ,GAAC,MAAM,OAAO,CAAC,yCAAyC,GAAC,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,GAAC,gBAAgB,EAAC;YAC5H,MAAM,EAAC,MAAM,EAAC,QAAQ,EAAC,OAAO,EAAC,MAAM,EAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EAAC,OAAO,EAAC,EAAC,aAAa,EAAC,SAAS,GAAC,MAAM,YAAY,EAAE,EAAC,cAAc,EAAC,kBAAkB,EAAC;YACzJ,IAAI,EAAC,IAAI,CAAC,SAAS,CAAC,EAAC,OAAO,EAAC,EAAC,KAAK,EAAC,MAAM,CAAC,KAAK,EAAC,YAAY,EAAC,EAAC,KAAK,EAAC,IAAI,EAAC,oCAAoC,EAAC,EAAC,IAAI;oBACnH,OAAO,EAAC,EAAC,QAAQ,EAAC,MAAM,EAAC,GAAG,EAAC,OAAO,EAAC,YAAY,EAAC,QAAQ,CAAC,IAAI,CAAC,EAAC,YAAY,EAAC,EAAC,GAAG,EAAC,QAAQ,CAAC,IAAI,CAAC,EAAC,UAAU,EAAC,iBAAiB,EAAC,EAAC,EAAC,EAAC,CAAC;SACpI,CAAC,CAAC;QACH,MAAM,MAAM,GAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAE,EAAE,CAAA,IAAI,CAAmD,CAAC;QACrG,IAAG,QAAQ,CAAC,MAAM,KAAG,GAAG;YAAC,WAAW,GAAC,EAAC,KAAK,EAAC,EAAE,EAAC,OAAO,EAAC,CAAC,EAAC,CAAC;QAC1D,OAAO,EAAC,QAAQ,EAAC,QAAQ,CAAC,EAAE,EAAC,aAAa,EAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAA,EAAE,CAAA,MAAM,CAAC,SAAS,KAAG,cAAc,CAAC,IAAE,KAAK;YACxH,SAAS,EAAC,QAAQ,CAAC,MAAM,KAAG,GAAG,IAAE,QAAQ,CAAC,MAAM,KAAG,GAAG,IAAE,QAAQ,CAAC,MAAM,IAAE,GAAG,EAAC,IAAI,EAAC,QAAQ,CAAC,EAAE,CAAA,CAAC,CAAA,UAAU,CAAA,CAAC,CAAA,QAAQ,CAAC,MAAM,KAAG,GAAG,IAAE,QAAQ,CAAC,MAAM,KAAG,GAAG,CAAA,CAAC,CAAA,6BAA6B,CAAA,CAAC,CAAA,iBAAiB,EAAC,CAAC;IACzM,CAAC;IACD,OAAO,EAAC,SAAS,EAAC,CAAC,GAAG,CAAC,KAAK,CAAA,CAAC,CAAA,CAAC,MAAe,CAAC,CAAA,CAAC,CAAA,EAAE,CAAC,EAAC,GAAG,CAAC,MAAM,CAAA,CAAC,CAAA,CAAC,KAAc,CAAC,CAAA,CAAC,CAAA,EAAE,CAAC,CAAC;QAClF,KAAK,CAAC,IAAI,CAAC,MAA0B,EAAC,IAAsB;YAC3D,IAAG,CAAC;gBAAA,OAAO,MAAM,CAAC,QAAQ,KAAG,MAAM,CAAA,CAAC,CAAA,MAAM,SAAS,CAAC,MAAM,EAAC,IAAI,CAAC,CAAA,CAAC,CAAA,MAAM,UAAU,CAAC,MAAM,EAAC,IAAI,CAAC,CAAC;YAAA,CAAC;YAChG,MAAK,CAAC;gBAAA,OAAO,EAAC,QAAQ,EAAC,KAAK,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,sBAAsB,EAAC,CAAC;YAAA,CAAC;QAC3E,CAAC;KACD,CAAC;AACH,CAAC"}