@robono/messaging-react-native 0.0.0-stage → 0.2.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 (76) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +130 -0
  3. package/README.md +240 -2
  4. package/SBOM.spdx.json +1847 -0
  5. package/app.plugin.cjs +5 -0
  6. package/dist/call-message.d.ts +6 -0
  7. package/dist/call-message.js +109 -0
  8. package/dist/calling.d.ts +15 -0
  9. package/dist/calling.js +22 -0
  10. package/dist/expo-calling.d.ts +6 -0
  11. package/dist/expo-calling.js +7 -0
  12. package/dist/expo-download.d.ts +3 -0
  13. package/dist/expo-download.js +43 -0
  14. package/dist/expo-media.d.ts +19 -0
  15. package/dist/expo-media.js +411 -0
  16. package/dist/expo-outbox.d.ts +13 -0
  17. package/dist/expo-outbox.js +81 -0
  18. package/dist/expo-saved.d.ts +24 -0
  19. package/dist/expo-saved.js +76 -0
  20. package/dist/expo-social.d.ts +3 -0
  21. package/dist/expo-social.js +21 -0
  22. package/dist/expo-thumbnails.d.ts +9 -0
  23. package/dist/expo-thumbnails.js +68 -0
  24. package/dist/expo.d.ts +42 -0
  25. package/dist/expo.js +549 -0
  26. package/dist/forward.d.ts +13 -0
  27. package/dist/forward.js +61 -0
  28. package/dist/icons.d.ts +7 -0
  29. package/dist/icons.js +10 -0
  30. package/dist/inbox.d.ts +16 -0
  31. package/dist/inbox.js +48 -0
  32. package/dist/index.d.ts +144 -0
  33. package/dist/index.js +696 -0
  34. package/dist/media-controller.d.ts +116 -0
  35. package/dist/media-controller.js +372 -0
  36. package/dist/media-readiness.d.ts +4 -0
  37. package/dist/media-readiness.js +4 -0
  38. package/dist/media-ui.d.ts +62 -0
  39. package/dist/media-ui.js +46 -0
  40. package/dist/message-links.d.ts +5 -0
  41. package/dist/message-links.js +30 -0
  42. package/dist/message-order.d.ts +4 -0
  43. package/dist/message-order.js +17 -0
  44. package/dist/native-outbox.d.ts +49 -0
  45. package/dist/native-outbox.js +51 -0
  46. package/dist/pollVoteQueue.d.ts +3 -0
  47. package/dist/pollVoteQueue.js +29 -0
  48. package/dist/polls.d.ts +25 -0
  49. package/dist/polls.js +121 -0
  50. package/dist/popup.d.ts +9 -0
  51. package/dist/popup.js +7 -0
  52. package/dist/recent-media.d.ts +21 -0
  53. package/dist/recent-media.js +123 -0
  54. package/dist/safety.d.ts +12 -0
  55. package/dist/safety.js +53 -0
  56. package/dist/saved.d.ts +10 -0
  57. package/dist/saved.js +46 -0
  58. package/dist/search.d.ts +9 -0
  59. package/dist/search.js +41 -0
  60. package/dist/social.d.ts +19 -0
  61. package/dist/social.js +35 -0
  62. package/dist/use-quote-gesture.d.ts +24 -0
  63. package/dist/use-quote-gesture.js +127 -0
  64. package/dist/visual/RobonoIcon.d.ts +232 -0
  65. package/dist/visual/RobonoIcon.js +21 -0
  66. package/dist/visual/icon-nodes.d.ts +3 -0
  67. package/dist/visual/icon-nodes.js +2 -0
  68. package/dist/visual/icon-paths.d.ts +221 -0
  69. package/dist/visual/icon-paths.js +221 -0
  70. package/dist/voice-composer.d.ts +37 -0
  71. package/dist/voice-composer.js +20 -0
  72. package/licenses/robono-icons-lucide-license.txt +43 -0
  73. package/licenses/robono-icons-weather-attribution.txt +1 -0
  74. package/licenses/robono-icons-weather-license.txt +21 -0
  75. package/package.json +126 -4
  76. package/plugin/preserve-call-audio.cjs +22 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Use the compact Messenger media toolbar, autoplay video after audio setup, and offer native full-screen playback. Dismiss iOS menus before presenting Saved media, Forward/Delete/Report or Search; keep native pickers free of a competing modal. Reserve the actual timestamp width so edited messages do not overlap it.
6
+
7
+ Add shared Edit/Delete actions, draft-preserving composer editing, delete confirmation and search/quote navigation. Media adapters report opening/playback independently of bubble visibility. Media replacement/removal, authorized forwarding, persistent Saved copies and native outgoing draft recovery are implemented. Physical iPhone checks cover selected flows; physical Android acceptance remains outstanding.
8
+
9
+ Add text/voice translation to text or voice, shared translation popup, browser/Expo spoken playback and expiring-result cleanup. Add optional viewer downloads (off by default): device photo library, iPhone Save to Files sheet and bounded Android folder export. Streamed video exports an authorized local copy without replacing playback. Requires expo-file-system >=55.0.5. iPhone checks cover playback, Saved files, restart recovery and popup transitions. Android device acceptance and physical export-sheet checks remain pending.
10
+
11
+ Add `MessagingInbox`, the shared search popup and optional expiring typing/online labels in the existing header. Clear private unread counts independently of public read receipts. Closed polls disable their controls on both native and web renderers. Requires the matching 0.2.0 core release; iPhone fixture acceptance is recorded separately from Android acceptance.
12
+
13
+ Render unfinished sends as ordinary chat bubbles with compact status icons and in-place retry. Keep the composer available during attachment sends. Stage browser and Expo attachments locally until Send; add native thumbnails and preview without consuming the draft.
14
+
15
+ Use the server-provided total member count for large groups instead of counting a partial member page.
16
+
17
+ ## 0.1.0
18
+
19
+ Optional messaging screens and media controls, attachment composer, recording, playback, quoting, reactions, polls, translation, transcription, block and report flows. Expo adapters and optional calling integration.
package/LICENSE ADDED
@@ -0,0 +1,130 @@
1
+ ROBONO SDK LICENSE AGREEMENT
2
+ Version 1.0, July 20, 2026
3
+
4
+ Copyright (c) 2026 Add to Loop LLC. All rights reserved.
5
+
6
+ This Robono SDK License Agreement (the "Agreement") is between Add to Loop LLC,
7
+ operating the Robono service ("Robono"), and the person or entity that downloads,
8
+ installs, copies, or uses the Software ("You"). By downloading, installing,
9
+ copying, or using the Software, You accept this Agreement. If You do not accept
10
+ it, do not use the Software.
11
+
12
+ 1. Definitions
13
+
14
+ "Software" means this Robono software development kit, its documentation, and
15
+ any updates Robono provides under this Agreement.
16
+
17
+ "Robono Service" means the messaging interoperability, routing, policy,
18
+ conversion, delivery, and related services operated or expressly authorized by
19
+ Robono.
20
+
21
+ "Application" means a product or service owned or controlled by You that is
22
+ authorized to connect to the Robono Service.
23
+
24
+ 2. Limited License
25
+
26
+ Subject to this Agreement, Robono grants You a limited, non-exclusive,
27
+ non-transferable, non-sublicensable, revocable license to:
28
+
29
+ (a) install, copy, and use the Software internally to develop, test, and
30
+ operate an Application that communicates with the Robono Service; and
31
+
32
+ (b) reproduce and distribute the portions of the Software that are necessarily
33
+ included in Your Application, solely in compiled, minified, bundled, or object
34
+ code form and solely as needed for that Application to communicate with the
35
+ Robono Service.
36
+
37
+ No right is granted to distribute the Software as a standalone product or SDK.
38
+
39
+ 3. Restrictions
40
+
41
+ You may not, and may not permit another person to:
42
+
43
+ (a) sell, rent, lease, sublicense, publish, or redistribute the Software as a
44
+ standalone product, library, SDK, or service;
45
+
46
+ (b) use the Software or a modified or derivative version of it to develop,
47
+ provide, or support a product or service that competes with the Robono Service;
48
+
49
+ (c) use the Software to bypass Robono, avoid applicable usage controls or fees,
50
+ or access the Robono Service without authorization;
51
+
52
+ (d) remove or alter copyright, trademark, attribution, or proprietary notices;
53
+
54
+ (e) reverse engineer, decompile, or disassemble the Software except to the
55
+ limited extent applicable law expressly prohibits this restriction; or
56
+
57
+ (f) use the Software in violation of applicable law, the rights of another
58
+ person, or the terms governing access to the Robono Service.
59
+
60
+ 4. Ownership and Reserved Rights
61
+
62
+ The Software is licensed, not sold. Robono and its licensors retain all right,
63
+ title, and interest in the Software and all related intellectual property.
64
+ Except for the limited rights expressly granted in Section 2, all rights are
65
+ reserved.
66
+
67
+ No patent license is granted except the minimum license, if any, necessarily
68
+ required to exercise the rights expressly granted in Section 2, and that
69
+ limited patent license applies only while the Software is used with an
70
+ authorized Robono Service.
71
+
72
+ 5. Service Access
73
+
74
+ This Agreement does not itself grant an account, API credentials, service
75
+ availability, support, or a right to access the Robono Service. Service access
76
+ may be governed by additional terms, plans, limits, and policies. Robono may
77
+ suspend or terminate access to protect users, systems, legal compliance, or the
78
+ integrity of the Robono Service.
79
+
80
+ 6. Updates and Support
81
+
82
+ Robono is not required to provide maintenance, updates, support, or continued
83
+ availability of any version of the Software unless separately agreed in
84
+ writing.
85
+
86
+ 7. Term and Termination
87
+
88
+ This Agreement continues until terminated. Your rights terminate automatically
89
+ if You breach this Agreement. On termination, You must stop using the Software
90
+ and delete copies under Your control, except for copies necessarily contained
91
+ in previously distributed Applications. Sections 3, 4, 8, 9, 10, and 11
92
+ survive termination.
93
+
94
+ 8. Disclaimer of Warranties
95
+
96
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" AND
97
+ "AS AVAILABLE," WITHOUT WARRANTIES OF ANY KIND, EXPRESS, IMPLIED, OR
98
+ STATUTORY, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
99
+ PURPOSE, TITLE, AND NON-INFRINGEMENT.
100
+
101
+ 9. Limitation of Liability
102
+
103
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, ROBONO WILL NOT BE LIABLE FOR INDIRECT,
104
+ INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR LOSS
105
+ OF DATA, REVENUE, PROFITS, GOODWILL, OR BUSINESS OPPORTUNITY, ARISING FROM OR
106
+ RELATING TO THE SOFTWARE. ROBONO'S TOTAL LIABILITY RELATING TO THE SOFTWARE
107
+ WILL NOT EXCEED THE GREATER OF ONE HUNDRED U.S. DOLLARS (US $100) OR THE AMOUNT
108
+ YOU PAID ROBONO FOR THE SOFTWARE DURING THE TWELVE MONTHS BEFORE THE EVENT
109
+ GIVING RISE TO THE CLAIM. THESE LIMITATIONS APPLY TO THE MAXIMUM EXTENT
110
+ PERMITTED BY LAW.
111
+
112
+ 10. Governing Law
113
+
114
+ This Agreement is governed by the laws of the State of Florida, excluding its
115
+ conflict-of-laws rules. The state and federal courts located in Palm Beach
116
+ County, Florida have exclusive jurisdiction, and each party consents to their
117
+ jurisdiction and venue.
118
+
119
+ 11. General
120
+
121
+ You may not assign this Agreement without Robono's prior written consent.
122
+ Robono may assign this Agreement in connection with a reorganization, transfer
123
+ of intellectual property, financing, merger, acquisition, or sale of all or
124
+ substantially all of the relevant assets. If a provision is unenforceable, it
125
+ will be enforced to the maximum extent permitted and the remaining provisions
126
+ will remain effective. A waiver must be in writing. This Agreement is the
127
+ entire agreement concerning the Software unless You and Robono sign a separate
128
+ written agreement that expressly replaces it.
129
+
130
+ Questions about this license may be submitted at https://robono.com/contact.
package/README.md CHANGED
@@ -1,3 +1,241 @@
1
- # Temporary Holding Version
1
+ # Optional Robono messaging screens
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Use a ready-made direct or group conversation screen, replace its sections, or compose the exported components into your own layout. This package is optional and adds no navigation, global audio configuration or push handlers to your app. The shared icon controls require `react-native-svg` (install it with your framework’s native dependency installer).
4
+
5
+ ```tsx
6
+ import {MessagingScreen} from '@robono/messaging-react-native';
7
+
8
+ <MessagingScreen
9
+ client={messagingClient}
10
+ userId={session.userId}
11
+ contact={{name: contact.displayName, avatarUrl: contact.avatarUrl}}
12
+ active={isConversationFocused}
13
+ onBack={navigation.goBack}
14
+ onMessageOptions={message => navigation.openMessageActions(message)}
15
+ pickAttachment={(kind, limits) => media.pickAndUpload(kind, limits)}
16
+ openAttachment={message => media.openAuthorized(message.media!.assetId)}
17
+ />
18
+ ```
19
+
20
+ The client is from `@robono/messaging`; initialize it with the authenticated backend transport and open the conversation before rendering. Your existing navigation, account/contact services and private media store supply the referenced callbacks. Mount inside your safe area. Pause polling in the background and dispose the client when the user signs out. No separate Robono end-user account is needed.
21
+
22
+ Enabled message kinds come from the server. In text-only mode there are no attachment controls. Voice-only mode has no text field. `pickAttachment(kind, {maxDurationMs})` receives the configured voice limit. Stop recording at that limit on both iOS and Android and show the remaining time. Cancel without sending when recording fails. Your backend must check the actual file duration too. The recorder displays the limit, and the client rejects oversized recordings before sending. `pickAttachment` handles the device picker or recording flow, uploads privately, and returns an asset reference or `null` on cancellation. `openAttachment` checks access through your backend and uses your media viewer/player. A missing callback never sends a fabricated attachment. Connect safety callbacks to your app's existing report/block actions.
23
+
24
+ ## Message presentation and camera
25
+
26
+ Text uses a send icon; `appearance.labels.send` remains its accessible label. Voice messages show a play control, waveform area and duration. Photos and videos use thumbnail bubbles, and files show a document icon, filename, type and size. Sent and received messages use the corresponding colors and alignment.
27
+
28
+ Supply `attachmentPreview(message)` to resolve `{uri?, waveform?}` after checking the current user's media access. `uri` is an image thumbnail (a poster frame for video); `waveform` contains 32 normalized amplitude values between 0 and 1. These stay in component state, not in the message payload. Without metadata the SDK shows a neutral media tile or waveform track. `openAttachment` remains responsible for authorized media access. The Expo screen supplies inline voice playback and the shared full-screen media viewer. To connect a different audio engine, supply `components.VoiceMessage` and render the exported `VoicePlayback` with its playback state and callbacks. `MediaViewer`, `ZoomablePhoto` and `MediaPlaybackControls` are also exported for hosts with their own media engines.
29
+
30
+ Photos, videos and files share an Attach menu. Set `appearance.showCamera` to offer Camera alongside them; photo messaging must also be enabled. The option defaults off for existing integrations. Camera calls `pickAttachment('image', {...limits, source: 'camera'})`, so captures follow the same photo restrictions and review/upload process. Hosts supplying their own media callback must handle that source explicitly. The Expo adapter requests camera permission only on selection and opens the device camera on iOS and Android. Configure the `expo-image-picker` plugin's `cameraPermission` explanation in your native build. Cancelling or refusing permission sends nothing.
31
+
32
+ ## Reuse or replace components
33
+
34
+ The screen accepts `components={{Header, Message, List, Composer}}`. Replacements read the same client state, theme, selected reply and callbacks through `useMessagingUI()`.
35
+
36
+ For complete layout control, wrap `MessagingHeader`, `MessagingList` and `MessagingComposer` in `MessagingProvider`. Use `MessagingBubble` for the standard message view. Supply only the components you want. The headless client works without this package.
37
+
38
+ Appearance comes from the selected integration's Messaging page, with an optional per-screen `appearance` override. The service still enforces message permissions even when your UI is fully custom. A local appearance override cannot change permissions.
39
+
40
+ The same React Native components support iOS and Android. Keyboard handling uses iOS padding and Android's host window configuration. Your host app owns microphone/library permissions, media audio focus, foreground notification behavior and navigation. Report component inventory through `client.reportUsage`; it is distinct from accepted-message usage.
41
+
42
+ ## Recording and attachments with Expo
43
+
44
+ The optional `@robono/messaging-react-native/expo` entry supplies recording, review, voice playback and seeking, photo/video picking and preview, document selection, and private upload/download progress. Users explicitly send after review. A failed upload can retry with the same upload identity; cancelling removes the adapter's cache copy, never the original library item. Viewer copies are removed when the viewer closes. Managed voice and video play directly from private storage using native streaming. By default, `ExpoMessagingScreen` preloads up to six recent photos in the open conversation (2 MiB per file, 6 MiB total, two background transfers at a time). Custom adapters without streaming can also preload small voice/video attachments within those limits. Each transfer uses the authenticated storage adapter and counts as normal transfer usage. The cache is cleared when the app backgrounds, the conversation changes, access fails, or messages disappear; cached files require a fresh download on opening if their message has not been refreshed from the conversation history within 60 seconds. Voice/video streams and document downloads start only on tap. Set `preloadRecentMedia={false}` to keep all media transfers on demand. The recorder reserves 100 milliseconds at the duration limit for encoder padding; storage completion verifies the finished file. Recording stops when the app backgrounds or a call takes audio priority, leaving a review rather than sending automatically.
45
+
46
+ Install native dependencies with your Expo project's installer so their versions match your SDK:
47
+
48
+ ```sh
49
+ npx expo install expo-audio expo-image-picker expo-document-picker expo-file-system expo-sharing expo-video expo-crypto react-native-safe-area-context react-native-svg
50
+ ```
51
+
52
+ This entry targets Expo SDK 55–56; the core components do not require Expo. Configure the `expo-audio` plugin's `microphonePermission` with your app's explanation (for example, “Allow microphone access to record voice messages”). Keep background recording disabled. Native dependency or permission changes need a new development/store build; screen changes do not. The standard system photo picker does not require full photo-library access. Documents use the platform's file picker, without broad storage access.
53
+
54
+ ```tsx
55
+ import {
56
+ ExpoMessagingScreen, createExpoMessagingStorage,
57
+ createExpoMessagingAudioSession, createExpoMessagingClient,
58
+ } from '@robono/messaging-react-native/expo';
59
+
60
+ const messagingClient = createExpoMessagingClient({
61
+ url: appConfig.messagingEndpoint,
62
+ userId: session.userId,
63
+ getAccessToken: () => sessions.accessToken(),
64
+ });
65
+
66
+ const storage = createExpoMessagingStorage({
67
+ url: appConfig.messagingMediaEndpoint,
68
+ getAccessToken: () => sessions.accessToken(),
69
+ });
70
+ const audioSession = createExpoMessagingAudioSession({
71
+ isCallActive: () => calls.isActive(),
72
+ });
73
+
74
+ <ExpoMessagingScreen
75
+ client={messagingClient}
76
+ userId={session.userId}
77
+ contact={{name: contact.displayName, avatarUrl: contact.avatarUrl}}
78
+ active={isConversationFocused}
79
+ onBack={navigation.goBack}
80
+ storage={storage}
81
+ audioSession={audioSession}
82
+ onMessageOptions={message => navigation.openMessageActions(message)}
83
+ />
84
+ ```
85
+
86
+ Open the conversation with `await messagingClient.open(conversationId)`, then call `messagingClient.start()` while it is active. Call `stop()` when it backgrounds and `start()` when it returns; call `dispose()` when the owning client is no longer used. The screen subscribes to the client but does not own its polling lifecycle.
87
+
88
+ Managed attachment history includes short-lived download authorization. The Expo adapter uses it to begin a fresh transfer without another authorization request; absent, mismatched or expired grants use the authenticated media endpoint. Keep grants in memory only and out of logs and persisted chat history. The bucket remains private. As with other issued transfer links, revocation can leave an already issued link usable for up to 60 seconds, unless the object has already been removed.
89
+
90
+ Managed voice messages and videos use progressive streaming: the native player can start before the complete original file has downloaded and request byte ranges when seeking. No lower-resolution copy or new streaming subscription is required. Photos and documents retain verified whole-file downloads. Set `streaming: false` in `createExpoMessagingStorage` to use whole-file downloads for voice/video too. Custom `MessagingMediaStorage` adapters may implement optional `stream(message, context)` returning `{...metadata, uri, stream: {headers?}}`; omit it or return `null` to use `download`.
91
+
92
+ If native streaming fails or a later range request encounters an expired link, the screen makes one recovery attempt through the existing authorized download path, preserving playback position and whether playback was paused. Denied access still fails; it does not bypass authorization or loop retries. Native buffers are temporary; streaming URLs and headers must never be persisted. Usage counts bytes served, including additional ranges or recovery downloads, rather than treating each range as a whole-file download.
93
+
94
+ The adapter prepares private file storage alongside download authorization, and prepares voice audio output alongside native file loading. Transfers still require authorization; playback waits for native media readiness and the audio session. Video rendering can prepare while audio initializes, with playback controls enabled only after audio is ready.
95
+
96
+ For performance measurement, `onMediaReady` reports `{messageId, kind, stage, elapsedMs}` from a photo's load, a video's first frame or advancing voice playback. Disable preloading to measure on-demand media startup. Streaming readiness means the player has usable data, not that it downloaded the whole file. This callback does not measure the exact audible onset at the speaker.
97
+
98
+ Create stable storage and audio-session instances outside rendering or with `useMemo`. Mount within your existing `SafeAreaProvider`. If your app already coordinates calls and media, supply its own `MessagingAudioSession.acquire()` lease instead. A lease returns an idempotent release function. `isAvailable()` must return false while a call owns audio. The supplied coordinator stops media when that guard changes and avoids overwriting the call's audio mode. Your call integration remains responsible for acquiring its own native audio session.
99
+
100
+ Photos, Videos and Files each have an allowed MIME-type list and maximum file size. Videos also have a duration limit. Files cannot be used to bypass image, video or voice restrictions. A phone may list more formats than the app allows; selection is checked before upload and again against verified metadata on the backend. Allowing a container format does not guarantee that every codec in it plays on every device. Choose formats compatible with your supported phones, or convert media in your storage pipeline. Documents open the native share/open menu; installed document viewers vary by phone.
101
+
102
+ The bundled adapter uses your private storage endpoint, described below. You can instead implement `MessagingMediaStorage` with your existing upload/download service, or use the core `MessagingScreen` callbacks. None of these screens or adapters is mandatory.
103
+
104
+ ## Private media endpoint
105
+
106
+ Mount `createMessagingMediaBackendAdapter` from `@robono/server` behind your existing session authentication. Supply the same `RobonoMessagingBackend` as your messaging endpoint, your authorization checks, and a `MessagingPrivateStorage` implementation for your storage provider. The adapter checks current conversation access and policy before asking storage for a transfer. Phone requests never contain Robono API keys or storage credentials.
107
+
108
+ The storage implementation has four operations:
109
+
110
+ - `prepare`: persist upload ID, actor, conversation, message kind and proposed metadata together; return a short-lived private HTTPS PUT URL and required headers. Retrying the same upload ID must resolve the same upload. Reject conflicting reuse.
111
+ - `complete`: verify that actor/conversation own the upload; inspect the stored bytes to determine the actual format, size and recording/video duration. Return the persisted kind and verified metadata. A filename, extension or phone-provided MIME type is not verification. Repeated completion must return the same asset.
112
+ - `download`: return a short-lived private HTTPS URL for the authorized message's asset. Robono checks that the message still exists; storage also verifies its asset belongs to that message/conversation.
113
+ - `discard`: revoke/delete only that actor's uncommitted upload. It must be safe to repeat, including after a lost completion response, and must never delete an asset already referenced by an accepted message.
114
+
115
+ The HTTP protocol uses POST with `{operation, ...}`. `prepare` takes `uploadId`, `conversationId`, `kind`, and `file` metadata; it returns `{url, headers?}` or `{media}` for an already verified upload. `complete` takes `uploadId` and `conversationId`, returning `{media}`. `download` takes `conversationId` and `messageId`, returning `{url, headers?, media}`. `discard` takes `uploadId`, returning `{ok:true}`. Errors use `{error:{code,message}}` and an appropriate HTTP status.
116
+
117
+ Also supply `resolveMedia` to `createMessagingBackendAdapter`, resolving the authenticated actor, conversation and asset ID to your verified stored metadata. This replaces untrusted phone claims before sending. Keep asset IDs opaque and unique per message. Check blocks/permissions in both endpoints. Restrict signed uploads to the prepared object and size where supported; delete rejected objects. Expire abandoned uploads and private cache files through your storage lifecycle. Your app owns storage and any malware/content checks appropriate to its policy; the SDK does not upload files to public storage or scan private conversations.
118
+
119
+ Handle signed `messaging.message_removed` events to revoke and delete assets when history expires. Account deletion must also erase associated storage. A terminated app cannot run cache cleanup; the adapter's temporary files live under the OS cache directory, which can be purged by the OS. Do not treat device cache eviction as a fixed retention guarantee.
120
+
121
+
122
+ ### Design messaging and calling together
123
+
124
+ The website Experience builder renders these messaging components and the optional calling components, with a local conversation transport. Saving a draft creates an immutable design version without changing active settings. Review and apply updates messaging and calling atomically, with revision checks against the existing editors. Use your existing settings/appearance fetches to receive the applied design. Service access and per-contact eligibility still apply.
125
+
126
+ For a conversation header with an optional voice-call action:
127
+
128
+ ```tsx
129
+ import { MessagingCallingScreen } from '@robono/messaging-react-native/calling';
130
+
131
+ <MessagingCallingScreen
132
+ client={messagingClient}
133
+ userId={signedInUserId}
134
+ contact={contact}
135
+ onCall={voiceAvailable ? () => callingController.call(connectionId, contact) : undefined}
136
+ />
137
+ ```
138
+
139
+ The `/calling` entry additionally needs the optional `@robono/calling-react-native` peer. Mount the calling overlay and configure native audio/notifications through the existing calling integration. This entry creates no call client and requests no permission. Omit `onCall` to hide the action; backend admission remains authoritative. `MessagingHeader` also accepts children for custom header actions.
140
+
141
+ The website preview renders the same bubbles, inline voice controls, full-screen photo/file viewer and video transport controls through React Native Web. Phone/tablet and portrait/landscape choices resize the real layout viewport without reloading the conversation. Browser media elements and Expo players drive the shared controls; the browser preview does not substitute native browser player controls or a generic dialog. Camera/library/file selection and OS permission prompts still follow the platform. Preview interactions never contact real users or send billable events.
142
+
143
+ The standard chat uses compact grouped bubbles, timestamps at the lower right, inline voice controls and an expandable attachment tray below the composer. Attach dismisses the keyboard; tap it again to return to typing. Android Back closes the tray first. The microphone remains available beside the text field, including while text is typed; Send is enabled when there is text or a finished voice recording. Camera remains available when capture is enabled, including with typed text or a reviewed voice recording. Captures join the existing pending attachments. New designs start with a light chat palette; existing saved colors remain unchanged. The builder’s “Use classic chat colors” action applies the default palette to the draft without changing enabled features.
144
+
145
+ Voice-only conversations use Robono Messenger’s compact composer: the duration-limit placeholder, embedded microphone and adjacent Send control. The Expo adapter records and reviews audio inline, with elapsed time, microphone metering, playback and removal before sending. The builder uses the same controls with a local browser microphone recording; no audio is uploaded from the preview. When texting is enabled, tap the microphone to record in the composer and tap it again to stop. Each completed recording becomes a separate playable attachment in the shared horizontal row, and the microphone is available for another recording. Typed text is preserved; Send puts accompanying text on the last attachment as its caption. Custom media adapters can use `components.VoiceComposer`, `VoiceMessageComposer` and `VoiceDraftPlayback`; `renderTextComposer` restores the text controls alongside the reviewed recording.
146
+
147
+ Recent attachments can appear directly beneath the attachment buttons. Photos, Videos and Document filter this strip; tapping the selected category again shows all enabled types. Tapping a thumbnail adds it above the composer as a pending attachment; its magnifying-glass button opens a preview instead. Remove unwanted attachments or press Send to submit the selection with any typed text. Camera starts capture. When no recent-media source is supplied, the category buttons open the device picker. Supply `attachmentChoices` with an ID, kind, name, and optional thumbnail URI/duration. Only enabled attachment types appear. With `MessagingScreen`, selecting a tile calls `pickAttachment` with `options.attachmentId`; use `options.intent` to distinguish selection (`send`, retained for adapter compatibility) from preview. Returning photo, video or document media stages it in the composer; returning null cancels without selecting it. Voice recordings also become pending attachments after recording stops. With `ExpoMessagingScreen`, supply `resolveAttachmentChoice(id, signal)` to return an authorized cache copy; the standard preview, validation, send and cleanup flow handles the rest. Keep originals in the user's library untouched. The host controls library access and which recent items it supplies.
148
+
149
+ Camera capture and library selection are separate controls. `appearance.showPhotoLibrary` and `showVideoLibrary` hide library choices without removing the corresponding message-type permission needed for camera captures. Both default to true for existing integrations. The builder manages these together with Camera, so enabling capture does not select Photos/Videos or insert example media. Parent and network message-type permissions remain enforced.
150
+
151
+ The Expo adapter keeps selected and recorded attachments locally until Send. Recording stops into a playable draft without uploading. Send immediately queues the bubbles, then uploads the selected files. A caption belongs to the final attachment; a quote belongs to the first. The batch preserves selection order when uploads finish out of order. Custom adapters can supply components.VoiceAttachment for draft playback and releaseAttachment for local cleanup.
152
+
153
+
154
+ ## Group conversations, reactions and polls
155
+
156
+ The same `MessagingScreen` opens direct or group conversations. Supply `participants: {[userId]: {name, avatarUrl}}` from your own authorized profile data. The service supplies the group name, membership and `canSend`; read-only members see the conversation without a composer. Your backend manages group membership through `RobonoMessagingBackend.setGroup`, never a mobile API key.
157
+
158
+ Enable reactions and polls in Experience builder. The screen includes the long-press reaction picker, reaction counts, poll composer and voting/results controls. These components share your configured message colors. `PollMessage` and `PollComposer` can also be used separately. Voting is visible to other participants; only a poll's creator can close it.
159
+
160
+ ## Optional message after no answer
161
+
162
+ Use `ExpoMessagingCallingScreen` from `@robono/messaging-react-native/expo-calling` for the supplied native media tools together with calling. It accepts the same `storage` and `audioSession` as `ExpoMessagingScreen`. For your own media adapter, use `MessagingCallingScreen` from the `/calling` entry to combine the optional call button and message composer. Connect the calling controller's `onUnansweredCall` callback to your existing conversation navigation, then pass `unansweredCallId` and `onDismissCallMessage`. The callback only runs for an outgoing call made in the current controller session that ended without an answer; opening old missed-call history does not trigger it.
163
+
164
+ `UnansweredCallMessage` is also exported separately for a custom composer. Place it inside `MessagingProvider` and supply your existing `pickAttachment` adapter. The adapter must support recording, review and private upload. Eligibility comes from your authenticated backend, and sending rechecks permissions. Recording is optional and starts only when the caller chooses it. An ordinary message is left in the chat, with normal history, retry and media rules.
165
+
166
+ ### Blocking, reporting, reactions and language tools
167
+
168
+ The default header’s three-dot menu provides Block/Unblock and Report when the authenticated backend advertises safety support. Hold a received bubble to report that message. `onContactOptions` optionally replaces the supplied contact menu with your existing safety flow. The exported `MessagingSafety` component can also be composed into your screen.
169
+
170
+ Blocking retains history and stops direct messages and calls for that pair in your app. Shared group messages remain visible. Reporting is separate: it shares the selected message, or up to five recent incoming messages, and the reason with your app’s moderators. It confirms success only after storage acknowledges the report. The SDK reuses the request ID on retry. Owners and administrators review reports in the Messaging workspace; private media is reviewed through your app’s authorized storage tools.
171
+
172
+ Enable Reactions to show an emoji ribbon beside the long-pressed bubble, or the hover smile icon on desktop. Selected reactions stay attached below the message. Polls are interactive chat messages created from the attachment menu. Transcript shows readable text under a voice message. Translate opens the shared compact popup with **Translate to text** and **Translate to voice**. Both text and voice messages support either output. The Expo screen supplies the spoken-result player and uses the normal audio session, so recordings and other playback do not overlap. The original message and timestamp stay in place. Provide `language` for the viewer’s target language and connect `MessagingClient.transformRequest` to the authenticated `createMessagingTransformBackendAdapter` on your server.
173
+
174
+ ### November 1 behavior update (unpublished)
175
+
176
+ The default palette follows Messenger. Existing saved customer colors remain in
177
+ place. Direct chats use the brand header; groups use Messenger’s blue header.
178
+ Bubble padding is fixed; the legacy `compact` property is accepted for backwards
179
+ compatibility but no longer changes core spacing.
180
+
181
+ Photo/video/file selections remain removable drafts; tapping a draft opens its
182
+ viewer without deleting the selection. Expo supplies small image/video previews
183
+ using `expo-image-manipulator` and `expo-video-thumbnails`; add those optional peers
184
+ when using the Expo entry. Copy uses `expo-clipboard`. Native media downloads use
185
+ photo-library permission; sharing uses the platform share sheet. New managed photo/video uploads have an authenticated preview (at most 480 pixels and 128 KiB) after the updated storage worker, inspector, media handler and migration 178 are deployed. Previews count toward normal storage/transfer usage. Older large uploads without a preview remain tap-to-open; originals over 2 MiB are never automatically downloaded to build thumbnails.
186
+
187
+ `appearance.showWaveform` controls display only. Verified waveform samples stay
188
+ in `media.waveform`. The playback track remains seekable when the waveform is
189
+ hidden. Managed storage computes waveform data independently of this preference.
190
+
191
+ Groups offer polls; direct chats offer a recipient-only Yes/No question whose
192
+ first answer is final. Both use centered popups on all platforms. Deploy migration
193
+ `20261101017400_messaging_captions_questions.sql` and the updated messaging handler
194
+ before releasing these SDK changes. Deploy the updated media inspector before the
195
+ updated managed-media handler. These source changes have not been published.
196
+
197
+ ### Optional downloads in the viewer
198
+
199
+ Select **Download button** in the website's **Features & preview → Appearance**, or
200
+ set `appearance.showDownloadButton: true`. The Expo viewer shows the same Download
201
+ action for photos, videos and files; it stays hidden by default. Photos and videos
202
+ save to the device photo library after the user grants permission. On iPhone,
203
+ documents open the native sheet with Save to Files. On Android, the user chooses a
204
+ folder and the document is copied there in bounded chunks. Cancelling creates no
205
+ file, and a failed copy removes its own partial output. Use `expo-file-system`
206
+ 55.0.5 or later for Android document saving. Originals remain in private storage.
207
+ This setting controls the visible action, not the authorization needed to view media.
208
+
209
+ `createExpoMessagingClient` keeps outgoing text, selected attachments and failed
210
+ sends in your app's private document storage, separated by endpoint and user.
211
+ `ExpoMessagingScreen` restores small previews and reuses the original upload/send
212
+ identity when retrying. Call `client.signOut()` on logout to remove that user's
213
+ saved work. `dispose()` preserves unfinished work for reopening. Use
214
+ `persistence: false` to disable storage. No received history or download links are
215
+ persisted by this store. The OS app sandbox protects these files; this is not an
216
+ additional application encryption layer.
217
+
218
+ The shared message menu and media preview include Forward and sender Delete.
219
+ Forward checks both chats and uploads a separate private attachment copy; retrying
220
+ uses the same send ID. Edits can replace/remove attachments before a recipient
221
+ opens them. Removing the only attachment requires text. Caption edits keep the
222
+ original file. The ordinary composer draft is retained throughout editing.
223
+
224
+ `MessagingInbox` offers Mute/Unmute and Delete chat on long press (right-click on
225
+ web), plus accessibility actions. Deletion clears only that user's view, up to the
226
+ message shown when confirmation opened. New messages bring the chat back. When
227
+ composing your own native navigation, pass
228
+ `onDeleted={id => messagingClient.forgetConversation(id)}` to clear its local work.
229
+ Updated backend support is required; old adapters hide these controls.
230
+
231
+ ### Saved and multiple-message actions
232
+
233
+ Long press a message, then choose Select to select more rows. The compact action bar supports chronological forwarding and deletion of eligible unread outgoing messages. A single selection also supports Copy, Save and Edit. Back clears the selection.
234
+
235
+ `createExpoMessagingClient` also supplies Saved: explicit local text and attachment copies in the app-private directory, separated by endpoint, account and namespace. The shared header opens Saved. Saving rechecks message access before and after making the copy, and marks incoming content consumed. Saved copies are independent of network retention; they never reuse signed URLs. `client.signOut()` deletes this account’s saved copies. Disabling persistence also disables Saved. Device storage is not a cloud backup or an additional app encryption layer. For a custom engine, supply a `MessagingSavedStore` to `MessagingClient` and `openSavedAttachment` to the shared screen.
236
+
237
+ ### Social previews
238
+
239
+ Install `react-native-webview` with the Expo peer dependencies and rebuild the native app. `ExpoMessagingScreen` supplies the player and the standard shared popup; a custom engine can supply `components.SocialPlayer`. The updated server adapter and hosted messaging endpoint advertise support. Recognized links gain a small play action, while tapping the link still opens the original app/site. Players are loaded only after an explicit tap and fresh message authorization, require explicit playback, and are removed on close/background/navigation. Opening a player stops the current voice-message player and uses the supplied audio-session coordinator. No application cookies or local-file access are shared with the embedded page. Some providers/private posts cannot be embedded; the popup keeps an Open-original link.
240
+
241
+ The same provider parser is used by Messenger, the browser and the backend, including Snapchat and Rumble. Preview opens contribute to the per-network `social.opened` metric. This source change still requires coordinated endpoint and package deployment; installing a newer UI against an older adapter does not enable unsupported controls.