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 = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@ -643,7 +643,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;

View File

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

View File

@ -56,22 +56,24 @@ export function Admin() {
</TouchableOpacity>
</View>
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
)}
{ state.enabled && state.agree && (
{ state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.reset} onPress={admin}>
{ state.busy && (
<ActivityIndicator size="small" color="#ffffff" />
@ -81,7 +83,7 @@ export function Admin() {
)}
</TouchableOpacity>
)}
{ (!state.enabled || !state.agree) && (
{ (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.noreset}>
<Text style={styles.noresettext}>Access</Text>
</View>

View File

@ -138,23 +138,25 @@ export function Create() {
</View>
)}
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
)}
<View style={styles.buttons}>
{ state.enabled && state.agree && (
{ state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.create} onPress={create}>
{ state.busy && (
<ActivityIndicator size="small" color="#ffffff" />
@ -164,7 +166,7 @@ export function Create() {
)}
</TouchableOpacity>
)}
{ (!state.enabled || !state.agree) && (
{ (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.nocreate}>
<Text style={styles.nocreatetext}>Create Account</Text>
</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 Ionicons from 'react-native-vector-icons/AntDesign';
import { useLogin } from './useLogin.hook';
@ -63,22 +63,24 @@ export function Login() {
</View>
)}
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
)}
{ state.enabled && state.agree && (
{ state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.login} onPress={login}>
{ state.busy && (
<ActivityIndicator size="small" color="#ffffff" />
@ -88,7 +90,7 @@ export function Login() {
)}
</TouchableOpacity>
)}
{ (!state.enabled || !state.agree) && (
{ (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.nologin}>
<Text style={styles.nologintext}>Login</Text>
</View>

View File

@ -47,23 +47,25 @@ export function Reset() {
autoCapitalize="none" placeholder="token" placeholderTextColor={Colors.grey} />
<View style={styles.space} />
</View>
{ Platform.OS !== 'ios' && (
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
)}
<View style={styles.tos}>
<TouchableOpacity style={styles.viewterms} onPress={actions.showTerms}>
<Text style={styles.viewtermstext}>View Terms of Service</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.agreeterms} onPress={() => actions.agree(!state.agree)}>
{ state.agree && (
<MatIcons name={'checkbox-outline'} size={20} color={Colors.primary} />
)}
{ !state.agree && (
<MatIcons name={'checkbox-blank-outline'} size={20} color={Colors.primary} />
)}
<Text style={styles.agreetermstext}>I agree to Terms of Service</Text>
</TouchableOpacity>
</View>
{ state.enabled && state.agree && (
{ state.enabled && (Platform.OS === 'ios' || state.agree) && (
<TouchableOpacity style={styles.reset} onPress={reset}>
{ state.busy && (
<ActivityIndicator size="small" color="#ffffff" />
@ -73,7 +75,7 @@ export function Reset() {
)}
</TouchableOpacity>
)}
{ (!state.enabled || !state.agree) && (
{ (!state.enabled || (Platform.OS !== 'ios' && !state.agree)) && (
<View style={styles.noreset}>
<Text style={styles.noresettext}>Access</Text>
</View>

View File

@ -131,8 +131,8 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block
return (
<View style={{ ...styles.wrapper, transform: [{rotate: '180deg'}]}}>
<TouchableOpacity activeOpacity={1} style={styles.item} onPress={focus}>
<View style={styles.header}>
<View style={styles.item}>
<TouchableOpacity activeOpacity={1} onPress={focus} style={styles.header}>
{ state.logo !== 'avatar' && state.logo && (
<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.timestamp}>{ state.timestamp }</Text>
</View>
</TouchableOpacity>
{ state.status === 'confirmed' && (
<>
{ 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} />
</View>
)}
{ state.message && !state.sealed && (
<Text style={{ ...styles.message, fontSize: state.fontSize, color: state.fontColor }}>{ state.message }</Text>
{ state.clickable && !state.sealed && (
<Text style={{ ...styles.message, fontSize: state.fontSize, color: state.fontColor }}>{ state.clickable }</Text>
)}
{ state.sealed && (
<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} />
</View>
)}
</TouchableOpacity>
</View>
{ focused && (
<View style={styles.focused}>
{ state.editable && (

View File

@ -1,13 +1,15 @@
import { useState, useEffect, useContext } from 'react';
import { Linking } from 'react-native';
import { ConversationContext } from 'context/ConversationContext';
import { CardContext } from 'context/CardContext';
import { ProfileContext } from 'context/ProfileContext';
import { AccountContext } from 'context/AccountContext';
import moment from 'moment';
import { useWindowDimensions } from 'react-native';
import { useWindowDimensions, Text } from 'react-native';
import Colors from 'constants/Colors';
import { getCardByGuid } from 'context/cardUtil';
import { decryptTopicSubject } from 'context/sealUtil';
import { sanitizeUrl } from '@braintree/sanitize-url';
export function useTopicItem(item, hosting, remove, contentKey) {
@ -18,6 +20,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
logo: null,
timestamp: null,
message: null,
clickable: null,
carousel: false,
carouselIndex: 0,
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') {
try {
sealed = false;
parsed = JSON.parse(data);
message = parsed.text;
message = parsed?.text;
clickable = clickableText(parsed.text);
assets = parsed.assets;
if (parsed.textSize === 'small') {
fontSize = 10;
@ -139,6 +143,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
sealed = false;
parsed = unsealed.message;
message = parsed?.text;
clickable = clickableText(parsed?.text);
if (parsed?.textSize === 'small') {
fontSize = 10;
}
@ -177,7 +182,7 @@ export function useTopicItem(item, hosting, remove, contentKey) {
const editable = guid === identity?.guid && parsed;
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]);
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 = {
showCarousel: (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"
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":
version "2.0.17"
resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz"

View File

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

View File

@ -123,7 +123,7 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
)}
{ !sealed && !state.editing && (
<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>
)}
{ state.editing && (

View File

@ -9,6 +9,7 @@ import { ProfileContext } from 'context/ProfileContext';
import { isUnsealed, getChannelSeals, getContentKey, encryptTopicSubject } from 'context/sealUtil';
import { decryptTopicSubject } from 'context/sealUtil';
import { getProfileByGuid } from 'context/cardUtil';
import * as DOMPurify from 'dompurify';
export function useConversation(cardId, channelId) {
@ -132,6 +133,31 @@ export function useConversation(cardId, channelId) {
// eslint-disable-next-line
}, [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 revision = value.data?.detailRevision;
const detail = value.data?.topicDetail || {};
@ -190,6 +216,7 @@ export function useConversation(cardId, channelId) {
const message = JSON.parse(detail.data);
item.assets = message.assets;
item.text = message.text;
item.clickable = clickableText(message.text);
item.textColor = message.textColor ? message.textColor : '#444444';
item.textSize = message.textSize ? message.textSize : 14;
}
@ -197,6 +224,7 @@ export function useConversation(cardId, channelId) {
const subject = decryptTopicSubject(detail.data, state.contentKey);
item.assets = subject.message.assets;
item.text = subject.message.text;
item.clickable = clickableText(subject.message.text);
item.textColor = subject.message.textColor ? subject.message.textColor : '#444444';
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:
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:
version "1.7.0"
resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"