@oxy.so/services 9.2.0 → 11.0.0
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/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +6 -6
- package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionModule.kt +106 -41
- package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionProvider.kt +45 -32
- package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionStore.kt +22 -27
- package/android/src/main/java/so/oxy/identity/OxyIdentityModule.kt +89 -43
- package/android/src/main/java/so/oxy/security/OxyCallerPolicy.kt +97 -0
- package/android/src/main/java/so/oxy/session/OxyBackgroundSessionStore.kt +4 -10
- package/android/src/main/java/so/oxy/storage/OxyEncryptedPrefs.kt +17 -18
- package/ios/OxyIdentityModule.swift +13 -14
- package/lib/commonjs/ui/boot/runProviderColdBoot.js +2 -2
- package/lib/commonjs/ui/components/BenefitList.js +1 -1
- package/lib/commonjs/ui/components/BenefitList.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +2 -2
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/FollowTargetButton.js +2 -2
- package/lib/commonjs/ui/components/FollowTargetButton.js.map +1 -1
- package/lib/commonjs/ui/components/PeableButton.js +1 -2
- package/lib/commonjs/ui/components/PeableButton.js.map +1 -1
- package/lib/commonjs/ui/components/SurfaceHeaderAction.js +1 -1
- package/lib/commonjs/ui/components/SurfaceHeaderAction.js.map +1 -1
- package/lib/commonjs/ui/components/SurfaceScreen.js +1 -1
- package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/primitives.js +1 -1
- package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js +1 -1
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -1
- package/lib/commonjs/ui/components/payment/PaymentDetailsStep.js +2 -2
- package/lib/commonjs/ui/components/payment/PaymentDetailsStep.js.map +1 -1
- package/lib/commonjs/ui/components/payment/PaymentMethodStep.js +2 -2
- package/lib/commonjs/ui/components/payment/PaymentMethodStep.js.map +1 -1
- package/lib/commonjs/ui/components/payment/PaymentReviewStep.js +2 -2
- package/lib/commonjs/ui/components/payment/PaymentReviewStep.js.map +1 -1
- package/lib/commonjs/ui/components/payment/PaymentSuccessStep.js +1 -1
- package/lib/commonjs/ui/components/payment/PaymentSuccessStep.js.map +1 -1
- package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js +2 -2
- package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyAuthScreen.js +1 -1
- package/lib/commonjs/ui/components/signIn/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyLinkCommonsPanel.js +1 -1
- package/lib/commonjs/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyPasswordPanel.js +2 -2
- package/lib/commonjs/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
- package/lib/commonjs/ui/components/surfaces/ActionSheetSurface.js +2 -1
- package/lib/commonjs/ui/components/surfaces/ActionSheetSurface.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountMembersScreen.js +4 -4
- package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js +1 -1
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +1 -1
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/DomainsScreen.js +1 -1
- package/lib/commonjs/ui/screens/DomainsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +4 -2
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +5 -5
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/HistoryViewScreen.js +1 -1
- package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +1 -1
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/NotificationsScreen.js +8 -8
- package/lib/commonjs/ui/screens/NotificationsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PreferencesScreen.js +2 -2
- package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +37 -37
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SearchSettingsScreen.js +5 -5
- package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/UserListScreen.js +1 -1
- package/lib/commonjs/ui/screens/UserListScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +11 -11
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/UploadBar.js +1 -1
- package/lib/commonjs/ui/screens/fileManagement/UploadBar.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +1 -1
- package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js +1 -1
- package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -1
- package/lib/commonjs/ui/session/tokenLossRecovery.js +1 -1
- package/lib/module/ui/boot/runProviderColdBoot.js +2 -2
- package/lib/module/ui/components/BenefitList.js +1 -1
- package/lib/module/ui/components/BenefitList.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +2 -2
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/FollowTargetButton.js +2 -2
- package/lib/module/ui/components/FollowTargetButton.js.map +1 -1
- package/lib/module/ui/components/PeableButton.js +2 -3
- package/lib/module/ui/components/PeableButton.js.map +1 -1
- package/lib/module/ui/components/SurfaceHeaderAction.js +1 -1
- package/lib/module/ui/components/SurfaceHeaderAction.js.map +1 -1
- package/lib/module/ui/components/SurfaceScreen.js +1 -1
- package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
- package/lib/module/ui/components/authChooser/primitives.js +1 -1
- package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
- package/lib/module/ui/components/authChooser/requestSurfaces.js +1 -1
- package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -1
- package/lib/module/ui/components/payment/PaymentDetailsStep.js +2 -2
- package/lib/module/ui/components/payment/PaymentDetailsStep.js.map +1 -1
- package/lib/module/ui/components/payment/PaymentMethodStep.js +2 -2
- package/lib/module/ui/components/payment/PaymentMethodStep.js.map +1 -1
- package/lib/module/ui/components/payment/PaymentReviewStep.js +2 -2
- package/lib/module/ui/components/payment/PaymentReviewStep.js.map +1 -1
- package/lib/module/ui/components/payment/PaymentSuccessStep.js +1 -1
- package/lib/module/ui/components/payment/PaymentSuccessStep.js.map +1 -1
- package/lib/module/ui/components/payment/PaymentSummaryStep.js +2 -2
- package/lib/module/ui/components/payment/PaymentSummaryStep.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyAuthScreen.js +1 -1
- package/lib/module/ui/components/signIn/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyLinkCommonsPanel.js +1 -1
- package/lib/module/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyPasswordPanel.js +2 -2
- package/lib/module/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
- package/lib/module/ui/components/surfaces/ActionSheetSurface.js +2 -1
- package/lib/module/ui/components/surfaces/ActionSheetSurface.js.map +1 -1
- package/lib/module/ui/screens/AccountMembersScreen.js +4 -4
- package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountVerificationScreen.js +1 -1
- package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -1
- package/lib/module/ui/screens/ConnectedAppsScreen.js +1 -1
- package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/module/ui/screens/DomainsScreen.js +1 -1
- package/lib/module/ui/screens/DomainsScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +4 -2
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +5 -5
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/HistoryViewScreen.js +1 -1
- package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/LegalDocumentsScreen.js +1 -1
- package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -1
- package/lib/module/ui/screens/NotificationsScreen.js +8 -8
- package/lib/module/ui/screens/NotificationsScreen.js.map +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/module/ui/screens/PreferencesScreen.js +2 -2
- package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +37 -37
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +1 -1
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SearchSettingsScreen.js +5 -5
- package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/UserListScreen.js +1 -1
- package/lib/module/ui/screens/UserListScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +11 -11
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/UploadBar.js +1 -1
- package/lib/module/ui/screens/fileManagement/UploadBar.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustCenterScreen.js +1 -1
- package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustRulesScreen.js +1 -1
- package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -1
- package/lib/module/ui/session/tokenLossRecovery.js +1 -1
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/BenefitList.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowTargetButton.d.ts +1 -1
- package/lib/typescript/commonjs/ui/components/FollowTargetButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/PeableButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +2 -2
- package/lib/typescript/module/ui/components/BenefitList.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowTargetButton.d.ts +1 -1
- package/lib/typescript/module/ui/components/FollowTargetButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/PeableButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/package.json +7 -7
- package/plugins/withOxySharedPermissions.js +89 -0
- package/plugins/withSharedDeviceSessionProvider.js +26 -71
- package/src/ui/boot/runProviderColdBoot.ts +2 -2
- package/src/ui/components/BenefitList.tsx +1 -2
- package/src/ui/components/FollowButton.tsx +3 -3
- package/src/ui/components/FollowTargetButton.tsx +3 -3
- package/src/ui/components/PeableButton.tsx +3 -4
- package/src/ui/components/SurfaceHeaderAction.tsx +1 -1
- package/src/ui/components/SurfaceScreen.tsx +1 -1
- package/src/ui/components/__tests__/followButtonAccessibility.test.tsx +9 -0
- package/src/ui/components/authChooser/AccountsMenuView.tsx +1 -1
- package/src/ui/components/authChooser/primitives.tsx +1 -1
- package/src/ui/components/authChooser/requestSurfaces.tsx +1 -1
- package/src/ui/components/payment/PaymentDetailsStep.tsx +2 -2
- package/src/ui/components/payment/PaymentMethodStep.tsx +2 -2
- package/src/ui/components/payment/PaymentReviewStep.tsx +2 -2
- package/src/ui/components/payment/PaymentSuccessStep.tsx +1 -1
- package/src/ui/components/payment/PaymentSummaryStep.tsx +2 -2
- package/src/ui/components/signIn/OxyAuthScreen.tsx +1 -1
- package/src/ui/components/signIn/OxyLinkCommonsPanel.tsx +1 -1
- package/src/ui/components/signIn/OxyPasswordPanel.tsx +1 -1
- package/src/ui/components/surfaces/ActionSheetSurface.tsx +2 -1
- package/src/ui/screens/AccountMembersScreen.tsx +4 -4
- package/src/ui/screens/AccountVerificationScreen.tsx +1 -1
- package/src/ui/screens/ConnectedAppsScreen.tsx +1 -1
- package/src/ui/screens/DomainsScreen.tsx +1 -1
- package/src/ui/screens/EditProfileFieldScreen.tsx +1 -2
- package/src/ui/screens/FileManagementScreen.tsx +5 -5
- package/src/ui/screens/HistoryViewScreen.tsx +1 -1
- package/src/ui/screens/LanguageSelectorScreen.tsx +1 -1
- package/src/ui/screens/LegalDocumentsScreen.tsx +1 -1
- package/src/ui/screens/NotificationsScreen.tsx +8 -8
- package/src/ui/screens/PaymentGatewayScreen.tsx +1 -1
- package/src/ui/screens/PreferencesScreen.tsx +2 -2
- package/src/ui/screens/PrivacySettingsScreen.tsx +21 -21
- package/src/ui/screens/ProfileScreen.tsx +1 -1
- package/src/ui/screens/SearchSettingsScreen.tsx +5 -5
- package/src/ui/screens/UserListScreen.tsx +1 -1
- package/src/ui/screens/WelcomeNewUserScreen.tsx +11 -11
- package/src/ui/screens/fileManagement/UploadBar.tsx +1 -1
- package/src/ui/screens/trust/TrustCenterScreen.tsx +1 -1
- package/src/ui/screens/trust/TrustRulesScreen.tsx +1 -1
- package/src/ui/session/__tests__/tokenLossRecovery.test.ts +2 -2
- package/src/ui/session/tokenLossRecovery.ts +1 -1
- package/android/src/main/java/so/oxy/identity/OxyIdentityProvider.kt +0 -77
- package/android/src/main/java/so/oxy/identity/OxyIdentityStore.kt +0 -66
- package/plugins/withSharedDeviceSessionReader.js +0 -57
- package/plugins/withSharedIdentityProvider.js +0 -83
- package/plugins/withSharedIdentityReader.js +0 -46
|
@@ -109,7 +109,7 @@ const HistoryViewScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) => {
|
|
|
109
109
|
/>
|
|
110
110
|
</SettingsListGroup>
|
|
111
111
|
<SettingsListGroup title={t('history.recent') || 'Recent History'}>
|
|
112
|
-
{isLoading ? <Loading size="
|
|
112
|
+
{isLoading ? <Loading size="lg" color={bloomTheme.colors.text} text={t('history.loading') || 'Loading history...'} />
|
|
113
113
|
: history.length === 0 ? <Text className="text-text-secondary text-center p-space-40">{t('history.empty') || 'No history yet'}</Text>
|
|
114
114
|
: history.map(item => (
|
|
115
115
|
<SettingsListItem
|
|
@@ -197,7 +197,7 @@ const LanguageSelectorScreen: React.FC<LanguageSelectorScreenProps> = () => {
|
|
|
197
197
|
</Text>
|
|
198
198
|
</View>
|
|
199
199
|
) : canRemove ? (
|
|
200
|
-
<Button appearance="plain" tone="neutral" size="
|
|
200
|
+
<Button appearance="plain" tone="neutral" size="sm" iconOnly stopPropagation
|
|
201
201
|
onPress={() => handleRemove(entry.code)} disabled={isBusy}
|
|
202
202
|
accessibilityLabel={`${t('language.remove')} ${entry.name}`}
|
|
203
203
|
icon={<Ionicons name="close-circle" size={REMOVE_ICON_SIZE} color={bloomTheme.colors.textSecondary} />} />
|
|
@@ -90,8 +90,8 @@ const NotificationsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
90
90
|
}
|
|
91
91
|
rightElement={
|
|
92
92
|
<Switch
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
checked={values.pushEnabled}
|
|
94
|
+
onCheckedChange={() => toggle('pushEnabled')}
|
|
95
95
|
disabled={isSaving}
|
|
96
96
|
/>
|
|
97
97
|
}
|
|
@@ -111,8 +111,8 @@ const NotificationsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
111
111
|
}
|
|
112
112
|
rightElement={
|
|
113
113
|
<Switch
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
checked={values.emailDigest}
|
|
115
|
+
onCheckedChange={() => toggle('emailDigest')}
|
|
116
116
|
disabled={isSaving}
|
|
117
117
|
/>
|
|
118
118
|
}
|
|
@@ -139,8 +139,8 @@ const NotificationsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
139
139
|
}
|
|
140
140
|
rightElement={
|
|
141
141
|
<Switch
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
checked={values.securityAlerts}
|
|
143
|
+
onCheckedChange={() => toggle('securityAlerts')}
|
|
144
144
|
disabled={isSaving}
|
|
145
145
|
/>
|
|
146
146
|
}
|
|
@@ -168,8 +168,8 @@ const NotificationsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
168
168
|
}
|
|
169
169
|
rightElement={
|
|
170
170
|
<Switch
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
checked={values.marketingEmails}
|
|
172
|
+
onCheckedChange={() => toggle('marketingEmails')}
|
|
173
173
|
disabled={isSaving}
|
|
174
174
|
/>
|
|
175
175
|
}
|
|
@@ -197,7 +197,7 @@ const PaymentGatewayScreen: React.FC<PaymentGatewayScreenProps> = (props) => {
|
|
|
197
197
|
<Button
|
|
198
198
|
appearance="solid" tone="accent"
|
|
199
199
|
onPress={handleClose}
|
|
200
|
-
size="
|
|
200
|
+
size="sm"
|
|
201
201
|
icon={<Ionicons name="close" size={16} />}
|
|
202
202
|
iconPosition="right"
|
|
203
203
|
>
|
|
@@ -158,8 +158,8 @@ const PreferencesScreen: React.FC<BaseScreenProps> = ({
|
|
|
158
158
|
}
|
|
159
159
|
rightElement={
|
|
160
160
|
<Switch
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
checked={reduceMotionPref}
|
|
162
|
+
onCheckedChange={handleReduceMotionToggle}
|
|
163
163
|
disabled={isSaving}
|
|
164
164
|
/>
|
|
165
165
|
}
|
|
@@ -192,7 +192,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
192
192
|
if (isLoading) {
|
|
193
193
|
return (
|
|
194
194
|
<>
|
|
195
|
-
<Loading size="
|
|
195
|
+
<Loading size="lg" color={bloomTheme.colors.text} />
|
|
196
196
|
</>
|
|
197
197
|
);
|
|
198
198
|
}
|
|
@@ -207,28 +207,28 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
207
207
|
icon={<SettingsIcon name="lock-outline" color={bloomTheme.colors.primary} />}
|
|
208
208
|
title={t('privacySettings.isPrivateAccount') || 'Private Account'}
|
|
209
209
|
description={t('privacySettings.isPrivateAccountDesc') || 'Only approved followers can see your posts'}
|
|
210
|
-
rightElement={<Switch
|
|
210
|
+
rightElement={<Switch checked={settings.isPrivateAccount} onCheckedChange={() => toggle('isPrivateAccount')} disabled={isSaving} />}
|
|
211
211
|
showChevron={false}
|
|
212
212
|
/>
|
|
213
213
|
<SettingsListItem
|
|
214
214
|
icon={<SettingsIcon name="account-eye-outline" color={bloomTheme.colors.info} />}
|
|
215
215
|
title={t('privacySettings.profileVisibility') || 'Profile Visibility'}
|
|
216
216
|
description={t('privacySettings.profileVisibilityDesc') || 'Control who can view your profile'}
|
|
217
|
-
rightElement={<Switch
|
|
217
|
+
rightElement={<Switch checked={settings.profileVisibility} onCheckedChange={() => toggle('profileVisibility')} disabled={isSaving} />}
|
|
218
218
|
showChevron={false}
|
|
219
219
|
/>
|
|
220
220
|
<SettingsListItem
|
|
221
221
|
icon={<SettingsIcon name="circle-outline" color={bloomTheme.colors.success} />}
|
|
222
222
|
title={t('privacySettings.hideOnlineStatus') || 'Hide Online Status'}
|
|
223
223
|
description={t('privacySettings.hideOnlineStatusDesc') || 'Don\'t show when you\'re online'}
|
|
224
|
-
rightElement={<Switch
|
|
224
|
+
rightElement={<Switch checked={settings.hideOnlineStatus} onCheckedChange={() => toggle('hideOnlineStatus')} disabled={isSaving} />}
|
|
225
225
|
showChevron={false}
|
|
226
226
|
/>
|
|
227
227
|
<SettingsListItem
|
|
228
228
|
icon={<SettingsIcon name="clock-outline" color={bloomTheme.colors.secondary} />}
|
|
229
229
|
title={t('privacySettings.hideLastSeen') || 'Hide Last Seen'}
|
|
230
230
|
description={t('privacySettings.hideLastSeenDesc') || 'Don\'t show when you were last active'}
|
|
231
|
-
rightElement={<Switch
|
|
231
|
+
rightElement={<Switch checked={settings.hideLastSeen} onCheckedChange={() => toggle('hideLastSeen')} disabled={isSaving} />}
|
|
232
232
|
showChevron={false}
|
|
233
233
|
/>
|
|
234
234
|
</SettingsListGroup>
|
|
@@ -239,28 +239,28 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
239
239
|
icon={<SettingsIcon name="tag-outline" color={bloomTheme.colors.primary} />}
|
|
240
240
|
title={t('privacySettings.allowTagging') || 'Allow Tagging'}
|
|
241
241
|
description={t('privacySettings.allowTaggingDesc') || 'Let others tag you in posts'}
|
|
242
|
-
rightElement={<Switch
|
|
242
|
+
rightElement={<Switch checked={settings.allowTagging} onCheckedChange={() => toggle('allowTagging')} disabled={isSaving} />}
|
|
243
243
|
showChevron={false}
|
|
244
244
|
/>
|
|
245
245
|
<SettingsListItem
|
|
246
246
|
icon={<SettingsIcon name="at" color={bloomTheme.colors.info} />}
|
|
247
247
|
title={t('privacySettings.allowMentions') || 'Allow Mentions'}
|
|
248
248
|
description={t('privacySettings.allowMentionsDesc') || 'Let others mention you'}
|
|
249
|
-
rightElement={<Switch
|
|
249
|
+
rightElement={<Switch checked={settings.allowMentions} onCheckedChange={() => toggle('allowMentions')} disabled={isSaving} />}
|
|
250
250
|
showChevron={false}
|
|
251
251
|
/>
|
|
252
252
|
<SettingsListItem
|
|
253
253
|
icon={<SettingsIcon name="message-outline" color={bloomTheme.colors.success} />}
|
|
254
254
|
title={t('privacySettings.allowDirectMessages') || 'Allow Direct Messages'}
|
|
255
255
|
description={t('privacySettings.allowDirectMessagesDesc') || 'Let others send you direct messages'}
|
|
256
|
-
rightElement={<Switch
|
|
256
|
+
rightElement={<Switch checked={settings.allowDirectMessages} onCheckedChange={() => toggle('allowDirectMessages')} disabled={isSaving} />}
|
|
257
257
|
showChevron={false}
|
|
258
258
|
/>
|
|
259
259
|
<SettingsListItem
|
|
260
260
|
icon={<SettingsIcon name="check-all" color={bloomTheme.colors.secondary} />}
|
|
261
261
|
title={t('privacySettings.hideReadReceipts') || 'Hide Read Receipts'}
|
|
262
262
|
description={t('privacySettings.hideReadReceiptsDesc') || 'Don\'t show read receipts in messages'}
|
|
263
|
-
rightElement={<Switch
|
|
263
|
+
rightElement={<Switch checked={settings.hideReadReceipts} onCheckedChange={() => toggle('hideReadReceipts')} disabled={isSaving} />}
|
|
264
264
|
showChevron={false}
|
|
265
265
|
/>
|
|
266
266
|
</SettingsListGroup>
|
|
@@ -271,28 +271,28 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
271
271
|
icon={<SettingsIcon name="pulse" color={bloomTheme.colors.primary} />}
|
|
272
272
|
title={t('privacySettings.showActivity') || 'Show Activity Status'}
|
|
273
273
|
description={t('privacySettings.showActivityDesc') || 'Display your activity on your profile'}
|
|
274
|
-
rightElement={<Switch
|
|
274
|
+
rightElement={<Switch checked={settings.showActivity} onCheckedChange={() => toggle('showActivity')} disabled={isSaving} />}
|
|
275
275
|
showChevron={false}
|
|
276
276
|
/>
|
|
277
277
|
<SettingsListItem
|
|
278
278
|
icon={<SettingsIcon name="database-outline" color={bloomTheme.colors.info} />}
|
|
279
279
|
title={t('privacySettings.dataSharing') || 'Data Sharing'}
|
|
280
280
|
description={t('privacySettings.dataSharingDesc') || 'Allow sharing data for personalization'}
|
|
281
|
-
rightElement={<Switch
|
|
281
|
+
rightElement={<Switch checked={settings.dataSharing} onCheckedChange={() => toggle('dataSharing')} disabled={isSaving} />}
|
|
282
282
|
showChevron={false}
|
|
283
283
|
/>
|
|
284
284
|
<SettingsListItem
|
|
285
285
|
icon={<SettingsIcon name="map-marker-outline" color={bloomTheme.colors.warning} />}
|
|
286
286
|
title={t('privacySettings.locationSharing') || 'Location Sharing'}
|
|
287
287
|
description={t('privacySettings.locationSharingDesc') || 'Share your location'}
|
|
288
|
-
rightElement={<Switch
|
|
288
|
+
rightElement={<Switch checked={settings.locationSharing} onCheckedChange={() => toggle('locationSharing')} disabled={isSaving} />}
|
|
289
289
|
showChevron={false}
|
|
290
290
|
/>
|
|
291
291
|
<SettingsListItem
|
|
292
292
|
icon={<SettingsIcon name="chart-line" color={bloomTheme.colors.secondary} />}
|
|
293
293
|
title={t('privacySettings.analyticsSharing') || 'Analytics Sharing'}
|
|
294
294
|
description={t('privacySettings.analyticsSharingDesc') || 'Allow analytics data collection'}
|
|
295
|
-
rightElement={<Switch
|
|
295
|
+
rightElement={<Switch checked={settings.analyticsSharing} onCheckedChange={() => toggle('analyticsSharing')} disabled={isSaving} />}
|
|
296
296
|
showChevron={false}
|
|
297
297
|
/>
|
|
298
298
|
</SettingsListGroup>
|
|
@@ -303,28 +303,28 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
303
303
|
icon={<SettingsIcon name="eye-off-outline" color={bloomTheme.colors.warning} />}
|
|
304
304
|
title={t('privacySettings.sensitiveContent') || 'Show Sensitive Content'}
|
|
305
305
|
description={t('privacySettings.sensitiveContentDesc') || 'Allow sensitive or explicit content'}
|
|
306
|
-
rightElement={<Switch
|
|
306
|
+
rightElement={<Switch checked={settings.sensitiveContent} onCheckedChange={() => toggle('sensitiveContent')} disabled={isSaving} />}
|
|
307
307
|
showChevron={false}
|
|
308
308
|
/>
|
|
309
309
|
<SettingsListItem
|
|
310
310
|
icon={<SettingsIcon name="filter-outline" color={bloomTheme.colors.success} />}
|
|
311
311
|
title={t('privacySettings.autoFilter') || 'Auto Filter'}
|
|
312
312
|
description={t('privacySettings.autoFilterDesc') || 'Automatically filter inappropriate content'}
|
|
313
|
-
rightElement={<Switch
|
|
313
|
+
rightElement={<Switch checked={settings.autoFilter} onCheckedChange={() => toggle('autoFilter')} disabled={isSaving} />}
|
|
314
314
|
showChevron={false}
|
|
315
315
|
/>
|
|
316
316
|
<SettingsListItem
|
|
317
317
|
icon={<SettingsIcon name="volume-off" color={bloomTheme.colors.info} />}
|
|
318
318
|
title={t('privacySettings.muteKeywords') || 'Mute Keywords'}
|
|
319
319
|
description={t('privacySettings.muteKeywordsDesc') || 'Hide posts containing muted keywords'}
|
|
320
|
-
rightElement={<Switch
|
|
320
|
+
rightElement={<Switch checked={settings.muteKeywords} onCheckedChange={() => toggle('muteKeywords')} disabled={isSaving} />}
|
|
321
321
|
showChevron={false}
|
|
322
322
|
/>
|
|
323
323
|
<SettingsListItem
|
|
324
324
|
icon={<SettingsIcon name="cellphone-screenshot" color={bloomTheme.colors.secondary} />}
|
|
325
325
|
title={t('privacySettings.blockScreenshots') || 'Block Screenshots'}
|
|
326
326
|
description={t('privacySettings.blockScreenshotsDesc') || 'Prevent screenshots of your content'}
|
|
327
|
-
rightElement={<Switch
|
|
327
|
+
rightElement={<Switch checked={settings.blockScreenshots} onCheckedChange={() => toggle('blockScreenshots')} disabled={isSaving} />}
|
|
328
328
|
showChevron={false}
|
|
329
329
|
/>
|
|
330
330
|
</SettingsListGroup>
|
|
@@ -332,7 +332,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
332
332
|
{/* Blocked Users */}
|
|
333
333
|
<SettingsListGroup title={t('privacySettings.sections.blockedUsers') || 'BLOCKED USERS'}>
|
|
334
334
|
{isLoadingUsers ? (
|
|
335
|
-
<Loading size="
|
|
335
|
+
<Loading size="sm" color={bloomTheme.colors.text} />
|
|
336
336
|
) : blockedUsers.length === 0 ? (
|
|
337
337
|
<Text className="text-text-secondary text-center p-space-40">
|
|
338
338
|
{t('privacySettings.noBlockedUsers') || 'No blocked users'}
|
|
@@ -349,7 +349,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
349
349
|
rightElement={
|
|
350
350
|
<Button
|
|
351
351
|
appearance="solid" tone="danger"
|
|
352
|
-
size="
|
|
352
|
+
size="sm"
|
|
353
353
|
onPress={() => handleUnblock(userId)}
|
|
354
354
|
>
|
|
355
355
|
{t('privacySettings.unblock') || 'Unblock'}
|
|
@@ -365,7 +365,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
365
365
|
{/* Restricted Users */}
|
|
366
366
|
<SettingsListGroup title={t('privacySettings.sections.restrictedUsers') || 'RESTRICTED USERS'}>
|
|
367
367
|
{isLoadingUsers ? (
|
|
368
|
-
<Loading size="
|
|
368
|
+
<Loading size="sm" color={bloomTheme.colors.text} />
|
|
369
369
|
) : restrictedUsers.length === 0 ? (
|
|
370
370
|
<Text className="text-text-secondary text-center p-space-40">
|
|
371
371
|
{t('privacySettings.noRestrictedUsers') || 'No restricted users'}
|
|
@@ -383,7 +383,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
383
383
|
rightElement={
|
|
384
384
|
<Button
|
|
385
385
|
appearance="subtle" tone="neutral"
|
|
386
|
-
size="
|
|
386
|
+
size="sm"
|
|
387
387
|
onPress={() => handleUnrestrict(userId)}
|
|
388
388
|
>
|
|
389
389
|
{t('privacySettings.unrestrict') || 'Unrestrict'}
|
|
@@ -210,7 +210,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
210
210
|
{isOwnProfile ? (
|
|
211
211
|
<Button
|
|
212
212
|
appearance="subtle" tone="neutral"
|
|
213
|
-
size="
|
|
213
|
+
size="sm"
|
|
214
214
|
onPress={() => navigate?.('ManageAccount')}
|
|
215
215
|
>
|
|
216
216
|
{t('editProfile.title') || 'Edit Profile'}
|
|
@@ -70,7 +70,7 @@ const SearchSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
70
70
|
if (isLoading) {
|
|
71
71
|
return (
|
|
72
72
|
<>
|
|
73
|
-
<Loading size="
|
|
73
|
+
<Loading size="lg" color={bloomTheme.colors.text} />
|
|
74
74
|
</>
|
|
75
75
|
);
|
|
76
76
|
}
|
|
@@ -92,8 +92,8 @@ const SearchSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
92
92
|
description={t('searchSettings.safeSearch.description') || 'Filter out explicit content from search results'}
|
|
93
93
|
rightElement={
|
|
94
94
|
<Switch
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
checked={settings.safeSearch}
|
|
96
|
+
onCheckedChange={() => toggle('safeSearch')}
|
|
97
97
|
disabled={isSaving}
|
|
98
98
|
/>
|
|
99
99
|
}
|
|
@@ -114,8 +114,8 @@ const SearchSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
114
114
|
description={t('searchSettings.personalization.description') || 'Use your activity to improve search results'}
|
|
115
115
|
rightElement={
|
|
116
116
|
<Switch
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
checked={settings.searchPersonalization}
|
|
118
|
+
onCheckedChange={() => toggle('searchPersonalization')}
|
|
119
119
|
disabled={isSaving}
|
|
120
120
|
/>
|
|
121
121
|
}
|
|
@@ -172,7 +172,7 @@ const UserListScreen: React.FC<UserListScreenProps> = ({
|
|
|
172
172
|
</Text> : null}
|
|
173
173
|
{description ? <Text className="text-text-secondary text-sm mt-space-4" numberOfLines={2}>{description}</Text> : null}
|
|
174
174
|
</View>} />
|
|
175
|
-
{!isCurrentUser && itemUserId ? <FollowButton userId={itemUserId} size="
|
|
175
|
+
{!isCurrentUser && itemUserId ? <FollowButton userId={itemUserId} size="sm" /> : null}
|
|
176
176
|
</View>
|
|
177
177
|
);
|
|
178
178
|
},
|
|
@@ -182,10 +182,10 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
182
182
|
if (currentStep === totalSteps - 1) {
|
|
183
183
|
return (
|
|
184
184
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
185
|
-
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="
|
|
185
|
+
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="sm">
|
|
186
186
|
{t('welcomeNew.actions.back') || 'Back'}
|
|
187
187
|
</Button>
|
|
188
|
-
<Button appearance="solid" tone="accent" onPress={finish} size="
|
|
188
|
+
<Button appearance="solid" tone="accent" onPress={finish} size="sm">
|
|
189
189
|
{t('welcomeNew.actions.enter') || 'Enter'}
|
|
190
190
|
</Button>
|
|
191
191
|
</View>
|
|
@@ -195,11 +195,11 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
195
195
|
return (
|
|
196
196
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
197
197
|
{avatarStepIndex > 0 && (
|
|
198
|
-
<Button appearance="subtle" tone="neutral" onPress={skipToAvatar} size="
|
|
198
|
+
<Button appearance="subtle" tone="neutral" onPress={skipToAvatar} size="sm">
|
|
199
199
|
{t('welcomeNew.actions.skip') || 'Skip'}
|
|
200
200
|
</Button>
|
|
201
201
|
)}
|
|
202
|
-
<Button appearance="solid" tone="accent" onPress={nextStep} size="
|
|
202
|
+
<Button appearance="solid" tone="accent" onPress={nextStep} size="sm">
|
|
203
203
|
{t('welcomeNew.actions.next') || 'Next'}
|
|
204
204
|
</Button>
|
|
205
205
|
</View>
|
|
@@ -208,10 +208,10 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
208
208
|
if (nameStepIndex >= 0 && currentStep === nameStepIndex) {
|
|
209
209
|
return (
|
|
210
210
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
211
|
-
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="
|
|
211
|
+
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="sm">
|
|
212
212
|
{t('welcomeNew.actions.back') || 'Back'}
|
|
213
213
|
</Button>
|
|
214
|
-
<Button appearance="solid" tone="accent" onPress={submitName} size="
|
|
214
|
+
<Button appearance="solid" tone="accent" onPress={submitName} size="sm" disabled={!firstName.trim() || savingName} loading={savingName}>
|
|
215
215
|
{t('welcomeNew.actions.continue') || 'Continue'}
|
|
216
216
|
</Button>
|
|
217
217
|
</View>
|
|
@@ -220,10 +220,10 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
220
220
|
if (step.showAvatar) {
|
|
221
221
|
return (
|
|
222
222
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
223
|
-
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="
|
|
223
|
+
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="sm">
|
|
224
224
|
{t('welcomeNew.actions.back') || 'Back'}
|
|
225
225
|
</Button>
|
|
226
|
-
<Button appearance="solid" tone="accent" onPress={nextStep} size="
|
|
226
|
+
<Button appearance="solid" tone="accent" onPress={nextStep} size="sm">
|
|
227
227
|
{avatarUri ? (t('welcomeNew.actions.continue') || 'Continue') : (t('welcomeNew.actions.skip') || 'Skip')}
|
|
228
228
|
</Button>
|
|
229
229
|
</View>
|
|
@@ -231,10 +231,10 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
231
231
|
}
|
|
232
232
|
return (
|
|
233
233
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
234
|
-
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="
|
|
234
|
+
<Button appearance="subtle" tone="neutral" onPress={prevStep} size="sm">
|
|
235
235
|
{t('welcomeNew.actions.back') || 'Back'}
|
|
236
236
|
</Button>
|
|
237
|
-
<Button appearance="solid" tone="accent" onPress={nextStep} size="
|
|
237
|
+
<Button appearance="solid" tone="accent" onPress={nextStep} size="sm">
|
|
238
238
|
{t('welcomeNew.actions.next') || 'Next'}
|
|
239
239
|
</Button>
|
|
240
240
|
</View>
|
|
@@ -296,7 +296,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
|
296
296
|
placeholderColor={`${colors.primary}20`}
|
|
297
297
|
style={styles.avatar}
|
|
298
298
|
/>
|
|
299
|
-
<Button appearance="solid" tone="accent" size="
|
|
299
|
+
<Button appearance="solid" tone="accent" size="sm" onPress={openAvatarPicker}>
|
|
300
300
|
{avatarUri ? (t('welcomeNew.avatar.change') || 'Change Avatar') : (t('welcomeNew.avatar.add') || 'Add Avatar')}
|
|
301
301
|
</Button>
|
|
302
302
|
</View>
|
|
@@ -70,7 +70,7 @@ const TrustRulesScreen: React.FC<BaseScreenProps> = () => {
|
|
|
70
70
|
return (
|
|
71
71
|
<>
|
|
72
72
|
{isLoading ? (
|
|
73
|
-
<Loading size="
|
|
73
|
+
<Loading size="lg" color={bloomTheme.colors.primary} />
|
|
74
74
|
) : error ? (
|
|
75
75
|
<Text className="text-text-secondary text-base text-center px-screen-margin pt-space-40">
|
|
76
76
|
{error}
|
|
@@ -102,7 +102,7 @@ describe('token loss recovery', () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it('re-establishes through the shared identity when the credential is gone, like the cold boot does', async () => {
|
|
105
|
-
const h = harness([null, 'from-
|
|
105
|
+
const h = harness([null, 'from-commons-proof']);
|
|
106
106
|
h.state.credential = false;
|
|
107
107
|
h.state.keyed = true;
|
|
108
108
|
const recovery = createTokenLossRecovery(h.deps);
|
|
@@ -111,7 +111,7 @@ describe('token loss recovery', () => {
|
|
|
111
111
|
await runToCompletion(recovery);
|
|
112
112
|
|
|
113
113
|
expect(h.signOutLocally).not.toHaveBeenCalled();
|
|
114
|
-
expect(h.state.token).toBe('from-
|
|
114
|
+
expect(h.state.token).toBe('from-commons-proof');
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it('gives the key-based lane a bounded number of tries, then signs out', async () => {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* this loop in a sign-out — so a real revocation still signs out.
|
|
22
22
|
* - No credential, but a key-based lane can still re-establish (the native
|
|
23
23
|
* shared identity, or an identity-bound client's own key) → try that a few
|
|
24
|
-
* times, as the cold boot's
|
|
24
|
+
* times, as the cold boot's Commons-proof step would, then sign out.
|
|
25
25
|
* - Neither → the session is over. Sign out now, as before.
|
|
26
26
|
*
|
|
27
27
|
* Framework-free and clock-injectable so the policy is unit tested without a
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
package so.oxy.identity
|
|
2
|
-
|
|
3
|
-
import android.content.ContentProvider
|
|
4
|
-
import android.content.ContentValues
|
|
5
|
-
import android.content.Context
|
|
6
|
-
import android.content.pm.PackageManager
|
|
7
|
-
import android.database.Cursor
|
|
8
|
-
import android.net.Uri
|
|
9
|
-
import android.os.Bundle
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Cross-process read surface for the shared Oxy identity, hosted ONLY by Commons
|
|
13
|
-
* (declared per-app by the `withSharedIdentityProvider` config plugin with a
|
|
14
|
-
* `signature`-level permission, authority `${applicationId}.identity`).
|
|
15
|
-
*
|
|
16
|
-
* `call("getShared")` returns the keypair from Commons's own
|
|
17
|
-
* EncryptedSharedPreferences — but ONLY after verifying the caller is signed
|
|
18
|
-
* with the SAME certificate. That signature check is belt-and-suspenders on top
|
|
19
|
-
* of the manifest's `signature` permission: even if the permission gate were
|
|
20
|
-
* ever misconfigured, a differently-signed caller still gets null.
|
|
21
|
-
*
|
|
22
|
-
* All standard CRUD operations are no-ops; this provider exists solely for the
|
|
23
|
-
* `call()` channel.
|
|
24
|
-
*/
|
|
25
|
-
class OxyIdentityProvider : ContentProvider() {
|
|
26
|
-
override fun onCreate(): Boolean = true
|
|
27
|
-
|
|
28
|
-
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
|
|
29
|
-
if (method != METHOD_GET_SHARED) return null
|
|
30
|
-
val ctx = context ?: return null
|
|
31
|
-
if (!callerSignatureMatches(ctx)) return null
|
|
32
|
-
|
|
33
|
-
val pair = runCatching { OxyIdentityStore.read(ctx) }.getOrNull() ?: return null
|
|
34
|
-
return Bundle().apply {
|
|
35
|
-
putString(OxyIdentityStore.KEY_PRIVATE, pair.first)
|
|
36
|
-
putString(OxyIdentityStore.KEY_PUBLIC, pair.second)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* True when the calling package shares this app's signing certificate. Uses
|
|
42
|
-
* `checkSignatures` (deprecated but still the simplest correct cross-package
|
|
43
|
-
* signature comparison; returns SIGNATURE_MATCH only for same-cert apps).
|
|
44
|
-
*/
|
|
45
|
-
private fun callerSignatureMatches(ctx: Context): Boolean {
|
|
46
|
-
val caller = callingPackage ?: return false
|
|
47
|
-
return runCatching {
|
|
48
|
-
@Suppress("DEPRECATION")
|
|
49
|
-
ctx.packageManager.checkSignatures(caller, ctx.packageName) == PackageManager.SIGNATURE_MATCH
|
|
50
|
-
}.getOrDefault(false)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
override fun query(
|
|
54
|
-
uri: Uri,
|
|
55
|
-
projection: Array<out String>?,
|
|
56
|
-
selection: String?,
|
|
57
|
-
selectionArgs: Array<out String>?,
|
|
58
|
-
sortOrder: String?
|
|
59
|
-
): Cursor? = null
|
|
60
|
-
|
|
61
|
-
override fun getType(uri: Uri): String? = null
|
|
62
|
-
|
|
63
|
-
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
|
64
|
-
|
|
65
|
-
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
|
66
|
-
|
|
67
|
-
override fun update(
|
|
68
|
-
uri: Uri,
|
|
69
|
-
values: ContentValues?,
|
|
70
|
-
selection: String?,
|
|
71
|
-
selectionArgs: Array<out String>?
|
|
72
|
-
): Int = 0
|
|
73
|
-
|
|
74
|
-
companion object {
|
|
75
|
-
private const val METHOD_GET_SHARED = "getShared"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
package so.oxy.identity
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.content.SharedPreferences
|
|
5
|
-
import so.oxy.storage.OxyEncryptedPrefs
|
|
6
|
-
import so.oxy.storage.RecoveryPolicy
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Shared accessor for the hardware-backed EncryptedSharedPreferences that holds
|
|
10
|
-
* the cross-app Oxy identity keypair.
|
|
11
|
-
*
|
|
12
|
-
* Used by BOTH [OxyIdentityModule] (the JS bridge / local read + write) and
|
|
13
|
-
* [OxyIdentityProvider] (the cross-process read surface) so the store name and
|
|
14
|
-
* the encryption scheme can never drift between the two halves.
|
|
15
|
-
*
|
|
16
|
-
* Opening the file — the single-memoized-instance rule and the two-stage keyset
|
|
17
|
-
* self-heal, both load-bearing and non-obvious — lives in [OxyEncryptedPrefs],
|
|
18
|
-
* which is shared with the background-session store. Read its documentation
|
|
19
|
-
* before changing anything about how this file is opened.
|
|
20
|
-
*
|
|
21
|
-
* One consequence worth restating here: an open can legitimately return an EMPTY
|
|
22
|
-
* file (the self-heal wipes a keyset it cannot read). That is safe for this slot
|
|
23
|
-
* specifically because it is a DERIVED copy — Commons re-populates it from the
|
|
24
|
-
* primary self-custody identity on the next boot (`migrateToSharedIdentity`), and
|
|
25
|
-
* that primary lives in expo-secure-store under a different file and different
|
|
26
|
-
* keystore aliases, untouched by any recovery here.
|
|
27
|
-
*/
|
|
28
|
-
internal object OxyIdentityStore {
|
|
29
|
-
const val PREFS_NAME = "oxy_shared_identity"
|
|
30
|
-
const val KEY_PRIVATE = "priv"
|
|
31
|
-
const val KEY_PUBLIC = "pub"
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* [RecoveryPolicy.RebuildFileOnly], like every other store (OxyHQ/oxy#1388).
|
|
35
|
-
* This store used to regenerate the UID-shared androidx master key when its
|
|
36
|
-
* keyset could not be rebuilt, which made every other Oxy app's encrypted prefs
|
|
37
|
-
* unreadable. It never needs to: this slot is a DERIVED copy that Commons
|
|
38
|
-
* re-populates from the primary identity on every launch
|
|
39
|
-
* (`KeyManager.syncSharedIdentity`), so an empty or unavailable slot costs
|
|
40
|
-
* nothing that a relaunch of Commons does not restore.
|
|
41
|
-
*/
|
|
42
|
-
private fun prefs(context: Context): SharedPreferences =
|
|
43
|
-
OxyEncryptedPrefs.open(context, PREFS_NAME, RecoveryPolicy.RebuildFileOnly)
|
|
44
|
-
|
|
45
|
-
/** Read the stored keypair as (privateKey, publicKey), or null when absent/blank. */
|
|
46
|
-
fun read(context: Context): Pair<String, String>? {
|
|
47
|
-
val p = prefs(context)
|
|
48
|
-
val priv = p.getString(KEY_PRIVATE, null) ?: return null
|
|
49
|
-
val pub = p.getString(KEY_PUBLIC, null) ?: return null
|
|
50
|
-
if (priv.isEmpty() || pub.isEmpty()) return null
|
|
51
|
-
return priv to pub
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
fun write(context: Context, priv: String, pub: String) {
|
|
55
|
-
// commit() (synchronous) so a cross-process reader that fires immediately
|
|
56
|
-
// after the write is guaranteed to see the flushed value.
|
|
57
|
-
prefs(context).edit()
|
|
58
|
-
.putString(KEY_PRIVATE, priv)
|
|
59
|
-
.putString(KEY_PUBLIC, pub)
|
|
60
|
-
.commit()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
fun clear(context: Context) {
|
|
64
|
-
prefs(context).edit().clear().commit()
|
|
65
|
-
}
|
|
66
|
-
}
|