@adminide-stack/yantra-mobile 12.0.69-alpha.22 → 12.0.69-alpha.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/messaging/ContactsScreen.js +1 -1
- package/lib/features/messaging/ContactsScreen.js.map +1 -1
- package/lib/features/messaging/chatOperations.js +1 -11
- package/lib/features/messaging/chatOperations.js.map +1 -1
- package/lib/features/messaging/useContactMatches.js +7 -59
- package/lib/features/messaging/useContactMatches.js.map +1 -1
- package/package.json +3 -3
|
@@ -86,7 +86,7 @@ function ContactsScreen({
|
|
|
86
86
|
} }) }));
|
|
87
87
|
}
|
|
88
88
|
if (permission === "undetermined" && !hasPeople) {
|
|
89
|
-
return shell(/* @__PURE__ */ jsx(View, { style: styles.centerFill, children: /* @__PURE__ */ jsx(EmptyState, { icon: "account-multiple-outline", title:
|
|
89
|
+
return shell(/* @__PURE__ */ jsx(View, { style: styles.centerFill, children: /* @__PURE__ */ jsx(EmptyState, { icon: "account-multiple-outline", title: "Find people you know", detail: "Allow contacts to see who is on Yantra Chat and invite everyone else.", theme, action: {
|
|
90
90
|
label: "Find from contacts",
|
|
91
91
|
onPress: onRequestAccess
|
|
92
92
|
} }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContactsScreen.js","sources":["../../../src/features/messaging/ContactsScreen.tsx"],"sourcesContent":["/**\n * \"Find people you know\" - the contacts sync and invite screen.\n */\nimport * as React from 'react';\nimport { ActivityIndicator, Pressable, SectionList, StyleSheet, Text, TextInput, View } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { Avatar, EmptyState, InlineBanner, type ChatTheme } from './ChatComponents';\nimport type { MatchedContact } from './useContactMatches';\nimport type { ChatPermissionStatus } from './chatHost';\n\nfunction ContactsHeader({ theme, onBack }: { theme: ChatTheme; onBack: () => void }) {\n return (\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 8,\n paddingBottom: 8,\n borderBottomWidth: StyleSheet.hairlineWidth,\n borderBottomColor: theme.border,\n backgroundColor: theme.headerBg,\n }}\n >\n <Pressable onPress={onBack} hitSlop={10} accessibilityRole=\"button\" accessibilityLabel=\"Back\">\n <MaterialCommunityIcons name=\"chevron-left\" size={28} color={theme.accent} />\n </Pressable>\n <Text\n style={{\n flex: 1,\n fontSize: 20,\n fontWeight: '700',\n color: theme.text,\n marginLeft: 8,\n }}\n >\n Select contact\n </Text>\n </View>\n </SafeAreaView>\n );\n}\n\nexport function ContactsScreen({\n permission,\n loading,\n error,\n onYantra,\n invitable,\n theme,\n busyContactId,\n onBack,\n onRequestAccess,\n onOpenSettings,\n onStartChat,\n onInvite,\n onDismissError,\n limited = false,\n onChooseMoreContacts,\n}: {\n permission: ChatPermissionStatus | 'unsupported';\n loading: boolean;\n error: string | null;\n onYantra: MatchedContact[];\n invitable: MatchedContact[];\n theme: ChatTheme;\n busyContactId: string | null;\n onBack: () => void;\n onRequestAccess: () => void;\n onOpenSettings: () => void;\n onStartChat: (contact: MatchedContact) => void;\n onInvite: (contact: MatchedContact) => void;\n onDismissError: () => void;\n /** Only some contacts are shared (iOS 18 Limited Access). */\n limited?: boolean;\n onChooseMoreContacts?: () => void;\n}) {\n const [query, setQuery] = React.useState('');\n\n const filterContact = React.useCallback(\n (c: MatchedContact) => {\n const q = query.trim().toLowerCase();\n if (!q) return true;\n const name = (c.match?.displayName || c.displayName).toLowerCase();\n const qDigits = q.replace(/\\D/g, '');\n return (\n name.includes(q) ||\n (qDigits.length > 0 &&\n [...c.e164, ...(c.rawNumbers ?? [])].some((n) => n.replace(/\\D/g, '').includes(qDigits)))\n );\n },\n [query],\n );\n\n const sections = React.useMemo(\n () =>\n [\n { key: 'here', title: 'On Yantra Chat', data: onYantra.filter(filterContact) },\n { key: 'invite', title: 'Invite to Yantra Chat', data: invitable.filter(filterContact) },\n ].filter((s) => s.data.length > 0),\n [onYantra, invitable, filterContact],\n );\n\n const hasPeople = onYantra.length > 0 || invitable.length > 0;\n\n const shell = (body: React.ReactNode) => (\n <View style={[styles.fill, { backgroundColor: theme.bg }]}>\n <ContactsHeader theme={theme} onBack={onBack} />\n {permission === 'granted' ? (\n <View style={[styles.searchWrap, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name=\"magnify\" size={20} color={theme.textMuted} />\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder=\"Search name or number\"\n placeholderTextColor={theme.textMuted}\n style={[styles.searchInput, { color: theme.text }]}\n />\n </View>\n ) : null}\n {body}\n </View>\n );\n\n if (permission === 'unsupported' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"cellphone-off\"\n title=\"Not available in this build\"\n detail=\"Finding people from your contacts needs a newer version of the app.\"\n theme={theme}\n action={{ label: 'Back to chats', onPress: onBack }}\n />\n </View>,\n );\n }\n\n if (permission === 'denied' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"lock-outline\"\n title=\"Contacts access is off\"\n detail=\"Allow contacts access in Settings to find people you know on Yantra Chat.\"\n theme={theme}\n action={{ label: 'Open Settings', onPress: onOpenSettings }}\n />\n </View>,\n );\n }\n\n if (permission === 'undetermined' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"account-multiple-outline\"\n title={loading ? 'Looking for people' : 'No other members yet'}\n detail={\n loading\n ? 'Checking who is already on Yantra Chat…'\n : 'You are registered. Allow contacts to invite people, or start a chat when someone else registers.'\n }\n theme={theme}\n action={{ label: 'Find from contacts', onPress: onRequestAccess }}\n />\n </View>,\n );\n }\n\n if (loading && !sections.length) {\n return shell(\n <View style={styles.centerFill}>\n <ActivityIndicator color={theme.accent} />\n <Text style={[styles.loadingText, { color: theme.textMuted }]}>Checking your contacts…</Text>\n </View>,\n );\n }\n\n // A limited selection is never a dead end: the user can share more contacts from here.\n const listHeader =\n permission === 'granted' && limited ? (\n <View>\n <View style={[styles.limitedNote, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name=\"account-lock-outline\" size={20} color={theme.textMuted} />\n <Text style={[styles.limitedText, { color: theme.textMuted }]}>\n You shared only some contacts with Yantra. People you did not share will not show here.\n </Text>\n </View>\n {onChooseMoreContacts ? (\n <ActionRow\n icon=\"account-multiple-plus-outline\"\n label=\"Choose more contacts\"\n theme={theme}\n onPress={onChooseMoreContacts}\n testID=\"chat-contacts-choose-more\"\n />\n ) : null}\n </View>\n ) : null;\n\n return shell(\n <>\n <InlineBanner message={error} theme={theme} onDismiss={onDismissError} />\n <SectionList\n ListHeaderComponent={listHeader}\n sections={sections}\n keyExtractor={(item) => item.id}\n stickySectionHeadersEnabled={false}\n renderSectionHeader={({ section }) => (\n <Text style={[styles.sectionTitle, { color: theme.textMuted, backgroundColor: theme.bg }]}>\n {section.title}\n </Text>\n )}\n renderItem={({ item, section }) => (\n <ContactRow\n contact={item}\n theme={theme}\n busy={busyContactId === item.id}\n mode={section.key === 'here' ? 'chat' : 'invite'}\n onPress={() => (section.key === 'here' ? onStartChat(item) : onInvite(item))}\n />\n )}\n ListEmptyComponent={\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"account-search-outline\"\n title=\"No contacts found\"\n detail={\n limited && !query.trim()\n ? 'Choose more contacts to share with Yantra.'\n : 'Add phone numbers to your contacts, or search with a different name.'\n }\n theme={theme}\n action={{ label: 'Back to chats', onPress: onBack }}\n />\n </View>\n }\n contentContainerStyle={sections.length === 0 ? styles.listEmptyGrow : undefined}\n testID=\"chat-contacts-list\"\n />\n </>,\n );\n}\n\nfunction ActionRow({\n icon,\n label,\n theme,\n onPress,\n testID,\n}: {\n icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'];\n label: string;\n theme: ChatTheme;\n onPress: () => void;\n testID: string;\n}) {\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({ width: '100%', backgroundColor: pressed ? theme.surface : theme.bg })}\n testID={testID}\n >\n {/* Inner wrapper: NativeWind can strip flexDirection from Pressable styles. */}\n <View style={styles.row}>\n <View style={[styles.actionIcon, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name={icon} size={22} color={theme.accent} />\n </View>\n <View style={[styles.rowBody, { borderBottomColor: theme.border }]}>\n <Text style={[styles.rowName, { color: theme.accent }]}>{label}</Text>\n </View>\n </View>\n </Pressable>\n );\n}\n\nfunction ContactRow({\n contact,\n theme,\n mode,\n busy,\n onPress,\n}: {\n contact: MatchedContact;\n theme: ChatTheme;\n mode: 'chat' | 'invite';\n busy: boolean;\n onPress: () => void;\n}) {\n const name = contact.match?.displayName || contact.displayName;\n return (\n <Pressable\n onPress={onPress}\n disabled={busy}\n accessibilityRole=\"button\"\n accessibilityLabel={mode === 'chat' ? `Message ${name}` : `Invite ${name}`}\n style={({ pressed }) => ({\n width: '100%',\n backgroundColor: pressed ? theme.surface : theme.bg,\n })}\n testID={`chat-contact-${contact.id}`}\n >\n {/* Inner wrapper: NativeWind can strip flexDirection from Pressable styles. */}\n <View style={styles.row}>\n <Avatar name={name} uri={contact.match?.picture ?? contact.imageUri} theme={theme} size={44} />\n <View style={[styles.rowBody, { borderBottomColor: theme.border }]}>\n <View style={styles.rowText}>\n <Text numberOfLines={1} style={[styles.rowName, { color: theme.text }]}>\n {name}\n </Text>\n <Text numberOfLines={1} style={[styles.rowPhone, { color: theme.textMuted }]}>\n {contact.rawNumbers?.[0] ?? contact.e164[0]}\n </Text>\n </View>\n {busy ? (\n <ActivityIndicator size=\"small\" color={theme.accent} />\n ) : mode === 'invite' ? (\n <View style={[styles.inviteChip, { borderColor: theme.accent }]}>\n <Text style={[styles.inviteText, { color: theme.accent }]}>Invite</Text>\n </View>\n ) : null}\n </View>\n </View>\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n centerFill: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: 28,\n minHeight: 360,\n },\n listEmptyGrow: { flexGrow: 1 },\n loadingText: { fontSize: 14, marginTop: 12 },\n searchWrap: {\n flexDirection: 'row',\n alignItems: 'center',\n marginHorizontal: 16,\n marginVertical: 8,\n borderRadius: 10,\n paddingHorizontal: 12,\n height: 36,\n },\n searchInput: { flex: 1, marginLeft: 8, fontSize: 16, paddingVertical: 0 },\n row: { flexDirection: 'row', alignItems: 'center', width: '100%', paddingLeft: 16 },\n rowBody: {\n flex: 1,\n flexDirection: 'row',\n alignItems: 'center',\n marginLeft: 12,\n paddingVertical: 12,\n paddingRight: 16,\n minHeight: 64,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n rowText: { flex: 1, minWidth: 0, marginRight: 12 },\n rowName: { fontSize: 16, fontWeight: '600' },\n rowPhone: { fontSize: 13, marginTop: 2 },\n actionIcon: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center' },\n limitedNote: {\n flexDirection: 'row',\n alignItems: 'flex-start',\n marginHorizontal: 16,\n marginTop: 4,\n marginBottom: 4,\n padding: 12,\n borderRadius: 10,\n },\n limitedText: { flex: 1, fontSize: 13, lineHeight: 18, marginLeft: 10 },\n inviteChip: { borderWidth: 1, borderRadius: 14, paddingHorizontal: 12, paddingVertical: 4 },\n inviteText: { fontSize: 13, fontWeight: '600' },\n sectionTitle: {\n fontSize: 13,\n fontWeight: '600',\n paddingHorizontal: 16,\n paddingTop: 14,\n paddingBottom: 6,\n },\n});\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ContactsScreen.js","sources":["../../../src/features/messaging/ContactsScreen.tsx"],"sourcesContent":["/**\n * \"Find people you know\" - the contacts sync and invite screen.\n */\nimport * as React from 'react';\nimport { ActivityIndicator, Pressable, SectionList, StyleSheet, Text, TextInput, View } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { Avatar, EmptyState, InlineBanner, type ChatTheme } from './ChatComponents';\nimport type { MatchedContact } from './useContactMatches';\nimport type { ChatPermissionStatus } from './chatHost';\n\nfunction ContactsHeader({ theme, onBack }: { theme: ChatTheme; onBack: () => void }) {\n return (\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 8,\n paddingBottom: 8,\n borderBottomWidth: StyleSheet.hairlineWidth,\n borderBottomColor: theme.border,\n backgroundColor: theme.headerBg,\n }}\n >\n <Pressable onPress={onBack} hitSlop={10} accessibilityRole=\"button\" accessibilityLabel=\"Back\">\n <MaterialCommunityIcons name=\"chevron-left\" size={28} color={theme.accent} />\n </Pressable>\n <Text\n style={{\n flex: 1,\n fontSize: 20,\n fontWeight: '700',\n color: theme.text,\n marginLeft: 8,\n }}\n >\n Select contact\n </Text>\n </View>\n </SafeAreaView>\n );\n}\n\nexport function ContactsScreen({\n permission,\n loading,\n error,\n onYantra,\n invitable,\n theme,\n busyContactId,\n onBack,\n onRequestAccess,\n onOpenSettings,\n onStartChat,\n onInvite,\n onDismissError,\n limited = false,\n onChooseMoreContacts,\n}: {\n permission: ChatPermissionStatus | 'unsupported';\n loading: boolean;\n error: string | null;\n onYantra: MatchedContact[];\n invitable: MatchedContact[];\n theme: ChatTheme;\n busyContactId: string | null;\n onBack: () => void;\n onRequestAccess: () => void;\n onOpenSettings: () => void;\n onStartChat: (contact: MatchedContact) => void;\n onInvite: (contact: MatchedContact) => void;\n onDismissError: () => void;\n /** Only some contacts are shared (iOS 18 Limited Access). */\n limited?: boolean;\n onChooseMoreContacts?: () => void;\n}) {\n const [query, setQuery] = React.useState('');\n\n const filterContact = React.useCallback(\n (c: MatchedContact) => {\n const q = query.trim().toLowerCase();\n if (!q) return true;\n const name = (c.match?.displayName || c.displayName).toLowerCase();\n const qDigits = q.replace(/\\D/g, '');\n return (\n name.includes(q) ||\n (qDigits.length > 0 &&\n [...c.e164, ...(c.rawNumbers ?? [])].some((n) => n.replace(/\\D/g, '').includes(qDigits)))\n );\n },\n [query],\n );\n\n const sections = React.useMemo(\n () =>\n [\n { key: 'here', title: 'On Yantra Chat', data: onYantra.filter(filterContact) },\n { key: 'invite', title: 'Invite to Yantra Chat', data: invitable.filter(filterContact) },\n ].filter((s) => s.data.length > 0),\n [onYantra, invitable, filterContact],\n );\n\n const hasPeople = onYantra.length > 0 || invitable.length > 0;\n\n const shell = (body: React.ReactNode) => (\n <View style={[styles.fill, { backgroundColor: theme.bg }]}>\n <ContactsHeader theme={theme} onBack={onBack} />\n {permission === 'granted' ? (\n <View style={[styles.searchWrap, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name=\"magnify\" size={20} color={theme.textMuted} />\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder=\"Search name or number\"\n placeholderTextColor={theme.textMuted}\n style={[styles.searchInput, { color: theme.text }]}\n />\n </View>\n ) : null}\n {body}\n </View>\n );\n\n if (permission === 'unsupported' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"cellphone-off\"\n title=\"Not available in this build\"\n detail=\"Finding people from your contacts needs a newer version of the app.\"\n theme={theme}\n action={{ label: 'Back to chats', onPress: onBack }}\n />\n </View>,\n );\n }\n\n if (permission === 'denied' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"lock-outline\"\n title=\"Contacts access is off\"\n detail=\"Allow contacts access in Settings to find people you know on Yantra Chat.\"\n theme={theme}\n action={{ label: 'Open Settings', onPress: onOpenSettings }}\n />\n </View>,\n );\n }\n\n if (permission === 'undetermined' && !hasPeople) {\n return shell(\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"account-multiple-outline\"\n title=\"Find people you know\"\n detail=\"Allow contacts to see who is on Yantra Chat and invite everyone else.\"\n theme={theme}\n action={{ label: 'Find from contacts', onPress: onRequestAccess }}\n />\n </View>,\n );\n }\n\n if (loading && !sections.length) {\n return shell(\n <View style={styles.centerFill}>\n <ActivityIndicator color={theme.accent} />\n <Text style={[styles.loadingText, { color: theme.textMuted }]}>Checking your contacts…</Text>\n </View>,\n );\n }\n\n // A limited selection is never a dead end: the user can share more contacts from here.\n const listHeader =\n permission === 'granted' && limited ? (\n <View>\n <View style={[styles.limitedNote, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name=\"account-lock-outline\" size={20} color={theme.textMuted} />\n <Text style={[styles.limitedText, { color: theme.textMuted }]}>\n You shared only some contacts with Yantra. People you did not share will not show here.\n </Text>\n </View>\n {onChooseMoreContacts ? (\n <ActionRow\n icon=\"account-multiple-plus-outline\"\n label=\"Choose more contacts\"\n theme={theme}\n onPress={onChooseMoreContacts}\n testID=\"chat-contacts-choose-more\"\n />\n ) : null}\n </View>\n ) : null;\n\n return shell(\n <>\n <InlineBanner message={error} theme={theme} onDismiss={onDismissError} />\n <SectionList\n ListHeaderComponent={listHeader}\n sections={sections}\n keyExtractor={(item) => item.id}\n stickySectionHeadersEnabled={false}\n renderSectionHeader={({ section }) => (\n <Text style={[styles.sectionTitle, { color: theme.textMuted, backgroundColor: theme.bg }]}>\n {section.title}\n </Text>\n )}\n renderItem={({ item, section }) => (\n <ContactRow\n contact={item}\n theme={theme}\n busy={busyContactId === item.id}\n mode={section.key === 'here' ? 'chat' : 'invite'}\n onPress={() => (section.key === 'here' ? onStartChat(item) : onInvite(item))}\n />\n )}\n ListEmptyComponent={\n <View style={styles.centerFill}>\n <EmptyState\n icon=\"account-search-outline\"\n title=\"No contacts found\"\n detail={\n limited && !query.trim()\n ? 'Choose more contacts to share with Yantra.'\n : 'Add phone numbers to your contacts, or search with a different name.'\n }\n theme={theme}\n action={{ label: 'Back to chats', onPress: onBack }}\n />\n </View>\n }\n contentContainerStyle={sections.length === 0 ? styles.listEmptyGrow : undefined}\n testID=\"chat-contacts-list\"\n />\n </>,\n );\n}\n\nfunction ActionRow({\n icon,\n label,\n theme,\n onPress,\n testID,\n}: {\n icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'];\n label: string;\n theme: ChatTheme;\n onPress: () => void;\n testID: string;\n}) {\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({ width: '100%', backgroundColor: pressed ? theme.surface : theme.bg })}\n testID={testID}\n >\n {/* Inner wrapper: NativeWind can strip flexDirection from Pressable styles. */}\n <View style={styles.row}>\n <View style={[styles.actionIcon, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name={icon} size={22} color={theme.accent} />\n </View>\n <View style={[styles.rowBody, { borderBottomColor: theme.border }]}>\n <Text style={[styles.rowName, { color: theme.accent }]}>{label}</Text>\n </View>\n </View>\n </Pressable>\n );\n}\n\nfunction ContactRow({\n contact,\n theme,\n mode,\n busy,\n onPress,\n}: {\n contact: MatchedContact;\n theme: ChatTheme;\n mode: 'chat' | 'invite';\n busy: boolean;\n onPress: () => void;\n}) {\n const name = contact.match?.displayName || contact.displayName;\n return (\n <Pressable\n onPress={onPress}\n disabled={busy}\n accessibilityRole=\"button\"\n accessibilityLabel={mode === 'chat' ? `Message ${name}` : `Invite ${name}`}\n style={({ pressed }) => ({\n width: '100%',\n backgroundColor: pressed ? theme.surface : theme.bg,\n })}\n testID={`chat-contact-${contact.id}`}\n >\n {/* Inner wrapper: NativeWind can strip flexDirection from Pressable styles. */}\n <View style={styles.row}>\n <Avatar name={name} uri={contact.match?.picture ?? contact.imageUri} theme={theme} size={44} />\n <View style={[styles.rowBody, { borderBottomColor: theme.border }]}>\n <View style={styles.rowText}>\n <Text numberOfLines={1} style={[styles.rowName, { color: theme.text }]}>\n {name}\n </Text>\n <Text numberOfLines={1} style={[styles.rowPhone, { color: theme.textMuted }]}>\n {contact.rawNumbers?.[0] ?? contact.e164[0]}\n </Text>\n </View>\n {busy ? (\n <ActivityIndicator size=\"small\" color={theme.accent} />\n ) : mode === 'invite' ? (\n <View style={[styles.inviteChip, { borderColor: theme.accent }]}>\n <Text style={[styles.inviteText, { color: theme.accent }]}>Invite</Text>\n </View>\n ) : null}\n </View>\n </View>\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n centerFill: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: 28,\n minHeight: 360,\n },\n listEmptyGrow: { flexGrow: 1 },\n loadingText: { fontSize: 14, marginTop: 12 },\n searchWrap: {\n flexDirection: 'row',\n alignItems: 'center',\n marginHorizontal: 16,\n marginVertical: 8,\n borderRadius: 10,\n paddingHorizontal: 12,\n height: 36,\n },\n searchInput: { flex: 1, marginLeft: 8, fontSize: 16, paddingVertical: 0 },\n row: { flexDirection: 'row', alignItems: 'center', width: '100%', paddingLeft: 16 },\n rowBody: {\n flex: 1,\n flexDirection: 'row',\n alignItems: 'center',\n marginLeft: 12,\n paddingVertical: 12,\n paddingRight: 16,\n minHeight: 64,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n rowText: { flex: 1, minWidth: 0, marginRight: 12 },\n rowName: { fontSize: 16, fontWeight: '600' },\n rowPhone: { fontSize: 13, marginTop: 2 },\n actionIcon: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center' },\n limitedNote: {\n flexDirection: 'row',\n alignItems: 'flex-start',\n marginHorizontal: 16,\n marginTop: 4,\n marginBottom: 4,\n padding: 12,\n borderRadius: 10,\n },\n limitedText: { flex: 1, fontSize: 13, lineHeight: 18, marginLeft: 10 },\n inviteChip: { borderWidth: 1, borderRadius: 14, paddingHorizontal: 12, paddingVertical: 4 },\n inviteText: { fontSize: 13, fontWeight: '600' },\n sectionTitle: {\n fontSize: 13,\n fontWeight: '600',\n paddingHorizontal: 16,\n paddingTop: 14,\n paddingBottom: 6,\n },\n});\n"],"names":[],"mappings":"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"}
|
|
@@ -114,16 +114,6 @@ const REGISTER_YANTRA_CHAT_MEMBER_MUTATION = `
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
`;
|
|
117
|
-
const YANTRA_CHAT_MEMBERS_QUERY = `
|
|
118
|
-
query YantraChatMembers {
|
|
119
|
-
yantraChatMembers {
|
|
120
|
-
phoneE164
|
|
121
|
-
userId
|
|
122
|
-
displayName
|
|
123
|
-
avatarUrl
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
`;
|
|
127
117
|
const YANTRA_CHAT_INVITER_QUERY = `
|
|
128
118
|
query YantraChatInviter($userId: String!) {
|
|
129
119
|
yantraChatInviter(userId: $userId) {
|
|
@@ -132,4 +122,4 @@ const YANTRA_CHAT_INVITER_QUERY = `
|
|
|
132
122
|
avatarUrl
|
|
133
123
|
}
|
|
134
124
|
}
|
|
135
|
-
`;export{CHANNELS_QUERY,CONTACT_MATCHES_QUERY,CREATE_DIRECT_CHANNEL_MUTATION,MESSAGES_QUERY,MESSAGE_ADDED_SUBSCRIPTION_WS,POST_FIELDS,REACTION_CHANGED_SUBSCRIPTION_WS,REGISTER_YANTRA_CHAT_MEMBER_MUTATION,SEND_MESSAGE_MUTATION,SET_MESSAGE_REACTION_MUTATION,VIEW_CHANNEL_MUTATION,YANTRA_CHAT_INVITER_QUERY,
|
|
125
|
+
`;export{CHANNELS_QUERY,CONTACT_MATCHES_QUERY,CREATE_DIRECT_CHANNEL_MUTATION,MESSAGES_QUERY,MESSAGE_ADDED_SUBSCRIPTION_WS,POST_FIELDS,REACTION_CHANGED_SUBSCRIPTION_WS,REGISTER_YANTRA_CHAT_MEMBER_MUTATION,SEND_MESSAGE_MUTATION,SET_MESSAGE_REACTION_MUTATION,VIEW_CHANNEL_MUTATION,YANTRA_CHAT_INVITER_QUERY,YANTRA_CHAT_REGISTRATION_STATUS_QUERY};//# sourceMappingURL=chatOperations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatOperations.js","sources":["../../../src/features/messaging/chatOperations.ts"],"sourcesContent":["/**\n * Every GraphQL operation the chat remote sends, as plain strings.\n *\n * Strings, not `gql` documents, because the remote must not import\n * @apollo/client or graphql - the host parses and executes these on the app's\n * own client (see `ChatGraphQLSeam`). The upside beyond bundle size is that the\n * QUERY SHAPE ships over the air: a missing field or a wrong argument is a\n * surface deploy, not an App Store release.\n *\n * HARD REQUIREMENT - every operation is NAMED, and every subscription's name\n * ends in `_WS`. `portable-devices/mobile/src/config/base-apollo-client.ts`\n * routes ws-vs-http with `operationName.endsWith('_WS')`; an anonymous\n * operation makes that `undefined.endsWith(...)`, which throws for EVERY\n * operation the app sends, not just this one. `chatOperations.test.ts` asserts\n * both properties over every export in this file, so a new operation cannot\n * quietly break the app's transport.\n */\n\n/**\n * Shared field set for a message.\n *\n * `author` and `creator` are both selected on purpose: messenger-box populates\n * `author` on some write paths and `creator` on others (`sendMessage` sets\n * creator), and a transcript that reads only one shows \"Unknown\" for half the\n * messages. `chatModel.pickAuthor` collapses them.\n */\nexport const POST_FIELDS = `\n id\n message\n type\n createdAt\n updatedAt\n editedAt\n deletedAt\n rootId\n parentId\n filenames\n props\n author { id givenName familyName username email picture }\n creator { id givenName familyName username email picture }\n reactionSummary { reaction count userIds }\n`;\n\nconst CHANNEL_FIELDS = `\n id\n type\n orgName\n title\n displayName\n topic\n lastPostAt\n totalMsgCount\n createdAt\n lastMessage { ${POST_FIELDS} }\n creator { id givenName familyName username email picture }\n members { id user { id givenName familyName username email picture } msgCount lastViewedAt }\n`;\n\n/**\n * The conversation list.\n *\n * `criteria.members.$elemMatch.user` is NOT sent: the server derives membership\n * from the session (`accountId`) and a client-supplied member filter is exactly\n * the kind of thing that lets a caller read someone else's list. Org scoping is\n * likewise server-side now (`channelOrgScope`). Native Messages passes\n * `criteria: { type: DIRECT, orgName: yantra-chat }` via `nativeChatChannelQuery`.\n */\nexport const CHANNELS_QUERY = `\n query ChatChannels($limit: Int, $skip: Int, $criteria: AnyObject, $sort: Sort) {\n channelsByUser(limit: $limit, skip: $skip, criteria: $criteria, sort: $sort) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * One page of a transcript, newest first.\n *\n * Paged with `skip`, which is what the server exposes. That is offset paging and\n * it can repeat or drop a row if messages arrive mid-scroll; `chatModel.mergePosts`\n * de-duplicates by id so the visible effect is bounded. A cursor (`before: id`)\n * would be strictly better and is worth adding server-side later.\n */\nexport const MESSAGES_QUERY = `\n query ChatMessages($channelId: ID, $limit: Int! = 30, $skip: Int! = 0, $sort: Sort) {\n messages(channelId: $channelId, limit: $limit, skip: $skip, sort: $sort) {\n totalCount\n data {\n ${POST_FIELDS}\n }\n }\n }\n`;\n\nexport const SEND_MESSAGE_MUTATION = `\n mutation ChatSendMessage($channelId: String!, $messageInput: MessageInput!) {\n sendMessage(channelId: $channelId, messageInput: $messageInput) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/** Marks the channel read for the caller; drives the unread badge to zero. */\nexport const VIEW_CHANNEL_MUTATION = `\n mutation ChatViewChannel($id: ID!) {\n viewChannel(id: $id)\n }\n`;\n\n/**\n * Opens (or returns) the 1:1 conversation with `receiver`.\n *\n * Idempotent server-side: an existing DM for the pair comes back rather than a\n * duplicate, so the UI can call this every time someone taps a contact without\n * checking first.\n */\nexport const CREATE_DIRECT_CHANNEL_MUTATION = `\n mutation ChatCreateDirectChannel($receiver: [ID]!, $displayName: String!) {\n createDirectChannel(receiver: $receiver, displayName: $displayName) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * Live messages for one channel.\n *\n * `_WS` suffix is load-bearing - see the file header. `directTo` is left unset:\n * the server treats it as a second, user-scoped topic and passing the wrong one\n * silently delivers nothing.\n */\nexport const MESSAGE_ADDED_SUBSCRIPTION_WS = `\n subscription ChatMessageAdded_WS($channelId: String) {\n chatMessageAdded(channelId: $channelId) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/**\n * Set the viewer's single Tapback on a message; `reaction: null` removes it.\n * Returns the message's reactions after the change.\n */\nexport const SET_MESSAGE_REACTION_MUTATION = `\n mutation ChatSetMessageReaction($channelId: String!, $postId: ID!, $reaction: String) {\n setMessageReaction(channelId: $channelId, postId: $postId, reaction: $reaction) {\n reaction\n count\n userIds\n }\n }\n`;\n\n/** Live reaction changes for one channel. `_WS` suffix is load-bearing - see the file header. */\nexport const REACTION_CHANGED_SUBSCRIPTION_WS = `\n subscription ChatReactionChanged_WS($channelId: String!) {\n chatReactionChanged(channelId: $channelId) {\n channelId\n postId\n reactions { reaction count userIds }\n }\n }\n`;\n\n/**\n * Which of these phone numbers already have an account.\n *\n * Sends SHA-256 hashes of E.164 numbers, never the numbers - the server never\n * receives a plaintext address book. Returns AnyObject: a list of\n * `{phoneHash, userId, username, displayName, picture}`.\n */\nexport const CONTACT_MATCHES_QUERY = `\n query ChatContactMatches($phoneHashes: [String!]!) {\n chatContactMatches(phoneHashes: $phoneHashes)\n }\n`;\n\nexport const YANTRA_CHAT_REGISTRATION_STATUS_QUERY = `\n query YantraChatRegistrationStatus {\n yantraChatRegistrationStatus {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const REGISTER_YANTRA_CHAT_MEMBER_MUTATION = `\n mutation RegisterYantraChatMember($phoneE164: String!, $displayName: String) {\n registerYantraChatMember(phoneE164: $phoneE164, displayName: $displayName) {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const MATCH_YANTRA_CHAT_MEMBERS_QUERY = `\n query MatchYantraChatMembersByPhone($phones: [String!]!, $viewerPhoneE164: String) {\n matchYantraChatMembersByPhone(phones: $phones, viewerPhoneE164: $viewerPhoneE164) {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\nexport const YANTRA_CHAT_MEMBERS_QUERY = `\n query YantraChatMembers {\n yantraChatMembers {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/**\n * The sender of a chat invite link, as the server knows them. The link's own `n=` name\n * is editable by anyone, so the conversation is opened with this name, and only when\n * the id belongs to a registered member (null otherwise).\n */\nexport const YANTRA_CHAT_INVITER_QUERY = `\n query YantraChatInviter($userId: String!) {\n yantraChatInviter(userId: $userId) {\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/** Composer draft persistence, server-side and per (member, channel). */\nexport const SET_DRAFT_MUTATION = `\n mutation ChatSetDraft($channelId: ID!, $text: String!) {\n setChannelDraft(channelId: $channelId, text: $text)\n }\n`;\n\nexport const DRAFT_QUERY = `\n query ChatDraft($channelId: ID!) {\n channelDraft(channelId: $channelId)\n }\n`;\n\n/** Every operation, for the contract test and for host-side preflight. */\nexport const CHAT_OPERATIONS: Readonly<Record<string, string>> = Object.freeze({\n CHANNELS_QUERY,\n CONTACT_MATCHES_QUERY,\n YANTRA_CHAT_REGISTRATION_STATUS_QUERY,\n REGISTER_YANTRA_CHAT_MEMBER_MUTATION,\n MATCH_YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_INVITER_QUERY,\n MESSAGES_QUERY,\n SEND_MESSAGE_MUTATION,\n VIEW_CHANNEL_MUTATION,\n CREATE_DIRECT_CHANNEL_MUTATION,\n MESSAGE_ADDED_SUBSCRIPTION_WS,\n SET_MESSAGE_REACTION_MUTATION,\n REACTION_CHANGED_SUBSCRIPTION_WS,\n SET_DRAFT_MUTATION,\n DRAFT_QUERY,\n});\n\n/**\n * The operation name inside a document string.\n *\n * Used by the contract test and by the host seam for logging. Intentionally a\n * regex rather than a parse: pulling in `graphql` to read a name would defeat\n * the whole point of shipping strings.\n */\nexport function operationNameOf(document: string): string | null {\n const match = /\\b(?:query|mutation|subscription)\\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(document);\n return match ? match[1] : null;\n}\n\nexport function operationTypeOf(document: string): 'query' | 'mutation' | 'subscription' | null {\n const match = /\\b(query|mutation|subscription)\\s+[A-Za-z_]/.exec(document);\n return match ? (match[1] as 'query' | 'mutation' | 'subscription') : null;\n}\n"],"names":[],"mappings":"AA0BO,MAAM,WAAc,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgB3B,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAA,EAUH,WAAW,CAAA;AAAA;AAAA;AAAA,CAAA;AAcxB,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA,YAAA,EAGhB,cAAc;AAAA;AAAA;AAAA;AAarB,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAA,EAKZ,WAAW;AAAA;AAAA;AAAA;AAAA;AAKtB,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA,YAAA,EAGvB,WAAW;AAAA;AAAA;AAAA;AAMlB,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA;AAAA;AAa9B,MAAM,8BAAiC,GAAA;AAAA;AAAA;AAAA,YAAA,EAGhC,cAAc;AAAA;AAAA;AAAA;AAYrB,MAAM,6BAAgC,GAAA;AAAA;AAAA;AAAA,YAAA,EAG/B,WAAW;AAAA;AAAA;AAAA;AASlB,MAAM,6BAAgC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWtC,MAAM,gCAAmC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBzC,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA;AAAA;AAK9B,MAAM,qCAAwC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU9C,MAAM,oCAAuC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB7C,MAAM,yBAA4B,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBlC,MAAM,yBAA4B,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"chatOperations.js","sources":["../../../src/features/messaging/chatOperations.ts"],"sourcesContent":["/**\n * Every GraphQL operation the chat remote sends, as plain strings.\n *\n * Strings, not `gql` documents, because the remote must not import\n * @apollo/client or graphql - the host parses and executes these on the app's\n * own client (see `ChatGraphQLSeam`). The upside beyond bundle size is that the\n * QUERY SHAPE ships over the air: a missing field or a wrong argument is a\n * surface deploy, not an App Store release.\n *\n * HARD REQUIREMENT - every operation is NAMED, and every subscription's name\n * ends in `_WS`. `portable-devices/mobile/src/config/base-apollo-client.ts`\n * routes ws-vs-http with `operationName.endsWith('_WS')`; an anonymous\n * operation makes that `undefined.endsWith(...)`, which throws for EVERY\n * operation the app sends, not just this one. `chatOperations.test.ts` asserts\n * both properties over every export in this file, so a new operation cannot\n * quietly break the app's transport.\n */\n\n/**\n * Shared field set for a message.\n *\n * `author` and `creator` are both selected on purpose: messenger-box populates\n * `author` on some write paths and `creator` on others (`sendMessage` sets\n * creator), and a transcript that reads only one shows \"Unknown\" for half the\n * messages. `chatModel.pickAuthor` collapses them.\n */\nexport const POST_FIELDS = `\n id\n message\n type\n createdAt\n updatedAt\n editedAt\n deletedAt\n rootId\n parentId\n filenames\n props\n author { id givenName familyName username email picture }\n creator { id givenName familyName username email picture }\n reactionSummary { reaction count userIds }\n`;\n\nconst CHANNEL_FIELDS = `\n id\n type\n orgName\n title\n displayName\n topic\n lastPostAt\n totalMsgCount\n createdAt\n lastMessage { ${POST_FIELDS} }\n creator { id givenName familyName username email picture }\n members { id user { id givenName familyName username email picture } msgCount lastViewedAt }\n`;\n\n/**\n * The conversation list.\n *\n * `criteria.members.$elemMatch.user` is NOT sent: the server derives membership\n * from the session (`accountId`) and a client-supplied member filter is exactly\n * the kind of thing that lets a caller read someone else's list. Org scoping is\n * likewise server-side now (`channelOrgScope`). Native Messages passes\n * `criteria: { type: DIRECT, orgName: yantra-chat }` via `nativeChatChannelQuery`.\n */\nexport const CHANNELS_QUERY = `\n query ChatChannels($limit: Int, $skip: Int, $criteria: AnyObject, $sort: Sort) {\n channelsByUser(limit: $limit, skip: $skip, criteria: $criteria, sort: $sort) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * One page of a transcript, newest first.\n *\n * Paged with `skip`, which is what the server exposes. That is offset paging and\n * it can repeat or drop a row if messages arrive mid-scroll; `chatModel.mergePosts`\n * de-duplicates by id so the visible effect is bounded. A cursor (`before: id`)\n * would be strictly better and is worth adding server-side later.\n */\nexport const MESSAGES_QUERY = `\n query ChatMessages($channelId: ID, $limit: Int! = 30, $skip: Int! = 0, $sort: Sort) {\n messages(channelId: $channelId, limit: $limit, skip: $skip, sort: $sort) {\n totalCount\n data {\n ${POST_FIELDS}\n }\n }\n }\n`;\n\nexport const SEND_MESSAGE_MUTATION = `\n mutation ChatSendMessage($channelId: String!, $messageInput: MessageInput!) {\n sendMessage(channelId: $channelId, messageInput: $messageInput) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/** Marks the channel read for the caller; drives the unread badge to zero. */\nexport const VIEW_CHANNEL_MUTATION = `\n mutation ChatViewChannel($id: ID!) {\n viewChannel(id: $id)\n }\n`;\n\n/**\n * Opens (or returns) the 1:1 conversation with `receiver`.\n *\n * Idempotent server-side: an existing DM for the pair comes back rather than a\n * duplicate, so the UI can call this every time someone taps a contact without\n * checking first.\n */\nexport const CREATE_DIRECT_CHANNEL_MUTATION = `\n mutation ChatCreateDirectChannel($receiver: [ID]!, $displayName: String!) {\n createDirectChannel(receiver: $receiver, displayName: $displayName) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * Live messages for one channel.\n *\n * `_WS` suffix is load-bearing - see the file header. `directTo` is left unset:\n * the server treats it as a second, user-scoped topic and passing the wrong one\n * silently delivers nothing.\n */\nexport const MESSAGE_ADDED_SUBSCRIPTION_WS = `\n subscription ChatMessageAdded_WS($channelId: String) {\n chatMessageAdded(channelId: $channelId) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/**\n * Set the viewer's single Tapback on a message; `reaction: null` removes it.\n * Returns the message's reactions after the change.\n */\nexport const SET_MESSAGE_REACTION_MUTATION = `\n mutation ChatSetMessageReaction($channelId: String!, $postId: ID!, $reaction: String) {\n setMessageReaction(channelId: $channelId, postId: $postId, reaction: $reaction) {\n reaction\n count\n userIds\n }\n }\n`;\n\n/** Live reaction changes for one channel. `_WS` suffix is load-bearing - see the file header. */\nexport const REACTION_CHANGED_SUBSCRIPTION_WS = `\n subscription ChatReactionChanged_WS($channelId: String!) {\n chatReactionChanged(channelId: $channelId) {\n channelId\n postId\n reactions { reaction count userIds }\n }\n }\n`;\n\n/**\n * Which of these phone numbers already have an account.\n *\n * Sends SHA-256 hashes of E.164 numbers, never the numbers - the server never\n * receives a plaintext address book. Returns AnyObject: a list of\n * `{phoneHash, userId, username, displayName, picture}`.\n */\nexport const CONTACT_MATCHES_QUERY = `\n query ChatContactMatches($phoneHashes: [String!]!) {\n chatContactMatches(phoneHashes: $phoneHashes)\n }\n`;\n\nexport const YANTRA_CHAT_REGISTRATION_STATUS_QUERY = `\n query YantraChatRegistrationStatus {\n yantraChatRegistrationStatus {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const REGISTER_YANTRA_CHAT_MEMBER_MUTATION = `\n mutation RegisterYantraChatMember($phoneE164: String!, $displayName: String) {\n registerYantraChatMember(phoneE164: $phoneE164, displayName: $displayName) {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const MATCH_YANTRA_CHAT_MEMBERS_QUERY = `\n query MatchYantraChatMembersByPhone($phones: [String!]!, $viewerPhoneE164: String) {\n matchYantraChatMembersByPhone(phones: $phones, viewerPhoneE164: $viewerPhoneE164) {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\nexport const YANTRA_CHAT_MEMBERS_QUERY = `\n query YantraChatMembers {\n yantraChatMembers {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/**\n * The sender of a chat invite link, as the server knows them. The link's own `n=` name\n * is editable by anyone, so the conversation is opened with this name, and only when\n * the id belongs to a registered member (null otherwise).\n */\nexport const YANTRA_CHAT_INVITER_QUERY = `\n query YantraChatInviter($userId: String!) {\n yantraChatInviter(userId: $userId) {\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/** Composer draft persistence, server-side and per (member, channel). */\nexport const SET_DRAFT_MUTATION = `\n mutation ChatSetDraft($channelId: ID!, $text: String!) {\n setChannelDraft(channelId: $channelId, text: $text)\n }\n`;\n\nexport const DRAFT_QUERY = `\n query ChatDraft($channelId: ID!) {\n channelDraft(channelId: $channelId)\n }\n`;\n\n/** Every operation, for the contract test and for host-side preflight. */\nexport const CHAT_OPERATIONS: Readonly<Record<string, string>> = Object.freeze({\n CHANNELS_QUERY,\n CONTACT_MATCHES_QUERY,\n YANTRA_CHAT_REGISTRATION_STATUS_QUERY,\n REGISTER_YANTRA_CHAT_MEMBER_MUTATION,\n MATCH_YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_INVITER_QUERY,\n MESSAGES_QUERY,\n SEND_MESSAGE_MUTATION,\n VIEW_CHANNEL_MUTATION,\n CREATE_DIRECT_CHANNEL_MUTATION,\n MESSAGE_ADDED_SUBSCRIPTION_WS,\n SET_MESSAGE_REACTION_MUTATION,\n REACTION_CHANGED_SUBSCRIPTION_WS,\n SET_DRAFT_MUTATION,\n DRAFT_QUERY,\n});\n\n/**\n * The operation name inside a document string.\n *\n * Used by the contract test and by the host seam for logging. Intentionally a\n * regex rather than a parse: pulling in `graphql` to read a name would defeat\n * the whole point of shipping strings.\n */\nexport function operationNameOf(document: string): string | null {\n const match = /\\b(?:query|mutation|subscription)\\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(document);\n return match ? match[1] : null;\n}\n\nexport function operationTypeOf(document: string): 'query' | 'mutation' | 'subscription' | null {\n const match = /\\b(query|mutation|subscription)\\s+[A-Za-z_]/.exec(document);\n return match ? (match[1] as 'query' | 'mutation' | 'subscription') : null;\n}\n"],"names":[],"mappings":"AA0BO,MAAM,WAAc,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgB3B,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAA,EAUH,WAAW,CAAA;AAAA;AAAA;AAAA,CAAA;AAcxB,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA,YAAA,EAGhB,cAAc;AAAA;AAAA;AAAA;AAarB,MAAM,cAAiB,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAA,EAKZ,WAAW;AAAA;AAAA;AAAA;AAAA;AAKtB,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA,YAAA,EAGvB,WAAW;AAAA;AAAA;AAAA;AAMlB,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA;AAAA;AAa9B,MAAM,8BAAiC,GAAA;AAAA;AAAA;AAAA,YAAA,EAGhC,cAAc;AAAA;AAAA;AAAA;AAYrB,MAAM,6BAAgC,GAAA;AAAA;AAAA;AAAA,YAAA,EAG/B,WAAW;AAAA;AAAA;AAAA;AASlB,MAAM,6BAAgC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWtC,MAAM,gCAAmC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBzC,MAAM,qBAAwB,GAAA;AAAA;AAAA;AAAA;AAAA;AAK9B,MAAM,qCAAwC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU9C,MAAM,oCAAuC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoC7C,MAAM,yBAA4B,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as React from'react';import {
|
|
1
|
+
import*as React from'react';import {CONTACT_MATCHES_QUERY}from'./chatOperations.js';import {regionFromE164,normalizeContacts,collectLookupNumbers}from'./phoneNumbers.js';import {sha256}from'./sha256.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -17,24 +17,10 @@ var __spreadValues = (a, b) => {
|
|
|
17
17
|
return a;
|
|
18
18
|
};
|
|
19
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
-
function
|
|
21
|
-
var _a, _b;
|
|
22
|
-
return {
|
|
23
|
-
id: `member:${row.userId}`,
|
|
24
|
-
displayName: row.displayName,
|
|
25
|
-
imageUri: (_a = row.avatarUrl) != null ? _a : null,
|
|
26
|
-
e164: row.phoneE164 ? [row.phoneE164] : [],
|
|
27
|
-
match: {
|
|
28
|
-
userId: String(row.userId),
|
|
29
|
-
displayName: row.displayName,
|
|
30
|
-
picture: (_b = row.avatarUrl) != null ? _b : null
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function mergeOnYantra(peers, fromBook, viewerUserId) {
|
|
20
|
+
function onePerUser(fromBook, viewerUserId) {
|
|
35
21
|
var _a, _b, _c, _d, _e;
|
|
36
22
|
const byUser = /* @__PURE__ */ new Map();
|
|
37
|
-
for (const row of
|
|
23
|
+
for (const row of fromBook) {
|
|
38
24
|
const uid = (_a = row.match) == null ? void 0 : _a.userId;
|
|
39
25
|
if (!uid || uid === viewerUserId || !/^[a-f0-9]{24}$/i.test(uid)) continue;
|
|
40
26
|
const existing = byUser.get(uid);
|
|
@@ -67,7 +53,6 @@ function useContactMatches(gql, contacts, viewer, phone) {
|
|
|
67
53
|
const [limited, setLimited] = React.useState(false);
|
|
68
54
|
const [loading, setLoading] = React.useState(true);
|
|
69
55
|
const [error, setError] = React.useState(null);
|
|
70
|
-
const [peers, setPeers] = React.useState([]);
|
|
71
56
|
const [fromBook, setFromBook] = React.useState([]);
|
|
72
57
|
const [invitable, setInvitable] = React.useState([]);
|
|
73
58
|
const mounted = React.useRef(true);
|
|
@@ -77,49 +62,15 @@ function useContactMatches(gql, contacts, viewer, phone) {
|
|
|
77
62
|
mounted.current = false;
|
|
78
63
|
};
|
|
79
64
|
}, []);
|
|
80
|
-
const loadPeers = React.useCallback(async () => {
|
|
81
|
-
var _a, _b, _c;
|
|
82
|
-
try {
|
|
83
|
-
let rows = [];
|
|
84
|
-
if (phone == null ? void 0 : phone.listMembers) {
|
|
85
|
-
const matches = await phone.listMembers();
|
|
86
|
-
rows = Object.values(matches).map((m) => {
|
|
87
|
-
var _a2;
|
|
88
|
-
return {
|
|
89
|
-
phoneE164: m.phoneE164,
|
|
90
|
-
userId: m.userId,
|
|
91
|
-
displayName: m.displayName,
|
|
92
|
-
avatarUrl: (_a2 = m.avatarUrl) != null ? _a2 : null
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
const res = await gql.query(YANTRA_CHAT_MEMBERS_QUERY);
|
|
97
|
-
if ((_a = res.errors) == null ? void 0 : _a.length) {
|
|
98
|
-
if (!mounted.current) return;
|
|
99
|
-
setError(res.errors[0].message);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
rows = (_c = (_b = res.data) == null ? void 0 : _b.yantraChatMembers) != null ? _c : [];
|
|
103
|
-
}
|
|
104
|
-
if (!mounted.current) return;
|
|
105
|
-
const viewerId = viewer == null ? void 0 : viewer.userId;
|
|
106
|
-
setPeers(rows.filter((r) => Boolean(r.userId) && r.userId !== viewerId && /^[a-f0-9]{24}$/i.test(r.userId)).map(memberToContact));
|
|
107
|
-
} catch (e) {
|
|
108
|
-
if (!mounted.current) return;
|
|
109
|
-
setError(e instanceof Error ? e.message : "Could not load Yantra Chat members");
|
|
110
|
-
}
|
|
111
|
-
}, [gql, phone, viewer == null ? void 0 : viewer.userId]);
|
|
112
65
|
const sync = React.useCallback(async () => {
|
|
113
66
|
var _a, _b, _c, _d;
|
|
114
67
|
if (!contacts) {
|
|
115
|
-
await loadPeers();
|
|
116
68
|
if (mounted.current) setLoading(false);
|
|
117
69
|
return;
|
|
118
70
|
}
|
|
119
71
|
setLoading(true);
|
|
120
72
|
setError(null);
|
|
121
73
|
try {
|
|
122
|
-
await loadPeers();
|
|
123
74
|
if (contacts.isLimited) {
|
|
124
75
|
const isLimited = await contacts.isLimited().catch(() => false);
|
|
125
76
|
if (mounted.current) setLimited(isLimited);
|
|
@@ -193,12 +144,7 @@ function useContactMatches(gql, contacts, viewer, phone) {
|
|
|
193
144
|
} finally {
|
|
194
145
|
if (mounted.current) setLoading(false);
|
|
195
146
|
}
|
|
196
|
-
}, [contacts, gql, viewer, phone
|
|
197
|
-
React.useEffect(() => {
|
|
198
|
-
void loadPeers().finally(() => {
|
|
199
|
-
if (mounted.current) setLoading(false);
|
|
200
|
-
});
|
|
201
|
-
}, [loadPeers]);
|
|
147
|
+
}, [contacts, gql, viewer, phone]);
|
|
202
148
|
React.useEffect(() => {
|
|
203
149
|
let cancelled = false;
|
|
204
150
|
if (!contacts) {
|
|
@@ -210,6 +156,7 @@ function useContactMatches(gql, contacts, viewer, phone) {
|
|
|
210
156
|
if (cancelled || !mounted.current) return;
|
|
211
157
|
setPermission(status);
|
|
212
158
|
if (status === "granted") void sync();
|
|
159
|
+
else setLoading(false);
|
|
213
160
|
})();
|
|
214
161
|
return () => {
|
|
215
162
|
cancelled = true;
|
|
@@ -221,13 +168,14 @@ function useContactMatches(gql, contacts, viewer, phone) {
|
|
|
221
168
|
if (!mounted.current) return;
|
|
222
169
|
setPermission(status);
|
|
223
170
|
if (status === "granted") await sync();
|
|
171
|
+
else setLoading(false);
|
|
224
172
|
}, [contacts, sync]);
|
|
225
173
|
const chooseMoreContacts = React.useMemo(() => (contacts == null ? void 0 : contacts.chooseMoreContacts) ? async () => {
|
|
226
174
|
var _a;
|
|
227
175
|
await ((_a = contacts.chooseMoreContacts) == null ? void 0 : _a.call(contacts));
|
|
228
176
|
await sync();
|
|
229
177
|
} : void 0, [contacts, sync]);
|
|
230
|
-
const onYantra = React.useMemo(() =>
|
|
178
|
+
const onYantra = React.useMemo(() => onePerUser(fromBook, viewer == null ? void 0 : viewer.userId), [fromBook, viewer == null ? void 0 : viewer.userId]);
|
|
231
179
|
return {
|
|
232
180
|
permission,
|
|
233
181
|
limited,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContactMatches.js","sources":["../../../src/features/messaging/useContactMatches.ts"],"sourcesContent":["/**\n * The contact sync: read the address book, work out who is already here.\n *\n * The whole pipeline runs in the remote, so all of it is fixable by a surface\n * deploy: normalise -> hash -> ask the server -> split into \"on Yantra\" and\n * \"invite\". Only the address-book read itself is a host seam, because that is\n * the one part needing a native module.\n *\n * With phone-based membership (issue #1312), contact matching delegates to\n * the ChatPhoneSeam, which calls the `yantra-messenger` connector to match\n * E.164 phone numbers against chat members.\n *\n * NOTHING LEAVES THE DEVICE IN PLAINTEXT (legacy mode). Numbers are hashed\n * before the query. See sha256.ts and the server's chat-contact-directory.ts\n * for what that does and does not buy - it is not anonymity, but it does mean\n * we never upload or store the address book of someone who never signed up.\n *\n * If a ChatPhoneSeam is available, it takes precedence for matching.\n */\nimport * as React from 'react';\nimport { CONTACT_MATCHES_QUERY, YANTRA_CHAT_MEMBERS_QUERY } from './chatOperations';\nimport { collectLookupNumbers, normalizeContacts, regionFromE164, type NormalizedContact } from './phoneNumbers';\nimport { sha256 } from './sha256';\nimport type { ChatContactsSeam, ChatGraphQLSeam, ChatPermissionStatus, ChatViewer, ChatPhoneSeam } from './chatHost';\n\nexport interface MatchedContact extends NormalizedContact {\n /** Present when this contact already has an account. */\n match?: { userId: string; displayName: string; picture?: string | null };\n}\n\nexport interface ContactMatchesState {\n permission: ChatPermissionStatus | 'unsupported';\n /** Granted for selected contacts only (iOS 18 Limited Access). */\n limited: boolean;\n loading: boolean;\n error: string | null;\n /** Contacts already on Yantra - tap to open a DM. */\n onYantra: MatchedContact[];\n /** Everyone else - tap to invite. */\n invitable: MatchedContact[];\n /** Prompts, then syncs on success. */\n requestAccess: () => Promise<void>;\n /** Re-read the address book and re-match. */\n refresh: () => Promise<void>;\n openSettings: () => Promise<void>;\n /** Present when the host can show the iOS contact access picker. */\n chooseMoreContacts?: () => Promise<void>;\n}\n\ninterface DirectoryMember {\n phoneE164: string;\n userId: string;\n displayName: string;\n avatarUrl?: string | null;\n}\n\nfunction memberToContact(row: DirectoryMember): MatchedContact {\n return {\n id: `member:${row.userId}`,\n displayName: row.displayName,\n imageUri: row.avatarUrl ?? null,\n e164: row.phoneE164 ? [row.phoneE164] : [],\n match: {\n userId: String(row.userId),\n displayName: row.displayName,\n picture: row.avatarUrl ?? null,\n },\n };\n}\n\nfunction mergeOnYantra(\n peers: MatchedContact[],\n fromBook: MatchedContact[],\n viewerUserId?: string | null,\n): MatchedContact[] {\n const byUser = new Map<string, MatchedContact>();\n for (const row of [...peers, ...fromBook]) {\n const uid = row.match?.userId;\n if (!uid || uid === viewerUserId || !/^[a-f0-9]{24}$/i.test(uid)) continue;\n const existing = byUser.get(uid);\n if (!existing) {\n byUser.set(uid, row);\n continue;\n }\n byUser.set(uid, {\n ...existing,\n displayName: existing.displayName || row.displayName,\n imageUri: existing.imageUri || row.imageUri,\n e164: Array.from(new Set([...existing.e164, ...row.e164])),\n match: {\n ...existing.match!,\n displayName: existing.match?.displayName || row.match?.displayName || existing.displayName,\n picture: existing.match?.picture || row.match?.picture,\n },\n });\n }\n return Array.from(byUser.values()).sort((a, b) => a.displayName.localeCompare(b.displayName));\n}\n\ninterface WireMatch {\n phoneHash?: string;\n userId?: string;\n displayName?: string | null;\n username?: string | null;\n picture?: string | null;\n}\n\n/**\n * The server returns AnyObject, so the payload arrives untyped and possibly as\n * a bare array or wrapped. Normalising defensively here beats trusting a shape\n * the schema does not enforce.\n */\nfunction readMatches(data: unknown): WireMatch[] {\n const raw = (data as { chatContactMatches?: unknown })?.chatContactMatches;\n if (Array.isArray(raw)) return raw as WireMatch[];\n if (raw && typeof raw === 'object' && Array.isArray((raw as { matches?: unknown }).matches)) {\n return (raw as { matches: WireMatch[] }).matches;\n }\n return [];\n}\n\nexport function useContactMatches(\n gql: ChatGraphQLSeam,\n contacts: ChatContactsSeam | undefined,\n viewer: ChatViewer | null,\n phone?: ChatPhoneSeam,\n): ContactMatchesState {\n const [permission, setPermission] = React.useState<ChatPermissionStatus | 'unsupported'>(\n contacts ? 'undetermined' : 'unsupported',\n );\n const [limited, setLimited] = React.useState(false);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n const [peers, setPeers] = React.useState<MatchedContact[]>([]);\n const [fromBook, setFromBook] = React.useState<MatchedContact[]>([]);\n const [invitable, setInvitable] = React.useState<MatchedContact[]>([]);\n const mounted = React.useRef(true);\n\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const loadPeers = React.useCallback(async () => {\n try {\n let rows: DirectoryMember[] = [];\n if (phone?.listMembers) {\n const matches = await phone.listMembers();\n rows = Object.values(matches).map((m) => ({\n phoneE164: m.phoneE164,\n userId: m.userId,\n displayName: m.displayName,\n avatarUrl: m.avatarUrl ?? null,\n }));\n } else {\n const res = await gql.query<{ yantraChatMembers?: DirectoryMember[] | null }>(\n YANTRA_CHAT_MEMBERS_QUERY,\n );\n if (res.errors?.length) {\n if (!mounted.current) return;\n setError(res.errors[0].message);\n return;\n }\n rows = res.data?.yantraChatMembers ?? [];\n }\n if (!mounted.current) return;\n const viewerId = viewer?.userId;\n setPeers(\n rows\n .filter((r) => Boolean(r.userId) && r.userId !== viewerId && /^[a-f0-9]{24}$/i.test(r.userId))\n .map(memberToContact),\n );\n } catch (e) {\n if (!mounted.current) return;\n setError(e instanceof Error ? e.message : 'Could not load Yantra Chat members');\n }\n }, [gql, phone, viewer?.userId]);\n\n const sync = React.useCallback(async () => {\n if (!contacts) {\n await loadPeers();\n if (mounted.current) setLoading(false);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n await loadPeers();\n // Re-checked on every sync: the user can widen or narrow access in Settings at any time.\n if (contacts.isLimited) {\n const isLimited = await contacts.isLimited().catch(() => false);\n if (mounted.current) setLimited(isLimited);\n }\n const raw = await contacts.read();\n // Normalisation drops unmatchable numbers and the viewer's own\n // card, which almost everyone has saved.\n // Numbers saved without a country code are read against the device region AND the\n // viewer's verified country: the device may report US for a user whose contacts are Indian.\n const regions = [contacts.defaultRegion, regionFromE164(viewer?.phoneE164)];\n const normalized = normalizeContacts(raw, regions, viewer?.phoneE164 ?? null);\n const numbers = collectLookupNumbers(normalized);\n\n if (!numbers.length) {\n if (!mounted.current) return;\n setFromBook([]);\n setInvitable(normalized);\n setLoading(false);\n return;\n }\n\n let matchedNumbers: Map<string, any>;\n\n if (phone?.matchContacts) {\n // New phone-based membership: use connector to match by E.164 phone number\n try {\n const matches = await phone.matchContacts(numbers);\n matchedNumbers = new Map(Object.entries(matches).map(([phoneE164, match]) => [phoneE164, match]));\n } catch (err) {\n if (!mounted.current) return;\n setError(err instanceof Error ? err.message : 'Could not match contacts');\n setFromBook([]);\n setInvitable(normalized);\n setLoading(false);\n return;\n }\n } else {\n // Legacy hashed directory approach\n const byHash = new Map<string, string>();\n for (const n of numbers) byHash.set(sha256(n), n);\n\n const res = await gql.query(CONTACT_MATCHES_QUERY, { phoneHashes: Array.from(byHash.keys()) });\n if (!mounted.current) return;\n\n matchedNumbers = new Map<string, any>();\n for (const m of readMatches(res.data)) {\n const number = m?.phoneHash ? byHash.get(m.phoneHash) : undefined;\n if (number && m?.userId) matchedNumbers.set(number, m);\n }\n\n if (res.errors?.length) {\n setError(res.errors[0].message);\n }\n }\n\n const here: MatchedContact[] = [];\n const rest: MatchedContact[] = [];\n for (const c of normalized) {\n const hit = c.e164.map((n) => matchedNumbers.get(n)).find(Boolean);\n if (hit?.userId) {\n here.push({\n ...c,\n match: {\n userId: String(hit.userId),\n // Prefer the name from THEIR account over the one in\n // your address book only when yours is missing -\n // you called them \"Mum\" for a reason.\n displayName: c.displayName || hit.displayName || hit.username || 'Unknown',\n picture: hit.avatarUrl ?? hit.picture ?? null,\n },\n });\n } else {\n rest.push(c);\n }\n }\n\n here.sort((a, b) => a.displayName.localeCompare(b.displayName));\n rest.sort((a, b) => a.displayName.localeCompare(b.displayName));\n setFromBook(here);\n setInvitable(rest);\n } catch (e) {\n if (!mounted.current) return;\n setError(e instanceof Error ? e.message : 'Could not read your contacts.');\n } finally {\n if (mounted.current) setLoading(false);\n }\n }, [contacts, gql, viewer, phone, loadPeers]);\n\n React.useEffect(() => {\n void loadPeers().finally(() => {\n if (mounted.current) setLoading(false);\n });\n }, [loadPeers]);\n\n // Check the CURRENT status without prompting, so the screen can offer the\n // right action - ask, or deep-link to Settings after a previous denial.\n React.useEffect(() => {\n let cancelled = false;\n if (!contacts) {\n setPermission('unsupported');\n return () => undefined;\n }\n void (async () => {\n const status = await contacts.getPermission();\n if (cancelled || !mounted.current) return;\n setPermission(status);\n if (status === 'granted') void sync();\n })();\n return () => {\n cancelled = true;\n };\n }, [contacts, sync]);\n\n const requestAccess = React.useCallback(async () => {\n if (!contacts) return;\n const status = await contacts.requestPermission();\n if (!mounted.current) return;\n setPermission(status);\n if (status === 'granted') await sync();\n }, [contacts, sync]);\n\n // Reopens the address book afterwards: the user may have just shared more people.\n const chooseMoreContacts = React.useMemo(\n () =>\n contacts?.chooseMoreContacts\n ? async () => {\n await contacts.chooseMoreContacts?.();\n await sync();\n }\n : undefined,\n [contacts, sync],\n );\n\n const onYantra = React.useMemo(\n () => mergeOnYantra(peers, fromBook, viewer?.userId),\n [peers, fromBook, viewer?.userId],\n );\n\n return {\n permission,\n limited,\n loading,\n error,\n onYantra,\n invitable,\n requestAccess,\n refresh: sync,\n openSettings: async () => contacts?.openSettings(),\n chooseMoreContacts,\n };\n}\n"],"names":["_a"],"mappings":";;;;;;;;;;;;;;;;;;;AAwDA,SAAS,gBAAgB,GAAsC,EAAA;AAxD/D,EAAA,IAAA,EAAA,EAAA,EAAA;AAyDE,EAAO,OAAA;AAAA,IACL,EAAA,EAAI,CAAU,OAAA,EAAA,GAAA,CAAI,MAAM,CAAA,CAAA;AAAA,IACxB,aAAa,GAAI,CAAA,WAAA;AAAA,IACjB,QAAA,EAAA,CAAU,EAAI,GAAA,GAAA,CAAA,SAAA,KAAJ,IAAiB,GAAA,EAAA,GAAA,IAAA;AAAA,IAC3B,MAAM,GAAI,CAAA,SAAA,GAAY,CAAC,GAAI,CAAA,SAAS,IAAI,EAAC;AAAA,IACzC,KAAO,EAAA;AAAA,MACL,MAAA,EAAQ,MAAO,CAAA,GAAA,CAAI,MAAM,CAAA;AAAA,MACzB,aAAa,GAAI,CAAA,WAAA;AAAA,MACjB,OAAA,EAAA,CAAS,EAAI,GAAA,GAAA,CAAA,SAAA,KAAJ,IAAiB,GAAA,EAAA,GAAA;AAAA;AAC5B,GACF;AACF;AACA,SAAS,aAAA,CAAc,KAAyB,EAAA,QAAA,EAA4B,YAAgD,EAAA;AArE5H,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAsEE,EAAM,MAAA,MAAA,uBAAa,GAA4B,EAAA;AAC/C,EAAA,KAAA,MAAW,OAAO,CAAC,GAAG,KAAO,EAAA,GAAG,QAAQ,CAAG,EAAA;AACzC,IAAM,MAAA,GAAA,GAAA,CAAM,EAAI,GAAA,GAAA,CAAA,KAAA,KAAJ,IAAW,GAAA,MAAA,GAAA,EAAA,CAAA,MAAA;AACvB,IAAI,IAAA,CAAC,OAAO,GAAQ,KAAA,YAAA,IAAgB,CAAC,iBAAkB,CAAA,IAAA,CAAK,GAAG,CAAG,EAAA;AAClE,IAAM,MAAA,QAAA,GAAW,MAAO,CAAA,GAAA,CAAI,GAAG,CAAA;AAC/B,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAO,MAAA,CAAA,GAAA,CAAI,KAAK,GAAG,CAAA;AACnB,MAAA;AAAA;AAEF,IAAO,MAAA,CAAA,GAAA,CAAI,GAAK,EAAA,aAAA,CAAA,cAAA,CAAA,EAAA,EACX,QADW,CAAA,EAAA;AAAA,MAEd,WAAA,EAAa,QAAS,CAAA,WAAA,IAAe,GAAI,CAAA,WAAA;AAAA,MACzC,QAAA,EAAU,QAAS,CAAA,QAAA,IAAY,GAAI,CAAA,QAAA;AAAA,MACnC,IAAM,EAAA,KAAA,CAAM,IAAK,iBAAA,IAAI,GAAI,CAAA,CAAC,GAAG,QAAA,CAAS,IAAM,EAAA,GAAG,GAAI,CAAA,IAAI,CAAC,CAAC,CAAA;AAAA,MACzD,KAAA,EAAO,aACF,CAAA,cAAA,CAAA,EAAA,EAAA,QAAA,CAAS,KADP,CAAA,EAAA;AAAA,QAEL,WAAA,EAAA,CAAA,CAAa,cAAS,KAAT,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAgB,kBAAe,EAAI,GAAA,GAAA,CAAA,KAAA,KAAJ,IAAW,GAAA,MAAA,GAAA,EAAA,CAAA,WAAA,CAAA,IAAe,QAAS,CAAA,WAAA;AAAA,QAC/E,WAAS,EAAS,GAAA,QAAA,CAAA,KAAA,KAAT,mBAAgB,OAAW,MAAA,CAAA,EAAA,GAAA,GAAA,CAAI,UAAJ,IAAW,GAAA,MAAA,GAAA,EAAA,CAAA,OAAA;AAAA,OACjD;AAAA,KACD,CAAA,CAAA;AAAA;AAEH,EAAA,OAAO,KAAM,CAAA,IAAA,CAAK,MAAO,CAAA,MAAA,EAAQ,CAAE,CAAA,IAAA,CAAK,CAAC,CAAA,EAAG,MAAM,CAAE,CAAA,WAAA,CAAY,aAAc,CAAA,CAAA,CAAE,WAAW,CAAC,CAAA;AAC9F;AAcA,SAAS,YAAY,IAA4B,EAAA;AAC/C,EAAA,MAAM,MAAO,IAET,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAA,kBAAA;AACJ,EAAA,IAAI,KAAM,CAAA,OAAA,CAAQ,GAAG,CAAA,EAAU,OAAA,GAAA;AAC/B,EAAI,IAAA,GAAA,IAAO,OAAO,GAAQ,KAAA,QAAA,IAAY,MAAM,OAAS,CAAA,GAAA,CAElD,OAAO,CAAG,EAAA;AACX,IAAA,OAAQ,GAEL,CAAA,OAAA;AAAA;AAEL,EAAA,OAAO,EAAC;AACV;AACO,SAAS,iBAAkB,CAAA,GAAA,EAAsB,QAAwC,EAAA,MAAA,EAA2B,KAA4C,EAAA;AACrK,EAAM,MAAA,CAAC,YAAY,aAAa,CAAA,GAAI,MAAM,QAA+C,CAAA,QAAA,GAAW,iBAAiB,aAAa,CAAA;AAClI,EAAA,MAAM,CAAC,OAAS,EAAA,UAAU,CAAI,GAAA,KAAA,CAAM,SAAS,KAAK,CAAA;AAClD,EAAA,MAAM,CAAC,OAAS,EAAA,UAAU,CAAI,GAAA,KAAA,CAAM,SAAS,IAAI,CAAA;AACjD,EAAA,MAAM,CAAC,KAAO,EAAA,QAAQ,CAAI,GAAA,KAAA,CAAM,SAAwB,IAAI,CAAA;AAC5D,EAAA,MAAM,CAAC,KAAO,EAAA,QAAQ,IAAI,KAAM,CAAA,QAAA,CAA2B,EAAE,CAAA;AAC7D,EAAA,MAAM,CAAC,QAAU,EAAA,WAAW,IAAI,KAAM,CAAA,QAAA,CAA2B,EAAE,CAAA;AACnE,EAAA,MAAM,CAAC,SAAW,EAAA,YAAY,IAAI,KAAM,CAAA,QAAA,CAA2B,EAAE,CAAA;AACrE,EAAM,MAAA,OAAA,GAAU,KAAM,CAAA,MAAA,CAAO,IAAI,CAAA;AACjC,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAA,OAAA,CAAQ,OAAU,GAAA,IAAA;AAClB,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,OAAU,GAAA,KAAA;AAAA,KACpB;AAAA,GACF,EAAG,EAAE,CAAA;AACL,EAAM,MAAA,SAAA,GAAY,KAAM,CAAA,WAAA,CAAY,YAAY;AAvIlD,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAwII,IAAI,IAAA;AACF,MAAA,IAAI,OAA0B,EAAC;AAC/B,MAAA,IAAI,+BAAO,WAAa,EAAA;AACtB,QAAM,MAAA,OAAA,GAAU,MAAM,KAAA,CAAM,WAAY,EAAA;AACxC,QAAA,IAAA,GAAO,MAAO,CAAA,MAAA,CAAO,OAAO,CAAA,CAAE,IAAI,CAAE,CAAA,KAAA;AA5I5C,UAAAA,IAAAA,GAAAA;AA4IgD,UAAA,OAAA;AAAA,YACtC,WAAW,CAAE,CAAA,SAAA;AAAA,YACb,QAAQ,CAAE,CAAA,MAAA;AAAA,YACV,aAAa,CAAE,CAAA,WAAA;AAAA,YACf,SAAWA,EAAAA,CAAAA,GAAAA,GAAA,CAAE,CAAA,SAAA,KAAF,OAAAA,GAAe,GAAA;AAAA,WAC5B;AAAA,SAAE,CAAA;AAAA,OACG,MAAA;AACL,QAAA,MAAM,GAAM,GAAA,MAAM,GAAI,CAAA,KAAA,CAEnB,yBAAyB,CAAA;AAC5B,QAAI,IAAA,CAAA,EAAA,GAAA,GAAA,CAAI,MAAJ,KAAA,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAY,MAAQ,EAAA;AACtB,UAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,UAAA,QAAA,CAAS,GAAI,CAAA,MAAA,CAAO,CAAC,CAAA,CAAE,OAAO,CAAA;AAC9B,UAAA;AAAA;AAEF,QAAA,IAAA,GAAA,CAAO,EAAI,GAAA,CAAA,EAAA,GAAA,GAAA,CAAA,IAAA,KAAJ,IAAU,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,iBAAA,KAAV,YAA+B,EAAC;AAAA;AAEzC,MAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,MAAA,MAAM,WAAW,MAAQ,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,CAAA,MAAA;AACzB,MAAA,QAAA,CAAS,KAAK,MAAO,CAAA,CAAA,CAAA,KAAK,QAAQ,CAAE,CAAA,MAAM,KAAK,CAAE,CAAA,MAAA,KAAW,QAAY,IAAA,iBAAA,CAAkB,KAAK,CAAE,CAAA,MAAM,CAAC,CAAE,CAAA,GAAA,CAAI,eAAe,CAAC,CAAA;AAAA,aACvH,CAAG,EAAA;AACV,MAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,MAAA,QAAA,CAAS,CAAa,YAAA,KAAA,GAAQ,CAAE,CAAA,OAAA,GAAU,oCAAoC,CAAA;AAAA;AAChF,KACC,CAAC,GAAA,EAAK,KAAO,EAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAM,CAAC,CAAA;AAC/B,EAAM,MAAA,IAAA,GAAO,KAAM,CAAA,WAAA,CAAY,YAAY;AArK7C,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAsKI,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAA,MAAM,SAAU,EAAA;AAChB,MAAI,IAAA,OAAA,CAAQ,OAAS,EAAA,UAAA,CAAW,KAAK,CAAA;AACrC,MAAA;AAAA;AAEF,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAI,IAAA;AACF,MAAA,MAAM,SAAU,EAAA;AAEhB,MAAA,IAAI,SAAS,SAAW,EAAA;AACtB,QAAA,MAAM,YAAY,MAAM,QAAA,CAAS,WAAY,CAAA,KAAA,CAAM,MAAM,KAAK,CAAA;AAC9D,QAAI,IAAA,OAAA,CAAQ,OAAS,EAAA,UAAA,CAAW,SAAS,CAAA;AAAA;AAE3C,MAAM,MAAA,GAAA,GAAM,MAAM,QAAA,CAAS,IAAK,EAAA;AAKhC,MAAA,MAAM,UAAU,CAAC,QAAA,CAAS,eAAe,cAAe,CAAA,MAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,CAAQ,SAAS,CAAC,CAAA;AAC1E,MAAA,MAAM,aAAa,iBAAkB,CAAA,GAAA,EAAK,UAAS,EAAQ,GAAA,MAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,CAAA,SAAA,KAAR,YAAqB,IAAI,CAAA;AAC5E,MAAM,MAAA,OAAA,GAAU,qBAAqB,UAAU,CAAA;AAC/C,MAAI,IAAA,CAAC,QAAQ,MAAQ,EAAA;AACnB,QAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,QAAA,WAAA,CAAY,EAAE,CAAA;AACd,QAAA,YAAA,CAAa,UAAU,CAAA;AACvB,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA;AAAA;AAEF,MAAI,IAAA,cAAA;AACJ,MAAA,IAAI,+BAAO,aAAe,EAAA;AAExB,QAAI,IAAA;AACF,UAAA,MAAM,OAAU,GAAA,MAAM,KAAM,CAAA,aAAA,CAAc,OAAO,CAAA;AACjD,UAAA,cAAA,GAAiB,IAAI,GAAI,CAAA,MAAA,CAAO,OAAQ,CAAA,OAAO,EAAE,GAAI,CAAA,CAAC,CAAC,SAAA,EAAW,KAAK,CAAM,KAAA,CAAC,SAAW,EAAA,KAAK,CAAC,CAAC,CAAA;AAAA,iBACzF,GAAK,EAAA;AACZ,UAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,UAAA,QAAA,CAAS,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,0BAA0B,CAAA;AACxE,UAAA,WAAA,CAAY,EAAE,CAAA;AACd,UAAA,YAAA,CAAa,UAAU,CAAA;AACvB,UAAA,UAAA,CAAW,KAAK,CAAA;AAChB,UAAA;AAAA;AACF,OACK,MAAA;AAEL,QAAM,MAAA,MAAA,uBAAa,GAAoB,EAAA;AACvC,QAAA,KAAA,MAAW,KAAK,OAAS,EAAA,MAAA,CAAO,IAAI,MAAO,CAAA,CAAC,GAAG,CAAC,CAAA;AAChD,QAAA,MAAM,GAAM,GAAA,MAAM,GAAI,CAAA,KAAA,CAAM,qBAAuB,EAAA;AAAA,UACjD,WAAa,EAAA,KAAA,CAAM,IAAK,CAAA,MAAA,CAAO,MAAM;AAAA,SACtC,CAAA;AACD,QAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,QAAA,cAAA,uBAAqB,GAAiB,EAAA;AACtC,QAAA,KAAA,MAAW,CAAK,IAAA,WAAA,CAAY,GAAI,CAAA,IAAI,CAAG,EAAA;AACrC,UAAA,MAAM,UAAS,CAAG,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,CAAA,CAAA,SAAA,IAAY,OAAO,GAAI,CAAA,CAAA,CAAE,SAAS,CAAI,GAAA,KAAA,CAAA;AACxD,UAAA,IAAI,WAAU,CAAG,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,CAAA,CAAA,MAAA,CAAA,EAAuB,cAAA,CAAA,GAAA,CAAI,QAAQ,CAAC,CAAA;AAAA;AAEvD,QAAI,IAAA,CAAA,EAAA,GAAA,GAAA,CAAI,MAAJ,KAAA,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAY,MAAQ,EAAA;AACtB,UAAA,QAAA,CAAS,GAAI,CAAA,MAAA,CAAO,CAAC,CAAA,CAAE,OAAO,CAAA;AAAA;AAChC;AAEF,MAAA,MAAM,OAAyB,EAAC;AAChC,MAAA,MAAM,OAAyB,EAAC;AAChC,MAAA,KAAA,MAAW,KAAK,UAAY,EAAA;AAC1B,QAAM,MAAA,GAAA,GAAM,CAAE,CAAA,IAAA,CAAK,GAAI,CAAA,CAAA,CAAA,KAAK,cAAe,CAAA,GAAA,CAAI,CAAC,CAAC,CAAE,CAAA,IAAA,CAAK,OAAO,CAAA;AAC/D,QAAA,IAAI,2BAAK,MAAQ,EAAA;AACf,UAAK,IAAA,CAAA,IAAA,CAAK,iCACL,CADK,CAAA,EAAA;AAAA,YAER,KAAO,EAAA;AAAA,cACL,MAAA,EAAQ,MAAO,CAAA,GAAA,CAAI,MAAM,CAAA;AAAA;AAAA;AAAA;AAAA,cAIzB,aAAa,CAAE,CAAA,WAAA,IAAe,GAAI,CAAA,WAAA,IAAe,IAAI,QAAY,IAAA,SAAA;AAAA,cACjE,UAAS,EAAI,GAAA,CAAA,EAAA,GAAA,GAAA,CAAA,SAAA,KAAJ,IAAiB,GAAA,EAAA,GAAA,GAAA,CAAI,YAArB,IAAgC,GAAA,EAAA,GAAA;AAAA;AAC3C,WACD,CAAA,CAAA;AAAA,SACI,MAAA;AACL,UAAA,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA;AACb;AAEF,MAAK,IAAA,CAAA,IAAA,CAAK,CAAC,CAAG,EAAA,CAAA,KAAM,EAAE,WAAY,CAAA,aAAA,CAAc,CAAE,CAAA,WAAW,CAAC,CAAA;AAC9D,MAAK,IAAA,CAAA,IAAA,CAAK,CAAC,CAAG,EAAA,CAAA,KAAM,EAAE,WAAY,CAAA,aAAA,CAAc,CAAE,CAAA,WAAW,CAAC,CAAA;AAC9D,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,YAAA,CAAa,IAAI,CAAA;AAAA,aACV,CAAG,EAAA;AACV,MAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,MAAA,QAAA,CAAS,CAAa,YAAA,KAAA,GAAQ,CAAE,CAAA,OAAA,GAAU,+BAA+B,CAAA;AAAA,KACzE,SAAA;AACA,MAAI,IAAA,OAAA,CAAQ,OAAS,EAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AACvC,KACC,CAAC,QAAA,EAAU,KAAK,MAAQ,EAAA,KAAA,EAAO,SAAS,CAAC,CAAA;AAC5C,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAK,KAAA,SAAA,EAAY,CAAA,OAAA,CAAQ,MAAM;AAC7B,MAAI,IAAA,OAAA,CAAQ,OAAS,EAAA,UAAA,CAAW,KAAK,CAAA;AAAA,KACtC,CAAA;AAAA,GACH,EAAG,CAAC,SAAS,CAAC,CAAA;AAId,EAAA,KAAA,CAAM,UAAU,MAAM;AACpB,IAAA,IAAI,SAAY,GAAA,KAAA;AAChB,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAA,aAAA,CAAc,aAAa,CAAA;AAC3B,MAAA,OAAO,MAAM,MAAA;AAAA;AAEf,IAAA,KAAA,CAAM,YAAY;AAChB,MAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,aAAc,EAAA;AAC5C,MAAI,IAAA,SAAA,IAAa,CAAC,OAAA,CAAQ,OAAS,EAAA;AACnC,MAAA,aAAA,CAAc,MAAM,CAAA;AACpB,MAAI,IAAA,MAAA,KAAW,SAAW,EAAA,KAAK,IAAK,EAAA;AAAA,KACnC,GAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAY,SAAA,GAAA,IAAA;AAAA,KACd;AAAA,GACC,EAAA,CAAC,QAAU,EAAA,IAAI,CAAC,CAAA;AACnB,EAAM,MAAA,aAAA,GAAgB,KAAM,CAAA,WAAA,CAAY,YAAY;AAClD,IAAA,IAAI,CAAC,QAAU,EAAA;AACf,IAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,iBAAkB,EAAA;AAChD,IAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACtB,IAAA,aAAA,CAAc,MAAM,CAAA;AACpB,IAAI,IAAA,MAAA,KAAW,SAAW,EAAA,MAAM,IAAK,EAAA;AAAA,GACpC,EAAA,CAAC,QAAU,EAAA,IAAI,CAAC,CAAA;AAGnB,EAAA,MAAM,qBAAqB,KAAM,CAAA,OAAA,CAAQ,MAAM,CAAA,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,sBAAqB,YAAY;AAlS5F,IAAA,IAAA,EAAA;AAmSI,IAAA,OAAA,CAAM,cAAS,kBAAT,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,QAAA,CAAA,CAAA;AACN,IAAA,MAAM,IAAK,EAAA;AAAA,GACT,GAAA,MAAA,EAAW,CAAC,QAAA,EAAU,IAAI,CAAC,CAAA;AAC/B,EAAA,MAAM,QAAW,GAAA,KAAA,CAAM,OAAQ,CAAA,MAAM,cAAc,KAAO,EAAA,QAAA,EAAU,MAAQ,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAA,MAAM,GAAG,CAAC,KAAA,EAAO,QAAU,EAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAM,CAAC,CAAA;AACtH,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,aAAA;AAAA,IACA,OAAS,EAAA,IAAA;AAAA,IACT,YAAA,EAAc,YAAY,QAAU,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAA,YAAA,EAAA;AAAA,IACpC;AAAA,GACF;AACF"}
|
|
1
|
+
{"version":3,"file":"useContactMatches.js","sources":["../../../src/features/messaging/useContactMatches.ts"],"sourcesContent":["/**\n * The contact sync: read the address book, work out who is already here.\n *\n * The whole pipeline runs in the remote, so all of it is fixable by a surface\n * deploy: normalise -> hash -> ask the server -> split into \"on Yantra\" and\n * \"invite\". Only the address-book read itself is a host seam, because that is\n * the one part needing a native module.\n *\n * With phone-based membership (issue #1312), contact matching delegates to\n * the ChatPhoneSeam, which calls the `yantra-messenger` connector to match\n * E.164 phone numbers against chat members.\n *\n * NOTHING LEAVES THE DEVICE IN PLAINTEXT (legacy mode). Numbers are hashed\n * before the query. See sha256.ts and the server's chat-contact-directory.ts\n * for what that does and does not buy - it is not anonymity, but it does mean\n * we never upload or store the address book of someone who never signed up.\n *\n * If a ChatPhoneSeam is available, it takes precedence for matching.\n *\n * Like WhatsApp, the list is the address book only: contacts registered for Yantra Chat\n * open a conversation, everyone else is invited. There is no directory of all members.\n */\nimport * as React from 'react';\nimport { CONTACT_MATCHES_QUERY } from './chatOperations';\nimport { collectLookupNumbers, normalizeContacts, regionFromE164, type NormalizedContact } from './phoneNumbers';\nimport { sha256 } from './sha256';\nimport type { ChatContactsSeam, ChatGraphQLSeam, ChatPermissionStatus, ChatViewer, ChatPhoneSeam } from './chatHost';\n\nexport interface MatchedContact extends NormalizedContact {\n /** Present when this contact already has an account. */\n match?: { userId: string; displayName: string; picture?: string | null };\n}\n\nexport interface ContactMatchesState {\n permission: ChatPermissionStatus | 'unsupported';\n /** Granted for selected contacts only (iOS 18 Limited Access). */\n limited: boolean;\n loading: boolean;\n error: string | null;\n /** Contacts already on Yantra - tap to open a DM. */\n onYantra: MatchedContact[];\n /** Everyone else - tap to invite. */\n invitable: MatchedContact[];\n /** Prompts, then syncs on success. */\n requestAccess: () => Promise<void>;\n /** Re-read the address book and re-match. */\n refresh: () => Promise<void>;\n openSettings: () => Promise<void>;\n /** Present when the host can show the iOS contact access picker. */\n chooseMoreContacts?: () => Promise<void>;\n}\n\n/** One row per registered person, even when they are saved under several contacts. */\nfunction onePerUser(fromBook: MatchedContact[], viewerUserId?: string | null): MatchedContact[] {\n const byUser = new Map<string, MatchedContact>();\n for (const row of fromBook) {\n const uid = row.match?.userId;\n if (!uid || uid === viewerUserId || !/^[a-f0-9]{24}$/i.test(uid)) continue;\n const existing = byUser.get(uid);\n if (!existing) {\n byUser.set(uid, row);\n continue;\n }\n byUser.set(uid, {\n ...existing,\n displayName: existing.displayName || row.displayName,\n imageUri: existing.imageUri || row.imageUri,\n e164: Array.from(new Set([...existing.e164, ...row.e164])),\n match: {\n ...existing.match!,\n displayName: existing.match?.displayName || row.match?.displayName || existing.displayName,\n picture: existing.match?.picture || row.match?.picture,\n },\n });\n }\n return Array.from(byUser.values()).sort((a, b) => a.displayName.localeCompare(b.displayName));\n}\n\ninterface WireMatch {\n phoneHash?: string;\n userId?: string;\n displayName?: string | null;\n username?: string | null;\n picture?: string | null;\n}\n\n/**\n * The server returns AnyObject, so the payload arrives untyped and possibly as\n * a bare array or wrapped. Normalising defensively here beats trusting a shape\n * the schema does not enforce.\n */\nfunction readMatches(data: unknown): WireMatch[] {\n const raw = (data as { chatContactMatches?: unknown })?.chatContactMatches;\n if (Array.isArray(raw)) return raw as WireMatch[];\n if (raw && typeof raw === 'object' && Array.isArray((raw as { matches?: unknown }).matches)) {\n return (raw as { matches: WireMatch[] }).matches;\n }\n return [];\n}\n\nexport function useContactMatches(\n gql: ChatGraphQLSeam,\n contacts: ChatContactsSeam | undefined,\n viewer: ChatViewer | null,\n phone?: ChatPhoneSeam,\n): ContactMatchesState {\n const [permission, setPermission] = React.useState<ChatPermissionStatus | 'unsupported'>(\n contacts ? 'undetermined' : 'unsupported',\n );\n const [limited, setLimited] = React.useState(false);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n const [fromBook, setFromBook] = React.useState<MatchedContact[]>([]);\n const [invitable, setInvitable] = React.useState<MatchedContact[]>([]);\n const mounted = React.useRef(true);\n\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const sync = React.useCallback(async () => {\n if (!contacts) {\n if (mounted.current) setLoading(false);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n // Re-checked on every sync: the user can widen or narrow access in Settings at any time.\n if (contacts.isLimited) {\n const isLimited = await contacts.isLimited().catch(() => false);\n if (mounted.current) setLimited(isLimited);\n }\n const raw = await contacts.read();\n // Normalisation drops unmatchable numbers and the viewer's own\n // card, which almost everyone has saved.\n // Numbers saved without a country code are read against the device region AND the\n // viewer's verified country: the device may report US for a user whose contacts are Indian.\n const regions = [contacts.defaultRegion, regionFromE164(viewer?.phoneE164)];\n const normalized = normalizeContacts(raw, regions, viewer?.phoneE164 ?? null);\n const numbers = collectLookupNumbers(normalized);\n\n if (!numbers.length) {\n if (!mounted.current) return;\n setFromBook([]);\n setInvitable(normalized);\n setLoading(false);\n return;\n }\n\n let matchedNumbers: Map<string, any>;\n\n if (phone?.matchContacts) {\n // New phone-based membership: use connector to match by E.164 phone number\n try {\n const matches = await phone.matchContacts(numbers);\n matchedNumbers = new Map(Object.entries(matches).map(([phoneE164, match]) => [phoneE164, match]));\n } catch (err) {\n if (!mounted.current) return;\n setError(err instanceof Error ? err.message : 'Could not match contacts');\n setFromBook([]);\n setInvitable(normalized);\n setLoading(false);\n return;\n }\n } else {\n // Legacy hashed directory approach\n const byHash = new Map<string, string>();\n for (const n of numbers) byHash.set(sha256(n), n);\n\n const res = await gql.query(CONTACT_MATCHES_QUERY, { phoneHashes: Array.from(byHash.keys()) });\n if (!mounted.current) return;\n\n matchedNumbers = new Map<string, any>();\n for (const m of readMatches(res.data)) {\n const number = m?.phoneHash ? byHash.get(m.phoneHash) : undefined;\n if (number && m?.userId) matchedNumbers.set(number, m);\n }\n\n if (res.errors?.length) {\n setError(res.errors[0].message);\n }\n }\n\n const here: MatchedContact[] = [];\n const rest: MatchedContact[] = [];\n for (const c of normalized) {\n const hit = c.e164.map((n) => matchedNumbers.get(n)).find(Boolean);\n if (hit?.userId) {\n here.push({\n ...c,\n match: {\n userId: String(hit.userId),\n // Prefer the name from THEIR account over the one in\n // your address book only when yours is missing -\n // you called them \"Mum\" for a reason.\n displayName: c.displayName || hit.displayName || hit.username || 'Unknown',\n picture: hit.avatarUrl ?? hit.picture ?? null,\n },\n });\n } else {\n rest.push(c);\n }\n }\n\n here.sort((a, b) => a.displayName.localeCompare(b.displayName));\n rest.sort((a, b) => a.displayName.localeCompare(b.displayName));\n setFromBook(here);\n setInvitable(rest);\n } catch (e) {\n if (!mounted.current) return;\n setError(e instanceof Error ? e.message : 'Could not read your contacts.');\n } finally {\n if (mounted.current) setLoading(false);\n }\n }, [contacts, gql, viewer, phone]);\n\n // Check the CURRENT status without prompting, so the screen can offer the\n // right action - ask, or deep-link to Settings after a previous denial.\n React.useEffect(() => {\n let cancelled = false;\n if (!contacts) {\n setPermission('unsupported');\n return () => undefined;\n }\n void (async () => {\n const status = await contacts.getPermission();\n if (cancelled || !mounted.current) return;\n setPermission(status);\n if (status === 'granted') void sync();\n else setLoading(false);\n })();\n return () => {\n cancelled = true;\n };\n }, [contacts, sync]);\n\n const requestAccess = React.useCallback(async () => {\n if (!contacts) return;\n const status = await contacts.requestPermission();\n if (!mounted.current) return;\n setPermission(status);\n if (status === 'granted') await sync();\n else setLoading(false);\n }, [contacts, sync]);\n\n // Reopens the address book afterwards: the user may have just shared more people.\n const chooseMoreContacts = React.useMemo(\n () =>\n contacts?.chooseMoreContacts\n ? async () => {\n await contacts.chooseMoreContacts?.();\n await sync();\n }\n : undefined,\n [contacts, sync],\n );\n\n const onYantra = React.useMemo(() => onePerUser(fromBook, viewer?.userId), [fromBook, viewer?.userId]);\n\n return {\n permission,\n limited,\n loading,\n error,\n onYantra,\n invitable,\n requestAccess,\n refresh: sync,\n openSettings: async () => contacts?.openSettings(),\n chooseMoreContacts,\n };\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminide-stack/yantra-mobile",
|
|
3
|
-
"version": "12.0.69-alpha.
|
|
3
|
+
"version": "12.0.69-alpha.24",
|
|
4
4
|
"description": "Sample core for higher packages to depend on",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@messenger-box/platform-mobile": "*",
|
|
43
43
|
"@react-native-async-storage/async-storage": "*",
|
|
44
44
|
"@react-navigation/native": "*",
|
|
45
|
-
"common": "12.0.69-alpha.
|
|
45
|
+
"common": "12.0.69-alpha.22",
|
|
46
46
|
"expo-constants": "*",
|
|
47
47
|
"expo-secure-store": "*",
|
|
48
48
|
"expo-web-browser": "*",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"typescript": {
|
|
60
60
|
"definition": "lib/index.d.ts"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "869cb7a45acb4d23318ae5d19e6dd9eba06cca0a"
|
|
63
63
|
}
|