@robono/messaging-react-native 0.2.0 → 0.3.1

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 (85) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +84 -7
  3. package/SBOM.spdx.json +1290 -234
  4. package/dist/account.d.ts +15 -0
  5. package/dist/account.js +27 -0
  6. package/dist/call-message.js +7 -1
  7. package/dist/calling.js +5 -9
  8. package/dist/conversation-details.d.ts +24 -0
  9. package/dist/conversation-details.js +83 -0
  10. package/dist/emoji-text.d.ts +2 -0
  11. package/dist/emoji-text.js +8 -0
  12. package/dist/expo-history.d.ts +3 -0
  13. package/dist/expo-history.js +59 -0
  14. package/dist/expo-media.d.ts +1 -0
  15. package/dist/expo-media.js +46 -43
  16. package/dist/expo-messenger.d.ts +35 -0
  17. package/dist/expo-messenger.js +39 -0
  18. package/dist/expo-notifications.d.ts +31 -0
  19. package/dist/expo-notifications.js +92 -0
  20. package/dist/expo-outbox.js +2 -1
  21. package/dist/expo-privacy.d.ts +4 -0
  22. package/dist/expo-privacy.js +7 -0
  23. package/dist/expo-screen.d.ts +42 -0
  24. package/dist/expo-screen.js +557 -0
  25. package/dist/expo.d.ts +2 -42
  26. package/dist/expo.js +2 -549
  27. package/dist/extras-lifecycle.d.ts +2 -0
  28. package/dist/extras-lifecycle.js +27 -0
  29. package/dist/extras.d.ts +49 -0
  30. package/dist/extras.js +49 -0
  31. package/dist/forward.js +4 -3
  32. package/dist/group-editor.d.ts +18 -0
  33. package/dist/group-editor.js +98 -0
  34. package/dist/header-calls.d.ts +18 -0
  35. package/dist/header-calls.js +45 -0
  36. package/dist/icons.d.ts +2 -0
  37. package/dist/icons.js +2 -0
  38. package/dist/inbox-search.d.ts +11 -0
  39. package/dist/inbox-search.js +42 -0
  40. package/dist/inbox.d.ts +5 -2
  41. package/dist/inbox.js +19 -15
  42. package/dist/index.d.ts +33 -3
  43. package/dist/index.js +359 -141
  44. package/dist/media-controller.d.ts +3 -0
  45. package/dist/media-controller.js +39 -0
  46. package/dist/media-ui.d.ts +14 -3
  47. package/dist/media-ui.js +90 -28
  48. package/dist/message-menu.d.ts +28 -0
  49. package/dist/message-menu.js +24 -0
  50. package/dist/message-order.d.ts +3 -0
  51. package/dist/message-order.js +10 -0
  52. package/dist/messenger-video-view.d.ts +8 -0
  53. package/dist/messenger-video-view.js +46 -0
  54. package/dist/native-outbox.d.ts +8 -2
  55. package/dist/native-outbox.js +3 -1
  56. package/dist/photo-gesture.d.ts +20 -0
  57. package/dist/photo-gesture.js +52 -0
  58. package/dist/polls.d.ts +2 -1
  59. package/dist/polls.js +19 -17
  60. package/dist/popup.d.ts +43 -1
  61. package/dist/popup.js +72 -6
  62. package/dist/quick-send.d.ts +15 -0
  63. package/dist/quick-send.js +26 -0
  64. package/dist/safety.d.ts +9 -1
  65. package/dist/safety.js +21 -6
  66. package/dist/saved.d.ts +1 -1
  67. package/dist/saved.js +9 -4
  68. package/dist/search.d.ts +2 -2
  69. package/dist/search.js +32 -14
  70. package/dist/social.d.ts +9 -3
  71. package/dist/social.js +11 -15
  72. package/dist/translation-settings.d.ts +12 -0
  73. package/dist/translation-settings.js +16 -0
  74. package/dist/use-quote-gesture.d.ts +5 -1
  75. package/dist/use-quote-gesture.js +35 -17
  76. package/dist/visual/WeatherArtwork.d.ts +9 -0
  77. package/dist/visual/WeatherArtwork.js +43 -0
  78. package/dist/visual/weather-artwork.d.ts +17 -0
  79. package/dist/visual/weather-artwork.js +86 -0
  80. package/dist/visual/weather-conditions.d.ts +5 -0
  81. package/dist/visual/weather-conditions.js +2 -0
  82. package/dist/voice-composer.js +2 -2
  83. package/dist/voice-waveform.d.ts +2 -0
  84. package/dist/voice-waveform.js +21 -0
  85. package/package.json +24 -4
@@ -0,0 +1,92 @@
1
+ import * as Notifications from 'expo-notifications';
2
+ import * as Crypto from 'expo-crypto';
3
+ import * as SecureStore from 'expo-secure-store';
4
+ import { Platform } from 'react-native';
5
+ import { createMessagingNotificationRouter, parseMessagingNotification } from '@robono/messaging';
6
+ /** One restored account. Never prompts on mount, replaces a global handler or handles calling pushes. */
7
+ export async function connectExpoMessagingNotifications(options) {
8
+ if (!['ios', 'android'].includes(Platform.OS))
9
+ throw Error('Mobile notification registration requires iPhone or Android.');
10
+ const endpoint = new URL(options.url);
11
+ if (endpoint.protocol !== 'https:' || endpoint.username || endpoint.password)
12
+ throw Error('Use your authenticated HTTPS notification endpoint.');
13
+ let disposed = false, closing = false, registerQueue = Promise.resolve(), accountScope;
14
+ const key = 'robono.messaging.device.' + await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, endpoint.origin + endpoint.pathname);
15
+ let deviceId = await SecureStore.getItemAsync(key);
16
+ if (!deviceId) {
17
+ deviceId = Crypto.randomUUID();
18
+ await SecureStore.setItemAsync(key, deviceId);
19
+ }
20
+ async function request(input) {
21
+ const token = await options.getAccessToken();
22
+ if (disposed)
23
+ throw Error('Notification session has ended.');
24
+ const response = await (options.fetch ?? fetch)(endpoint.href, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15000), headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, body: JSON.stringify({ ...input, ...(accountScope ? { accountScope } : {}) }) });
25
+ const data = await response.json();
26
+ if (!response.ok)
27
+ throw Error(data?.error?.message ?? 'Notification setup is unavailable. Check your app’s Apple/Google credentials.');
28
+ return data;
29
+ }
30
+ const status = await request({ operation: 'notification-status' });
31
+ if (status.userId !== options.userId || typeof status.accountScope !== 'string' || !/^[a-f\d]{64}$/.test(status.accountScope))
32
+ throw Error('The notification session does not match the signed-in account.');
33
+ accountScope = status.accountScope;
34
+ const router = createMessagingNotificationRouter({ accountScope: status.accountScope, authorize: options.authorize, open: options.open, refresh: options.refresh });
35
+ const register = (token) => {
36
+ const work = registerQueue.then(async () => {
37
+ if (disposed || closing)
38
+ return;
39
+ const data = await request({ operation: 'register-device', deviceId, provider: Platform.OS === 'ios' ? 'apns' : 'fcm', token: String(token.data) });
40
+ if (data.accountScope !== status.accountScope)
41
+ throw Error('The signed-in account changed. Reconnect notifications for the new account.');
42
+ });
43
+ registerQueue = work.catch(() => { });
44
+ return work;
45
+ };
46
+ const tokenListener = Notifications.addPushTokenListener(token => { void register(token).catch(options.onError); });
47
+ const received = Notifications.addNotificationReceivedListener(notification => { void router.handle(notification.request.content.data).catch(options.onError); });
48
+ const response = async (value) => {
49
+ if (disposed || closing || !value || value.actionIdentifier !== Notifications.DEFAULT_ACTION_IDENTIFIER)
50
+ return;
51
+ if (await router.handle(value.notification.request.content.data, true)) {
52
+ const last = await Notifications.getLastNotificationResponseAsync();
53
+ if (!disposed && last?.notification.request.identifier === value.notification.request.identifier)
54
+ await Notifications.clearLastNotificationResponseAsync();
55
+ }
56
+ };
57
+ const opened = Notifications.addNotificationResponseReceivedListener(value => { void response(value).catch(options.onError); });
58
+ const ready = (async () => {
59
+ if (Platform.OS === 'android')
60
+ await Notifications.setNotificationChannelAsync('robono-messages', { name: 'Messages', importance: Notifications.AndroidImportance.HIGH });
61
+ if ((await Notifications.getPermissionsAsync()).granted)
62
+ await register(await Notifications.getDevicePushTokenAsync());
63
+ await response(await Notifications.getLastNotificationResponseAsync());
64
+ })();
65
+ void ready.catch(options.onError);
66
+ const dispose = () => { disposed = true; router.dispose(); tokenListener.remove(); received.remove(); opened.remove(); };
67
+ return {
68
+ ready,
69
+ /** Call only after the user chooses Enable notifications in your interface. */
70
+ async enable() { if (disposed || closing)
71
+ return false; const permission = await Notifications.requestPermissionsAsync(); if (!permission.granted)
72
+ return false; await register(await Notifications.getDevicePushTokenAsync()); return true; },
73
+ /** Await before revoking your login session, so the old account's token is removed. */
74
+ async signOut() { closing = true; router.dispose(); try {
75
+ await registerQueue;
76
+ await request({ operation: 'remove-device', deviceId });
77
+ }
78
+ finally {
79
+ dispose();
80
+ } },
81
+ dispose,
82
+ };
83
+ }
84
+ /** Compose with the host's existing handler. Suppress only this account's open conversation. */
85
+ export function withMessagingNotificationPresentation(handler, current) {
86
+ return { ...handler, handleNotification: async (notification) => {
87
+ const event = parseMessagingNotification(notification.request.content.data), active = current();
88
+ if (event && (event.accountScope !== active.accountScope || event.conversationId === active.conversationId))
89
+ return { shouldPlaySound: false, shouldSetBadge: false, shouldShowBanner: false, shouldShowList: false };
90
+ return handler.handleNotification(notification);
91
+ } };
92
+ }
@@ -2,6 +2,7 @@ import * as FS from 'expo-file-system/legacy';
2
2
  import * as Crypto from 'expo-crypto';
3
3
  import { MessagingClient, createMessagingTransport } from '@robono/messaging';
4
4
  import { NativeMessagingOutbox, emptyNativeDraft } from './native-outbox.js';
5
+ import { createExpoMessagingHistory } from './expo-history.js';
5
6
  import { ExpoSavedMessages, bindExpoSaved } from './expo-saved.js';
6
7
  const stores = new WeakMap();
7
8
  export const expoOutbox = (client) => stores.get(client);
@@ -72,7 +73,7 @@ const files = {
72
73
  export function createExpoMessagingClient(options) {
73
74
  const request = createMessagingTransport(options), store = options.persistence === false ? undefined : new NativeMessagingOutbox(options.url, options.userId, files, options.namespace);
74
75
  const saved = store ? new ExpoSavedMessages(options.url, options.userId, options.namespace) : undefined;
75
- const client = new MessagingClient({ request, userId: options.userId, createMessageId: Crypto.randomUUID, outbox: store, saved, transformRequest: options.transformRequest });
76
+ const client = new MessagingClient({ request, userId: options.userId, createMessageId: Crypto.randomUUID, outbox: store, saved, historyCache: store ? createExpoMessagingHistory(options.url, options.userId, options.namespace) : undefined, transformRequest: options.transformRequest });
76
77
  if (store)
77
78
  stores.set(client, store);
78
79
  if (saved)
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { type PrivacyLayerProps } from './extras.js';
3
+ /** Optional Expo adapter; install expo-blur in the customer app. */
4
+ export declare function ExpoMessagingPrivacyLayer({ children, enabled, background }: PrivacyLayerProps): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { View } from 'react-native';
4
+ import { BlurView, BlurTargetView } from 'expo-blur';
5
+ import { privacyReadingStripHeight } from './extras.js';
6
+ /** Optional Expo adapter; install expo-blur in the customer app. */
7
+ export function ExpoMessagingPrivacyLayer({ children, enabled, background }) { const target = useRef(null); return _jsxs(View, { style: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: [_jsx(BlurTargetView, { ref: target, style: { flex: 1, minHeight: 0 }, children: children }), enabled && _jsx(BlurView, { pointerEvents: "none", testID: "privacy-lens-mask", blurTarget: target, blurMethod: "dimezisBlurView", intensity: 50, tint: background.toLowerCase() === "#ffffff" ? "light" : "dark", style: { position: 'absolute', left: 0, right: 0, top: 0, bottom: privacyReadingStripHeight } })] }); }
@@ -0,0 +1,42 @@
1
+ export { createExpoMessagingClient } from './expo-outbox.js';
2
+ import React from 'react';
3
+ import { type MessagingAppearance } from '@robono/messaging';
4
+ import { type MessagingScreenProps } from './index.js';
5
+ import { MessagingMediaController, type LocalMessagingMedia, type MessagingMediaStorage } from './media-controller.js';
6
+ import { type MediaReadyStage } from './media-readiness.js';
7
+ import { type MessagingAudioSession } from './expo-media.js';
8
+ export { createExpoMessagingAudioSession, createExpoMessagingDevice, createExpoMessagingStorage } from './expo-media.js';
9
+ export type { MessagingAudioSession } from './expo-media.js';
10
+ export type { LocalMessagingMedia, MessagingMediaStorage, MediaTransferOptions } from './media-controller.js';
11
+ export { MessagingMediaController } from './media-controller.js';
12
+ export declare function MessagingMediaOverlay({ controller, audio, appearance, inlineVoice, inlineRecorder, viewerActions, onDismiss }: {
13
+ onDismiss?: () => void;
14
+ viewerActions?: () => {
15
+ onForward?: () => void;
16
+ onDelete?: () => void;
17
+ };
18
+ controller: MessagingMediaController;
19
+ audio: MessagingAudioSession;
20
+ appearance: MessagingAppearance;
21
+ inlineVoice?: boolean | (() => boolean);
22
+ inlineRecorder?: boolean | (() => boolean);
23
+ }): React.JSX.Element | null;
24
+ export interface ExpoMessagingScreenProps extends Omit<MessagingScreenProps, 'pickAttachment' | 'openAttachment'> {
25
+ storage: MessagingMediaStorage;
26
+ audioSession: MessagingAudioSession;
27
+ /** Optional timing callback from native image/frame/playback readiness, measured from opening. */
28
+ onMediaReady?: (event: {
29
+ messageId: string;
30
+ kind: string;
31
+ stage: MediaReadyStage;
32
+ elapsedMs: number;
33
+ }) => void;
34
+ /** Preload a bounded set of small recent attachments while this conversation is open. Default true. */
35
+ preloadRecentMedia?: boolean;
36
+ /** Compose a conversation with the supplied media adapters. */
37
+ renderConversation?: (props: MessagingScreenProps) => React.ReactNode;
38
+ /** Resolve an authorized recent attachment to an adapter-owned cache copy, never a library original. */
39
+ resolveAttachmentChoice?: (id: string, signal: AbortSignal) => Promise<LocalMessagingMedia | null>;
40
+ }
41
+ /** Optional ready-made recording, preview, upload and download tools for both mobile platforms. */
42
+ export declare function ExpoMessagingScreen({ storage, audioSession, renderConversation, preloadRecentMedia, ...props }: ExpoMessagingScreenProps): React.JSX.Element;