Merge branch 'main' into webrtc

This commit is contained in:
Roland Osborne 2023-04-05 10:46:37 -07:00
commit 0efa818249
13 changed files with 157 additions and 79 deletions

View File

@ -571,7 +571,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
@ -643,7 +643,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;

View File

@ -10,6 +10,7 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.2",
"@react-native-clipboard/clipboard": "^1.11.1", "@react-native-clipboard/clipboard": "^1.11.1",
"@react-native-firebase/app": "^17.2.0", "@react-native-firebase/app": "^17.2.0",
"@react-native-firebase/messaging": "^17.2.0", "@react-native-firebase/messaging": "^17.2.0",

View File

@ -56,6 +56,7 @@ export function Admin() {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}> <View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}> <TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text> <Text style={styles.viewtermstext}>View Terms of Service</Text>
@ -70,8 +71,9 @@ export function Admin() {
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text> <Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)}
{ state.enabled && state.agree && ( { state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.reset} onPress={admin}> <TouchableOpacity style={styles.reset} onPress={admin}>
{ state.busy && ( { state.busy && (
<ActivityIndicator size="small" color="#ffffff" /> <ActivityIndicator size="small" color="#ffffff" />
@ -81,7 +83,7 @@ export function Admin() {
)} )}
</TouchableOpacity> </TouchableOpacity>
)} )}
{ (!state.enabled || !state.agree) && ( { (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.noreset}> <View style={styles.noreset}>
<Text style={styles.noresettext}>Access</Text> <Text style={styles.noresettext}>Access</Text>
</View> </View>

View File

@ -138,6 +138,7 @@ export function Create() {
</View> </View>
)} )}
{ Platform.OS !== 'ios' && (
<View style={styles.tos}> <View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}> <TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text> <Text style={styles.viewtermstext}>View Terms of Service</Text>
@ -152,9 +153,10 @@ export function Create() {
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text> <Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)}
<View style={styles.buttons}> <View style={styles.buttons}>
{ state.enabled && state.agree && ( { state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.create} onPress={create}> <TouchableOpacity style={styles.create} onPress={create}>
{ state.busy && ( { state.busy && (
<ActivityIndicator size="small" color="#ffffff" /> <ActivityIndicator size="small" color="#ffffff" />
@ -164,7 +166,7 @@ export function Create() {
)} )}
</TouchableOpacity> </TouchableOpacity>
)} )}
{ (!state.enabled || !state.agree) && ( { (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.nocreate}> <View style={styles.nocreate}>
<Text style={styles.nocreatetext}>Create Account</Text> <Text style={styles.nocreatetext}>Create Account</Text>
</View> </View>

View File

@ -1,4 +1,4 @@
import { KeyboardAvoidingView, ActivityIndicator, Modal, ScrollView, Alert, Text, TextInput, View, TouchableOpacity } from 'react-native'; import { Platform, KeyboardAvoidingView, ActivityIndicator, Modal, ScrollView, Alert, Text, TextInput, View, TouchableOpacity } from 'react-native';
import { styles } from './Login.styled'; import { styles } from './Login.styled';
import Ionicons from 'react-native-vector-icons/AntDesign'; import Ionicons from 'react-native-vector-icons/AntDesign';
import { useLogin } from './useLogin.hook'; import { useLogin } from './useLogin.hook';
@ -63,6 +63,7 @@ export function Login() {
</View> </View>
)} )}
{ Platform.OS !== 'ios' && (
<View style={styles.tos}> <View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}> <TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text> <Text style={styles.viewtermstext}>View Terms of Service</Text>
@ -77,8 +78,9 @@ export function Login() {
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text> <Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)}
{ state.enabled && state.agree && ( { state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.login} onPress={login}> <TouchableOpacity style={styles.login} onPress={login}>
{ state.busy && ( { state.busy && (
<ActivityIndicator size="small" color="#ffffff" /> <ActivityIndicator size="small" color="#ffffff" />
@ -88,7 +90,7 @@ export function Login() {
)} )}
</TouchableOpacity> </TouchableOpacity>
)} )}
{ (!state.enabled || !state.agree) && ( { (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.nologin}> <View style={styles.nologin}>
<Text style={styles.nologintext}>Login</Text> <Text style={styles.nologintext}>Login</Text>
</View> </View>

View File

@ -48,6 +48,7 @@ export function Reset() {
<View style={styles.space} /> <View style={styles.space} />
</View> </View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}> <View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}> <TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text> <Text style={styles.viewtermstext}>View Terms of Service</Text>
@ -62,8 +63,9 @@ export function Reset() {
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text> <Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)}
{ state.enabled && state.agree && ( { state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.reset} onPress={reset}> <TouchableOpacity style={styles.reset} onPress={reset}>
{ state.busy && ( { state.busy && (
<ActivityIndicator size="small" color="#ffffff" /> <ActivityIndicator size="small" color="#ffffff" />
@ -73,7 +75,7 @@ export function Reset() {
)} )}
</TouchableOpacity> </TouchableOpacity>
)} )}
{ (!state.enabled || !state.agree) && ( { (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.noreset}> <View style={styles.noreset}>
<Text style={styles.noresettext}>Access</Text> <Text style={styles.noresettext}>Access</Text>
</View> </View>

View File

@ -131,8 +131,8 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block
return ( return (
<View style={{ ...styles.wrapper, transform: [{rotate: '180deg'}]}}> <View style={{ ...styles.wrapper, transform: [{rotate: '180deg'}]}}>
<TouchableOpacity activeOpacity={1} style={styles.item} onPress={focus}> <View style={styles.item}>
<View style={styles.header}> <TouchableOpacity activeOpacity={1} onPress={focus} style={styles.header}>
{ state.logo !== 'avatar' && state.logo && ( { state.logo !== 'avatar' && state.logo && (
<Image source={{ uri: state.logo }} style={{ width: 28, height: 28, borderRadius: 6 }} /> <Image source={{ uri: state.logo }} style={{ width: 28, height: 28, borderRadius: 6 }} />
)} )}
@ -141,7 +141,7 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block
)} )}
<Text style={{ ...styles.name, color: state.nameSet ? Colors.text : Colors.grey }}>{ state.name }</Text> <Text style={{ ...styles.name, color: state.nameSet ? Colors.text : Colors.grey }}>{ state.name }</Text>
<Text style={styles.timestamp}>{ state.timestamp }</Text> <Text style={styles.timestamp}>{ state.timestamp }</Text>
</View> </TouchableOpacity>
{ state.status === 'confirmed' && ( { state.status === 'confirmed' && (
<> <>
{ state.transform === 'complete' && state.assets && ( { state.transform === 'complete' && state.assets && (
@ -162,8 +162,8 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block
<MatIcons name="weather-cloudy-alert" size={32} color={Colors.alert} /> <MatIcons name="weather-cloudy-alert" size={32} color={Colors.alert} />
</View> </View>
)} )}
{ state.message && !state.sealed && ( { state.clickable && !state.sealed && (
<Text style={{ ...styles.message, fontSize: state.fontSize, color: state.fontColor }}>{ state.message }</Text> <Text style={{ ...styles.message, fontSize: state.fontSize, color: state.fontColor }}>{ state.clickable }</Text>
)} )}
{ state.sealed && ( { state.sealed && (
<Text style={ styles.sealed }>sealed message</Text> <Text style={ styles.sealed }>sealed message</Text>
@ -175,7 +175,7 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block
<MatIcons name="cloud-refresh" size={32} color={Colors.divider} /> <MatIcons name="cloud-refresh" size={32} color={Colors.divider} />
</View> </View>
)} )}
</TouchableOpacity> </View>
{ focused && ( { focused && (
<View style={styles.focused}> <View style={styles.focused}>
{ state.editable && ( { state.editable && (

View File

@ -1,13 +1,15 @@
import { useState, useEffect, useContext } from 'react'; import { useState, useEffect, useContext } from 'react';
import { Linking } from 'react-native';
import { ConversationContext } from 'context/ConversationContext'; import { ConversationContext } from 'context/ConversationContext';
import { CardContext } from 'context/CardContext'; import { CardContext } from 'context/CardContext';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
import { AccountContext } from 'context/AccountContext'; import { AccountContext } from 'context/AccountContext';
import moment from 'moment'; import moment from 'moment';
import { useWindowDimensions } from 'react-native'; import { useWindowDimensions, Text } from 'react-native';
import Colors from 'constants/Colors'; import Colors from 'constants/Colors';
import { getCardByGuid } from 'context/cardUtil'; import { getCardByGuid } from 'context/cardUtil';
import { decryptTopicSubject } from 'context/sealUtil'; import { decryptTopicSubject } from 'context/sealUtil';
import { sanitizeUrl } from '@braintree/sanitize-url';
export function useTopicItem(item, hosting, remove, contentKey) { export function useTopicItem(item, hosting, remove, contentKey) {
@ -18,6 +20,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
logo: null, logo: null,
timestamp: null, timestamp: null,
message: null, message: null,
clickable: null,
carousel: false, carousel: false,
carouselIndex: 0, carouselIndex: 0,
width: null, width: null,
@ -90,12 +93,13 @@ export function useTopicItem(item, hosting, remove, contentKey) {
} }
} }
let parsed, sealed, message, assets, fontSize, fontColor; let parsed, sealed, message, clickable, assets, fontSize, fontColor;
if (dataType === 'superbasictopic') { if (dataType === 'superbasictopic') {
try { try {
sealed = false; sealed = false;
parsed = JSON.parse(data); parsed = JSON.parse(data);
message = parsed.text; message = parsed?.text;
clickable = clickableText(parsed.text);
assets = parsed.assets; assets = parsed.assets;
if (parsed.textSize === 'small') { if (parsed.textSize === 'small') {
fontSize = 10; fontSize = 10;
@ -139,6 +143,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
sealed = false; sealed = false;
parsed = unsealed.message; parsed = unsealed.message;
message = parsed?.text; message = parsed?.text;
clickable = clickableText(parsed?.text);
if (parsed?.textSize === 'small') { if (parsed?.textSize === 'small') {
fontSize = 10; fontSize = 10;
} }
@ -177,7 +182,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
const editable = guid === identity?.guid && parsed; const editable = guid === identity?.guid && parsed;
const deletable = editable || hosting; const deletable = editable || hosting;
updateState({ logo, name, nameSet, known, sealed, message, fontSize, fontColor, timestamp, transform, status, assets, deletable, editable, editData: parsed, editMessage: message, editType: dataType }); updateState({ logo, name, nameSet, known, sealed, message, clickable, fontSize, fontColor, timestamp, transform, status, assets, deletable, editable, editData: parsed, editMessage: message, editType: dataType });
}, [conversation.state, card.state, account.state, item, contentKey]); }, [conversation.state, card.state, account.state, item, contentKey]);
const unsealTopic = async (topicId, revision, topicDetail) => { const unsealTopic = async (topicId, revision, topicDetail) => {
@ -194,6 +199,31 @@ export function useTopicItem(item, hosting, remove, contentKey) {
} }
}; };
const clickableText = (text) => {
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
let clickable = [];
let group = '';
const words = text == null ? [''] : text.split(' ');
words.forEach((word, index) => {
if (!!pattern.test(word)) {
clickable.push(<Text key={index}>{ group }</Text>);
group = '';
clickable.push(<Text key={'link-' + index} onPress={() => Linking.openURL(sanitizeUrl(word))} style={{ fontStyle: 'italic' }}>{ sanitizeUrl(word) + ' ' }</Text>);
}
else {
group += `${word} `;
}
})
clickable.push(<Text key={words.length}>{ group }</Text>);
return <Text>{ clickable }</Text>;
};
const actions = { const actions = {
showCarousel: (index) => { showCarousel: (index) => {
updateState({ carousel: true, carouselIndex: index }); updateState({ carousel: true, carouselIndex: index });

View File

@ -1077,6 +1077,11 @@
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@braintree/sanitize-url@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
"@egjs/hammerjs@^2.0.17": "@egjs/hammerjs@^2.0.17":
version "2.0.17" version "2.0.17"
resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz" resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz"

View File

@ -22,6 +22,7 @@
"axios": "^0.27.2", "axios": "^0.27.2",
"base-64": "^1.0.0", "base-64": "^1.0.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dompurify": "^3.0.1",
"jsencrypt": "^2.3.1", "jsencrypt": "^2.3.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-color": "^2.19.3", "react-color": "^2.19.3",

View File

@ -123,7 +123,7 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
)} )}
{ !sealed && !state.editing && ( { !sealed && !state.editing && (
<div class="message"> <div class="message">
<div style={{ color: topic.textColor, fontSize: topic.textSize }}>{ topic.text }</div> <div style={{ color: topic.textColor, fontSize: topic.textSize }}>{ topic.clickable }</div>
</div> </div>
)} )}
{ state.editing && ( { state.editing && (

View File

@ -9,6 +9,7 @@ import { ProfileContext } from 'context/ProfileContext';
import { isUnsealed, getChannelSeals, getContentKey, encryptTopicSubject } from 'context/sealUtil'; import { isUnsealed, getChannelSeals, getContentKey, encryptTopicSubject } from 'context/sealUtil';
import { decryptTopicSubject } from 'context/sealUtil'; import { decryptTopicSubject } from 'context/sealUtil';
import { getProfileByGuid } from 'context/cardUtil'; import { getProfileByGuid } from 'context/cardUtil';
import * as DOMPurify from 'dompurify';
export function useConversation(cardId, channelId) { export function useConversation(cardId, channelId) {
@ -132,6 +133,31 @@ export function useConversation(cardId, channelId) {
// eslint-disable-next-line // eslint-disable-next-line
}, [state.contentKey]); }, [state.contentKey]);
const clickableText = (text) => {
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
let group = '';
let clickable = [];
const words = text == null ? '' : DOMPurify.sanitize(text).split(' ');
words.forEach((word, index) => {
if (!!pattern.test(word)) {
clickable.push(<span key={index}>{ group }</span>);
group = '';
clickable.push(<a key={'link-'+index} target="_blank" rel="noopener noreferrer" href={word}>{ `${word} ` }</a>);
}
else {
group += `${word} `;
}
})
clickable.push(<span key={words.length}>{ group }</span>);
return <p>{ clickable }</p>;
};
const syncTopic = (item, value) => { const syncTopic = (item, value) => {
const revision = value.data?.detailRevision; const revision = value.data?.detailRevision;
const detail = value.data?.topicDetail || {}; const detail = value.data?.topicDetail || {};
@ -190,6 +216,7 @@ export function useConversation(cardId, channelId) {
const message = JSON.parse(detail.data); const message = JSON.parse(detail.data);
item.assets = message.assets; item.assets = message.assets;
item.text = message.text; item.text = message.text;
item.clickable = clickableText(message.text);
item.textColor = message.textColor ? message.textColor : '#444444'; item.textColor = message.textColor ? message.textColor : '#444444';
item.textSize = message.textSize ? message.textSize : 14; item.textSize = message.textSize ? message.textSize : 14;
} }
@ -197,6 +224,7 @@ export function useConversation(cardId, channelId) {
const subject = decryptTopicSubject(detail.data, state.contentKey); const subject = decryptTopicSubject(detail.data, state.contentKey);
item.assets = subject.message.assets; item.assets = subject.message.assets;
item.text = subject.message.text; item.text = subject.message.text;
item.clickable = clickableText(subject.message.text);
item.textColor = subject.message.textColor ? subject.message.textColor : '#444444'; item.textColor = subject.message.textColor ? subject.message.textColor : '#444444';
item.textSize = subject.message.textSize ? subject.message.textSize : 14; item.textSize = subject.message.textSize ? subject.message.textSize : 14;
} }

View File

@ -4366,6 +4366,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
dompurify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.1.tgz#a0933f38931b3238934dd632043b727e53004289"
integrity sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==
domutils@^1.7.0: domutils@^1.7.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz" resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"