feat: Android Face Biometric Auth support

This commit is contained in:
Bryan Chauvin
2020-02-05 19:52:36 -06:00
parent 160d769f5a
commit f01323fd63
7 changed files with 307 additions and 230 deletions

View File

@@ -41,7 +41,7 @@ import StyleKit from '@Style/StyleKit';
if (__DEV__ === false) {
const bugsnag = new Client();
// Disable console.log for non-dev builds
/** Disable console.log for non-dev builds */
console.log = () => {};
}
@@ -80,9 +80,9 @@ const AppDrawerStack = createDrawerNavigator(
openRightDrawer: () => DrawerActions.openDrawer({ key: stateKey }),
closeRightDrawer: () => DrawerActions.closeDrawer({ key: stateKey }),
lockRightDrawer: lock => {
// this is the key part
/** This is the key part */
SideMenuManager.get().setLockedForRightSideMenu(lock);
// We have to return something
/** We have to return something. */
return NavigationActions.setParams({
params: { dummy: true },
key: route.key,
@@ -159,9 +159,9 @@ const DrawerStack = createDrawerNavigator(
openLeftDrawer: () => DrawerActions.openDrawer({ key: stateKey }),
closeLeftDrawer: () => DrawerActions.closeDrawer({ key: stateKey }),
lockLeftDrawer: lock => {
// this is the key part
/** This is the key part. */
SideMenuManager.get().setLockedForLeftSideMenu(lock);
// We have to return something
/** We have to return something. */
return NavigationActions.setParams({
params: { dummy: true },
key: route.key,

View File

@@ -17,9 +17,9 @@ export function isMatchCaseInsensitive(a, b) {
* Returns a Date object from a JSON stringifed date
*/
export function dateFromJsonString(str) {
if(str) {
if (str) {
return new Date(JSON.parse(str));
}
return str;
}
}

View File

@@ -87,6 +87,10 @@ export default class Authenticate extends Abstract {
}
submitPressed() {
if (this.activeSource && this.activeSource.isLocked()) {
return;
}
/**
* If we just pressed submit on the only pending source left, disable
* submit button
@@ -187,7 +191,7 @@ export default class Authenticate extends Abstract {
return;
}
// Disable submit while we're processing. Will be re-enabled below.
/** Disable submit while we're processing. Will be re-enabled below. */
this.setState({ submitDisabled: true });
const result = await source.authenticate();
@@ -197,6 +201,7 @@ export default class Authenticate extends Abstract {
this.forceUpdate();
} else if (source.isLocked()) {
this.onSourceLocked(source);
return;
} else {
if (result.error && result.error.message) {
Alert.alert('Unsuccessful', result.error.message);
@@ -229,14 +234,14 @@ export default class Authenticate extends Abstract {
/**
* @private
* When a source returns in a locked status we create a timeout for the lock
* period.
* period. This will auto reprompt the user for auth after the period is up.
*/
onSourceLocked(source) {
this.setState({ sourceLocked: true, submitDisabled: true });
setTimeout(() => {
source.setWaitingForInput();
this.setState({ sourceLocked: false, submitDisabled: false });
this.beginAuthenticationForSource(source);
this.forceUpdate();
}, source.lockTimeout);
}

View File

@@ -12,6 +12,7 @@ export default class AuthenticationSourceBiometric extends AuthenticationSource
super.initializeForInterface();
KeysManager.getDeviceBiometricsAvailability((available, type, noun) => {
this.isReady = true;
this.isAvailable = available;
this.biometricsType = type;
this.biometricsNoun = noun;
@@ -32,7 +33,7 @@ export default class AuthenticationSourceBiometric extends AuthenticationSource
}
get title() {
return this.biometricsType;
return this.biometricsType || 'Biometrics';
}
get isFingerprint() {
@@ -78,7 +79,7 @@ export default class AuthenticationSourceBiometric extends AuthenticationSource
}
async authenticate() {
if (!this.isAvailable) {
if (this.isReady && !this.isAvailable) {
this.didFail();
return {
success: false,
@@ -92,6 +93,7 @@ export default class AuthenticationSourceBiometric extends AuthenticationSource
if (Platform.OS === 'android') {
return FingerprintScanner.authenticate({
deviceCredentialAllowed: true,
description: 'Biometrics are required to access your notes.',
})
.then(() => {
@@ -102,6 +104,7 @@ export default class AuthenticationSourceBiometric extends AuthenticationSource
if (error.name === 'DeviceLocked') {
this.setLocked();
FingerprintScanner.release();
return this._fail(
'Authentication failed. Wait 30 seconds to try again.'
);

View File

@@ -1,8 +1,5 @@
import React, { Component } from 'react';
import {
ScrollView,
Alert
} from 'react-native';
import React from 'react';
import { ScrollView, Alert } from 'react-native';
import { SafeAreaView } from 'react-navigation';
import SectionHeader from '@Components/SectionHeader';
import SectionedAccessoryTableCell from '@Components/SectionedAccessoryTableCell';
@@ -16,10 +13,7 @@ import SF from '@SFJS/sfjs';
import Storage from '@SFJS/storageManager';
import Sync from '@SFJS/syncManager';
import Abstract from '@Screens/Abstract';
import {
SCREEN_INPUT_MODAL,
SCREEN_MANAGE_PRIVILEGES
} from '@Screens/screens';
import { SCREEN_INPUT_MODAL, SCREEN_MANAGE_PRIVILEGES } from '@Screens/screens';
import AuthSection from '@Screens/Settings/Sections/AuthSection';
import CompanySection from '@Screens/Settings/Sections/CompanySection';
import EncryptionSection from '@Screens/Settings/Sections/EncryptionSection';
@@ -29,16 +23,21 @@ import { ICON_CHECKMARK } from '@Style/icons';
import StyleKit from '@Style/StyleKit';
export default class Settings extends Abstract {
static navigationOptions = ({ navigation, navigationOptions }) => {
const templateOptions = {
title: "Settings",
title: 'Settings',
leftButton: {
title: ApplicationState.isIOS ? "Done" : null,
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon(ICON_CHECKMARK),
}
}
return Abstract.getDefaultNavigationOptions({navigation, navigationOptions, templateOptions});
title: ApplicationState.isIOS ? 'Done' : null,
iconName: ApplicationState.isIOS
? null
: StyleKit.nameForIcon(ICON_CHECKMARK),
},
};
return Abstract.getDefaultNavigationOptions({
navigation,
navigationOptions,
templateOptions,
});
};
constructor(props) {
@@ -46,31 +45,33 @@ export default class Settings extends Abstract {
props.navigation.setParams({
leftButton: {
title: ApplicationState.isIOS ? "Done" : null,
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon(ICON_CHECKMARK),
title: ApplicationState.isIOS ? 'Done' : null,
iconName: ApplicationState.isIOS
? null
: StyleKit.nameForIcon(ICON_CHECKMARK),
onPress: () => {
this.dismiss();
}
}
})
},
},
});
if(__DEV__) {
if (__DEV__) {
props.navigation.setParams({
rightButton: {
title: "Destroy Data",
title: 'Destroy Data',
onPress: () => {
Storage.get().clear();
Auth.get().signout();
KeysManager.get().clearOfflineKeysAndData(true);
}
}
})
},
},
});
}
this.sortOptions = [
{key: "created_at", label: "Date Added"},
{key: "client_updated_at", label: "Date Modified"},
{key: "title", label: "Title"},
{ key: 'created_at', label: 'Date Added' },
{ key: 'client_updated_at', label: 'Date Modified' },
{ key: 'title', label: 'Title' },
];
this.options = ApplicationState.getOptions();
@@ -84,10 +85,14 @@ export default class Settings extends Abstract {
}
loadSecurityStatus() {
var hasPasscode = KeysManager.get().hasOfflinePasscode();
var hasBiometrics = KeysManager.get().hasBiometrics();
var encryptedStorage = KeysManager.get().isStorageEncryptionEnabled();
this.mergeState({hasPasscode: hasPasscode, hasBiometrics: hasBiometrics, storageEncryption: encryptedStorage})
const hasPasscode = KeysManager.get().hasOfflinePasscode();
const hasBiometrics = KeysManager.get().hasBiometrics();
const encryptedStorage = KeysManager.get().isStorageEncryptionEnabled();
this.mergeState({
hasPasscode: hasPasscode,
hasBiometrics: hasBiometrics,
storageEncryption: encryptedStorage,
});
}
componentWillUnmount() {
@@ -102,215 +107,278 @@ export default class Settings extends Abstract {
}
resaveOfflineData(callback, updateAfter = false) {
Sync.get().resaveOfflineData().then(() => {
if(updateAfter) {
this.forceUpdate();
}
callback && callback();
});
Sync.get()
.resaveOfflineData()
.then(() => {
if (updateAfter) {
this.forceUpdate();
}
callback && callback();
});
}
onSignOutPress = () => {
AlertManager.get().confirm({
title: "Sign Out?",
text: "Signing out will remove all data from this device, including notes and tags. Make sure your data is synced before proceeding.",
confirmButtonText: "Sign Out",
title: 'Sign Out?',
text: 'Signing out will remove all data from this device, including notes and tags. Make sure your data is synced before proceeding.',
confirmButtonText: 'Sign Out',
onConfirm: () => {
Auth.get().signout().then(() => {
this.forceUpdate();
})
}
})
}
Auth.get()
.signout()
.then(() => {
this.forceUpdate();
});
},
});
};
onStorageEncryptionEnable = () => {
AlertManager.get().confirm({
title: "Enable Storage Encryption?",
text: "Storage encryption improves your security by encrypting your data on your device. It may increase app start-up time.",
confirmButtonText: "Enable",
title: 'Enable Storage Encryption?',
text: 'Storage encryption improves your security by encrypting your data on your device. It may increase app start-up time.',
confirmButtonText: 'Enable',
onConfirm: () => {
this.mergeState({storageEncryptionLoading: true});
this.mergeState({ storageEncryptionLoading: true });
KeysManager.get().enableStorageEncryption();
this.resaveOfflineData(() => {
this.mergeState({storageEncryption: true, storageEncryptionLoading: false});
this.mergeState({
storageEncryption: true,
storageEncryptionLoading: false,
});
});
}
})
}
},
});
};
onStorageEncryptionDisable = () => {
AlertManager.get().confirm({
title: "Disable Storage Encryption?",
text: "Storage encryption improves your security by encrypting your data on your device. Disabling it can improve app start-up speed.",
confirmButtonText: "Disable",
title: 'Disable Storage Encryption?',
text: 'Storage encryption improves your security by encrypting your data on your device. Disabling it can improve app start-up speed.',
confirmButtonText: 'Disable',
onConfirm: () => {
this.mergeState({storageEncryptionLoading: true});
this.mergeState({ storageEncryptionLoading: true });
KeysManager.get().disableStorageEncryption();
this.resaveOfflineData(() => {
this.mergeState({storageEncryption: false, storageEncryptionLoading: false});
this.mergeState({
storageEncryption: false,
storageEncryptionLoading: false,
});
});
}
})
}
},
});
};
onPasscodeEnable = () => {
this.props.navigation.navigate(SCREEN_INPUT_MODAL, {
title: "Setup Passcode",
placeholder: "Enter a passcode",
confirmPlaceholder: "Confirm passcode",
title: 'Setup Passcode',
placeholder: 'Enter a passcode',
confirmPlaceholder: 'Confirm passcode',
secureTextEntry: true,
requireConfirm: true,
showKeyboardChooser: true,
onSubmit: async (value, keyboardType) => {
Storage.get().setItem("passcodeKeyboardType", keyboardType);
Storage.get().setItem('passcodeKeyboardType', keyboardType);
let identifier = await SF.get().crypto.generateUUID();
SF.get().crypto.generateInitialKeysAndAuthParamsForUser(identifier, value).then((results) => {
let keys = results.keys;
let authParams = results.authParams;
SF.get()
.crypto.generateInitialKeysAndAuthParamsForUser(identifier, value)
.then(results => {
let keys = results.keys;
let authParams = results.authParams;
// make sure it has valid items
if(_.keys(keys).length > 0) {
KeysManager.get().setOfflineAuthParams(authParams);
KeysManager.get().persistOfflineKeys(keys);
var encryptionSource = KeysManager.get().encryptionSource();
if(encryptionSource == "offline") {
this.resaveOfflineData(null, true);
// make sure it has valid items
if (_.keys(keys).length > 0) {
KeysManager.get().setOfflineAuthParams(authParams);
KeysManager.get().persistOfflineKeys(keys);
const encryptionSource = KeysManager.get().encryptionSource();
if (encryptionSource === 'offline') {
this.resaveOfflineData(null, true);
}
} else {
Alert.alert(
'Passcode Error',
'There was an error setting up your passcode. Please try again.'
);
}
} else {
this.mergeState({setupButtonText: SAVE_BUTTON_DEFAULT_TEXT, setupButtonEnabled: true});
Alert.alert("Passcode Error", "There was an error setting up your passcode. Please try again.");
}
});
}
});
},
});
}
};
onPasscodeDisable = () => {
this.handlePrivilegedAction(true, SFPrivilegesManager.ActionManagePasscode, () => {
var encryptionSource = KeysManager.get().encryptionSource();
var message;
if(encryptionSource == "account") {
message = "Are you sure you want to disable your local passcode? This will not affect your encryption status, as your data is currently being encrypted through your sync account keys.";
} else if(encryptionSource == "offline") {
message = "Are you sure you want to disable your local passcode? This will disable encryption on your data.";
}
AlertManager.get().confirm({
title: "Disable Passcode",
text: message,
confirmButtonText: "Disable Passcode",
onConfirm: async () => {
var result = await KeysManager.get().clearOfflineKeysAndData();
if(encryptionSource == "offline") {
// remove encryption from all items
this.resaveOfflineData(null, true);
}
this.mergeState({hasPasscode: false});
this.forceUpdate();
this.handlePrivilegedAction(
true,
SFPrivilegesManager.ActionManagePasscode,
() => {
const encryptionSource = KeysManager.get().encryptionSource();
let message;
if (encryptionSource === 'account') {
message = 'Are you sure you want to disable your local passcode? This will not affect your encryption status, as your data is currently being encrypted through your sync account keys.';
} else if (encryptionSource === 'offline') {
message = 'Are you sure you want to disable your local passcode? This will disable encryption on your data.';
}
})
})
}
AlertManager.get().confirm({
title: 'Disable Passcode',
text: message,
confirmButtonText: 'Disable Passcode',
onConfirm: async () => {
await KeysManager.get().clearOfflineKeysAndData();
if (encryptionSource === 'offline') {
// remove encryption from all items
this.resaveOfflineData(null, true);
}
this.mergeState({ hasPasscode: false });
this.forceUpdate();
},
});
}
);
};
onFingerprintEnable = () => {
KeysManager.get().enableBiometrics();
this.loadSecurityStatus();
}
};
onFingerprintDisable = () => {
this.handlePrivilegedAction(true, SFPrivilegesManager.ActionManagePasscode, () => {
KeysManager.get().disableBiometrics();
this.loadSecurityStatus();
});
}
this.handlePrivilegedAction(
true,
SFPrivilegesManager.ActionManagePasscode,
() => {
KeysManager.get().disableBiometrics();
this.loadSecurityStatus();
}
);
};
onSortChange = (key) => {
onSortChange = key => {
this.options.setSortBy(key);
this.forceUpdate();
}
};
onOptionSelect = (option) => {
this.options.setDisplayOptionKeyValue(option, !this.options.getDisplayOptionValue(option));
onOptionSelect = option => {
this.options.setDisplayOptionKeyValue(
option,
!this.options.getDisplayOptionValue(option)
);
this.forceUpdate();
}
};
toggleSortReverse = () => {
this.options.setSortReverse(!this.options.sortReverse);
this.forceUpdate();
}
};
openManagePrivs = () => {
this.handlePrivilegedAction(true, SFPrivilegesManager.ActionManagePrivileges, () => {
this.props.navigation.navigate(SCREEN_MANAGE_PRIVILEGES)
})
}
this.handlePrivilegedAction(
true,
SFPrivilegesManager.ActionManagePrivileges,
() => {
this.props.navigation.navigate(SCREEN_MANAGE_PRIVILEGES);
}
);
};
render() {
if(this.state.lockContent) {
return (<LockedView />);
if (this.state.lockContent) {
return <LockedView />;
}
let signedIn = !Auth.get().offline();
const signedIn = !Auth.get().offline();
return (
<SafeAreaView forceInset={{ top: 'never', bottom: 'never', left: 'always' }} style={[StyleKit.styles.container, StyleKit.styles.baseBackground]}>
<ScrollView style={{backgroundColor: StyleKit.variable("stylekitBackgroundColor")}} keyboardShouldPersistTaps={'always'} keyboardDismissMode={'interactive'}>
{!signedIn && !this.state.confirmRegistration &&
<SafeAreaView
forceInset={{ top: 'never', bottom: 'never', left: 'always' }}
style={[StyleKit.styles.container, StyleKit.styles.baseBackground]}
>
<ScrollView
style={{
backgroundColor: StyleKit.variables.stylekitBackgroundColor,
}}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'interactive'}
>
{!signedIn && !this.state.confirmRegistration && (
<AuthSection
title={"Account"}
onAuthSuccess = {() => {this.dismiss()}}
title={'Account'}
onAuthSuccess={() => {
this.dismiss();
}}
/>
}
)}
<OptionsSection
title={"Options"}
title={'Options'}
onSignOutPress={this.onSignOutPress}
onManagePrivileges={this.openManagePrivs}
/>
<TableSection>
<SectionHeader title={"Sort Notes By"} buttonText={this.options.sortReverse ? "Disable Reverse Sort" : "Enable Reverse Sort"} buttonAction={this.toggleSortReverse} />
<SectionHeader
title={'Sort Notes By'}
buttonText={
this.options.sortReverse
? 'Disable Reverse Sort'
: 'Enable Reverse Sort'
}
buttonAction={this.toggleSortReverse}
/>
{this.sortOptions.map((option, i) => {
return (
<SectionedAccessoryTableCell
onPress={() => {this.onSortChange(option.key)}}
onPress={() => {
this.onSortChange(option.key);
}}
text={option.label}
key={option.key}
first={i == 0}
last={i == this.sortOptions.length - 1}
selected={() => {return option.key == this.options.sortBy}}
selected={() => {
return option.key === this.options.sortBy;
}}
/>
)
);
})}
</TableSection>
<TableSection>
<SectionHeader title={"Note List Options"} />
<SectionHeader title={'Note List Options'} />
<SectionedAccessoryTableCell
onPress={() => {this.onOptionSelect('hidePreviews')}}
text={"Hide note previews"}
onPress={() => {
this.onOptionSelect('hidePreviews');
}}
text={'Hide note previews'}
first={true}
selected={() => {return this.options.hidePreviews}}
selected={() => {
return this.options.hidePreviews;
}}
/>
<SectionedAccessoryTableCell
onPress={() => {this.onOptionSelect('hideTags')}}
text={"Hide note tags"}
selected={() => {return this.options.hideTags}}
onPress={() => {
this.onOptionSelect('hideTags');
}}
text={'Hide note tags'}
selected={() => {
return this.options.hideTags;
}}
/>
<SectionedAccessoryTableCell
onPress={() => {this.onOptionSelect('hideDates')}}
text={"Hide note dates"}
onPress={() => {
this.onOptionSelect('hideDates');
}}
text={'Hide note dates'}
last={true}
selected={() => {return this.options.hideDates}}
selected={() => {
return this.options.hideDates;
}}
/>
</TableSection>
<PasscodeSection
@@ -324,13 +392,12 @@ export default class Settings extends Abstract {
onDisable={this.onPasscodeDisable}
onFingerprintEnable={this.onFingerprintEnable}
onFingerprintDisable={this.onFingerprintDisable}
title={"Security"}
title={'Security'}
/>
<EncryptionSection title={"Encryption Status"}/>
<CompanySection title={"Standard Notes"}/>
<EncryptionSection title={'Encryption Status'} />
<CompanySection title={'Standard Notes'} />
</ScrollView>
</SafeAreaView>
);

View File

@@ -1,19 +1,13 @@
import React, { Component, Fragment } from 'react';
import { ScrollView, View, Text, FlatList } from 'react-native';
import ApplicationState from "@Lib/ApplicationState"
/*
Because SideMenus (SideMenu and NoteSideMenu) are rendering by React Navigation as drawer components
on app startup, we can't give them params at will. We need a way for components like the Compose
screen to talk to the NoteSideMenu and give it the current note context. The only way seems to be
some shared singleton object, which is this.
This object will handle state for both side menus.
*/
/**
* Because SideMenus (SideMenu and NoteSideMenu) are rendering by React
* Navigation as drawer components on app startup, we can't give them params at
* will. We need a way for components like the Compose screen to talk to the
* NoteSideMenu and give it the current note context. The only way seems to be
* some shared singleton object, which is this.
*
* This object will handle state for both side menus.
*/
export default class SideMenuManager {
static instance = null;
static get() {
if (this.instance == null) {
@@ -24,35 +18,41 @@ export default class SideMenuManager {
}
setLeftSideMenuReference(ref) {
// The ref handler of the main component sometimes passes null, then passes the correct reference
if(!this.leftSideMenu) {
/**
* The ref handler of the main component sometimes passes null, then passes
* the correct reference
*/
if (!this.leftSideMenu) {
this.leftSideMenu = ref;
}
}
setRightSideMenuReference(ref) {
// The ref handler of the main component sometimes passes null, then passes the correct reference
if(!this.rightSideMenu) {
/**
* The ref handler of the main component sometimes passes null, then passes
* the correct reference
*/
if (!this.rightSideMenu) {
this.rightSideMenu = ref;
}
}
/*
@param handler.onEditorSelect
@param handler.onTagSelect
@param handler.getSelectedTags
*/
/**
* @param handler.onEditorSelect
* @param handler.onTagSelect
* @param handler.getSelectedTags
*/
setHandlerForLeftSideMenu(handler) {
this.leftSideMenuHandler = handler;
return handler;
}
/*
@param handler.onTagSelect
@param handler.getSelectedTags
@param handler.getCurrentNote
*/
/**
* @param handler.onTagSelect
* @param handler.getSelectedTags
* @param handler.getCurrentNote
*/
setHandlerForRightSideMenu(handler) {
this.rightSideMenuHandler = handler;
@@ -72,7 +72,7 @@ export default class SideMenuManager {
removeHandlerForRightSideMenu(handler) {
// In tablet switching mode, a new Compose window may be created before the first one unmounts.
// If an old instance asks us to remove handler, we want to make sure it's not the new one
if(handler == this.rightSideMenuHandler) {
if (handler === this.rightSideMenuHandler) {
this.rightSideMenuHandler = null;
}
}
@@ -92,5 +92,4 @@ export default class SideMenuManager {
isRightSideMenuLocked() {
return this.rightSideMenuLocked;
}
}

View File

@@ -1,31 +1,31 @@
const GenericVarPrefix = "--";
const StylekitPrefix = "--sn-stylekit";
const StylekitPrefixToBurn = "--sn-";
const PREFIX_GENERIC = '--';
const PREFIX_STANDARD_NOTES = '--sn-stylekit';
const PREFIX_STANDARD_NOTES_BURN = '--sn-';
export default class CSSParser {
/*
@param css: CSS file contents in string format
*/
/**
* @param css: CSS file contents in string format
*/
static cssToObject(css) {
let object = {};
let lines = css.split("\n");
const object = {};
const lines = css.split('\n');
for(var line of lines) {
for (let line of lines) {
line = line.trim();
if(line.startsWith(GenericVarPrefix)) {
// Remove initial "--"
if(line.startsWith(StylekitPrefix)) {
line = line.slice(StylekitPrefixToBurn.length, line.length);
if (line.startsWith(PREFIX_GENERIC)) {
// Remove initial '--'
if (line.startsWith(PREFIX_STANDARD_NOTES)) {
line = line.slice(PREFIX_STANDARD_NOTES_BURN.length, line.length);
} else {
// Not all vars start with --sn-stylekit. e.g --background-color
line = line.slice(GenericVarPrefix.length, line.length);
line = line.slice(PREFIX_GENERIC.length, line.length);
}
let parts = line.split(":");
const parts = line.split(':');
let key = parts[0].trim();
let value = parts[1].trim();;
let value = parts[1].trim();
key = this.hyphenatedStringToCamelCase(key);
value = value.replace(";", "").trim();
value = value.replace(';', '').trim();
object[key] = value;
}
@@ -37,18 +37,21 @@ export default class CSSParser {
}
static resolveVariablesThatReferenceOtherVariables(object, round = 0) {
for(const key of Object.keys(object)) {
let value = object[key];
let stripValue = "var(";
if(typeof value == "string" && value.startsWith(stripValue)) {
let from = stripValue.length;
let to = value.indexOf(")");
for (const key of Object.keys(object)) {
const value = object[key];
const stripValue = 'var(';
if (typeof value === 'string' && value.startsWith(stripValue)) {
const from = stripValue.length;
const to = value.indexOf(')');
let varName = value.slice(from, to);
if(varName.startsWith(StylekitPrefix)) {
varName = varName.slice(StylekitPrefixToBurn.length, varName.length);
if (varName.startsWith(PREFIX_STANDARD_NOTES)) {
varName = varName.slice(
PREFIX_STANDARD_NOTES_BURN.length,
varName.length
);
} else {
// Not all vars start with --sn-stylekit. e.g --background-color
varName = varName.slice(GenericVarPrefix.length, varName.length);
varName = varName.slice(PREFIX_GENERIC.length, varName.length);
}
varName = this.hyphenatedStringToCamelCase(varName);
object[key] = object[varName];
@@ -59,17 +62,17 @@ export default class CSSParser {
// the left hand counterparts. In the first round, variables on rhs mentioned before
// its value has been gotten to in the loop will be missed. The second round takes care of this
// and makes sure that all values will resolve.
if(round == 0) {
if (round === 0) {
this.resolveVariablesThatReferenceOtherVariables(object, ++round);
}
}
static hyphenatedStringToCamelCase(string) {
let comps = string.split("-");
let result = "";
for(var i = 0; i < comps.length; i++) {
const comps = string.split('-');
let result = '';
for (let i = 0; i < comps.length; i++) {
let part = comps[i];
if(i == 0) {
if (i === 0) {
result += part;
} else {
result += this.capitalizeFirstLetter(part);