зеркало из https://github.com/mozilla/fxa.git
feat(recoveryKey): Add `estimatedSyncDeviceCount` to graphql account resolver
This commit is contained in:
Родитель
58f8e720b2
Коммит
c32e74c82f
|
@ -75,7 +75,9 @@ describe('#integration - AccountResolver', () => {
|
|||
useValue: notifierService,
|
||||
};
|
||||
authClient = {};
|
||||
profileClient = {};
|
||||
profileClient = {
|
||||
deleteCache: jest.fn(),
|
||||
};
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AccountResolver,
|
||||
|
@ -159,9 +161,10 @@ describe('#integration - AccountResolver', () => {
|
|||
it('resolves recoveryKey', async () => {
|
||||
authClient.recoveryKeyExists = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ exists: true });
|
||||
.mockResolvedValue({ exists: true, estimatedSyncDeviceCount: 1 });
|
||||
const result = await resolver.recoveryKey('token', headers);
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.exists).toBeTruthy();
|
||||
expect(result.estimatedSyncDeviceCount).toBe(1);
|
||||
});
|
||||
|
||||
it('resolves totp', async () => {
|
||||
|
|
|
@ -37,26 +37,26 @@ import { AuthClientService } from '../backend/auth-client.service';
|
|||
import { ProfileClientService } from '../backend/profile-client.service';
|
||||
import { GqlSessionToken, GqlUserId, GqlXHeaders } from '../decorators';
|
||||
import {
|
||||
AccountResetInput,
|
||||
AccountStatusInput,
|
||||
AttachedClientDisconnectInput,
|
||||
ChangeRecoveryCodesInput,
|
||||
CreateTotpInput,
|
||||
DeleteRecoveryKeyInput,
|
||||
DeleteTotpInput,
|
||||
EmailInput,
|
||||
SendSessionVerificationInput,
|
||||
UpdateDisplayNameInput,
|
||||
VerifyEmailInput,
|
||||
VerifyEmailCodeInput,
|
||||
VerifySessionInput,
|
||||
VerifyTotpInput,
|
||||
PasswordChangeFinishInput,
|
||||
PasswordChangeStartInput,
|
||||
PasswordForgotCodeStatusInput,
|
||||
PasswordForgotSendCodeInput,
|
||||
PasswordForgotVerifyCodeInput,
|
||||
PasswordForgotCodeStatusInput,
|
||||
AccountResetInput,
|
||||
AccountStatusInput,
|
||||
RecoveryKeyBundleInput,
|
||||
PasswordChangeStartInput,
|
||||
PasswordChangeFinishInput,
|
||||
SendSessionVerificationInput,
|
||||
UpdateDisplayNameInput,
|
||||
VerifyEmailCodeInput,
|
||||
VerifyEmailInput,
|
||||
VerifySessionInput,
|
||||
VerifyTotpInput,
|
||||
} from './dto/input';
|
||||
import { DeleteAvatarInput } from './dto/input/delete-avatar';
|
||||
import { MetricsOptInput } from './dto/input/metrics-opt';
|
||||
|
@ -64,21 +64,21 @@ import { RejectUnblockCodeInput } from './dto/input/reject-unblock-code';
|
|||
import { SignInInput } from './dto/input/sign-in';
|
||||
import { SignUpInput } from './dto/input/sign-up';
|
||||
import {
|
||||
AccountResetPayload,
|
||||
AccountStatusPayload,
|
||||
BasicPayload,
|
||||
ChangeRecoveryCodesPayload,
|
||||
CreateTotpPayload,
|
||||
UpdateDisplayNamePayload,
|
||||
VerifyTotpPayload,
|
||||
CredentialStatusPayload,
|
||||
PasswordChangeFinishPayload,
|
||||
PasswordChangeStartPayload,
|
||||
PasswordForgotCodeStatusPayload,
|
||||
PasswordForgotSendCodePayload,
|
||||
PasswordForgotVerifyCodePayload,
|
||||
PasswordForgotCodeStatusPayload,
|
||||
AccountResetPayload,
|
||||
AccountStatusPayload,
|
||||
RecoveryKeyBundlePayload,
|
||||
CredentialStatusPayload,
|
||||
PasswordChangeStartPayload,
|
||||
UpdateDisplayNamePayload,
|
||||
VerifyTotpPayload,
|
||||
WrappedKeysPayload,
|
||||
PasswordChangeFinishPayload,
|
||||
} from './dto/payload';
|
||||
import { SignedInAccountPayload } from './dto/payload/signed-in-account';
|
||||
import { SignedUpAccountPayload } from './dto/payload/signed-up-account';
|
||||
|
@ -806,12 +806,7 @@ export class AccountResolver {
|
|||
@GqlSessionToken() token: string,
|
||||
@GqlXHeaders() headers: Headers
|
||||
) {
|
||||
const result = await this.authAPI.recoveryKeyExists(
|
||||
token,
|
||||
undefined,
|
||||
headers
|
||||
);
|
||||
return result.exists;
|
||||
return await this.authAPI.recoveryKeyExists(token, undefined, headers);
|
||||
}
|
||||
|
||||
@ResolveField()
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Subscription } from './subscription';
|
|||
import { Totp } from './totp';
|
||||
import { LinkedAccount } from './linkedAccount';
|
||||
import { SecurityEvent } from './securityEvent';
|
||||
import { RecoveryKey } from './recoveryKey';
|
||||
|
||||
@ObjectType({
|
||||
description: "The current authenticated user's Firefox Account record.",
|
||||
|
@ -41,10 +42,10 @@ export class Account {
|
|||
@Field((type) => Totp)
|
||||
public totp!: Totp;
|
||||
|
||||
@Field({
|
||||
@Field((type) => RecoveryKey, {
|
||||
description: 'Whether the user has had an account recovery key issued.',
|
||||
})
|
||||
public recoveryKey!: boolean;
|
||||
public recoveryKey!: RecoveryKey;
|
||||
|
||||
@Field({ description: 'Whether metrics are enabled and may be reported' })
|
||||
public metricsEnabled!: boolean;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class RecoveryKey {
|
||||
@Field({
|
||||
nullable: false,
|
||||
description: 'Whether recovery key exists for user',
|
||||
})
|
||||
public exists!: boolean;
|
||||
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'The number of estimated sync devices a user might have.',
|
||||
})
|
||||
public estimatedSyncDeviceCount!: number;
|
||||
}
|
|
@ -11,7 +11,10 @@ export const INITIAL_METRICS_QUERY = gql`
|
|||
query GetInitialMetricsState {
|
||||
account {
|
||||
uid
|
||||
recoveryKey
|
||||
recoveryKey {
|
||||
exists
|
||||
estimatedSyncDeviceCount
|
||||
}
|
||||
metricsEnabled
|
||||
emails {
|
||||
email
|
||||
|
|
|
@ -95,7 +95,7 @@ const mockMetricsQueryAccountAmplitude = {
|
|||
const mockMetricsQueryAccountResult = {
|
||||
account: {
|
||||
uid: 'abc123',
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
metricsEnabled: true,
|
||||
emails: [
|
||||
{
|
||||
|
|
|
@ -202,7 +202,7 @@ export const App = ({
|
|||
Metrics.init(metricsEnabled, flowQueryParams);
|
||||
if (data?.account?.metricsEnabled) {
|
||||
Metrics.initUserPreferences({
|
||||
recoveryKey: data.account.recoveryKey,
|
||||
recoveryKey: data.account.recoveryKey.exists,
|
||||
hasSecondaryVerifiedEmail:
|
||||
data.account.emails.length > 1 && data.account.emails[1].verified,
|
||||
totpActive: data.account.totp.exists && data.account.totp.verified,
|
||||
|
|
|
@ -32,7 +32,9 @@ const accountWithChangeKeySuccess = {
|
|||
createRecoveryKey: () => {
|
||||
return new Uint8Array(20);
|
||||
},
|
||||
recoveryKey: true,
|
||||
recoveryKey: {
|
||||
exists: true,
|
||||
},
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithCreateKeySuccess = {
|
||||
|
@ -40,7 +42,7 @@ const accountWithCreateKeySuccess = {
|
|||
createRecoveryKey: () => {
|
||||
return new Uint8Array(20);
|
||||
},
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithInvalidPasswordOnSubmit = {
|
||||
|
@ -48,7 +50,7 @@ const accountWithInvalidPasswordOnSubmit = {
|
|||
createRecoveryKey: () => {
|
||||
throw AuthUiErrors.INCORRECT_PASSWORD;
|
||||
},
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithThrottledErrorOnSubmit = {
|
||||
|
@ -56,7 +58,7 @@ const accountWithThrottledErrorOnSubmit = {
|
|||
createRecoveryKey: () => {
|
||||
throw AuthUiErrors.THROTTLED;
|
||||
},
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const StoryWithContext = (account: Account) => {
|
||||
|
|
|
@ -35,6 +35,7 @@ jest.mock('base32-encode', () =>
|
|||
const setFormattedRecoveryKey = jest.fn();
|
||||
|
||||
const accountWithKeyCreationSuccess = {
|
||||
recoveryKey: { exists: false },
|
||||
accountRecovery: false,
|
||||
createRecoveryKey: jest.fn().mockResolvedValue(new Uint8Array(20)),
|
||||
} as unknown as Account;
|
||||
|
@ -48,6 +49,7 @@ const accountWithKeyChangeSuccess = {
|
|||
|
||||
const getAccountWithErrorOnKeyCreation = (error: AuthUiError) => {
|
||||
return {
|
||||
recoveryKey: { exists: false },
|
||||
accountRecovery: false,
|
||||
createRecoveryKey: () => {
|
||||
throw error;
|
||||
|
|
|
@ -49,12 +49,12 @@ export const FlowRecoveryKeyConfirmPwd = ({
|
|||
const [actionType, setActionType] = useState<RecoveryKeyAction>();
|
||||
|
||||
useEffect(() => {
|
||||
if (account.recoveryKey === true) {
|
||||
if (account.recoveryKey.exists === true) {
|
||||
setActionType(RecoveryKeyAction.Change);
|
||||
} else {
|
||||
setActionType(RecoveryKeyAction.Create);
|
||||
}
|
||||
}, [account.recoveryKey]);
|
||||
}, [account.recoveryKey.exists]);
|
||||
|
||||
const { formState, getValues, handleSubmit, register } = useForm<FormData>({
|
||||
mode: 'all',
|
||||
|
|
|
@ -21,7 +21,7 @@ const recoveryKeyRaw = new Uint8Array(20);
|
|||
|
||||
const accountWithSuccess = {
|
||||
...MOCK_ACCOUNT,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
createRecoveryKey: () => recoveryKeyRaw,
|
||||
} as unknown as Account;
|
||||
|
||||
|
@ -48,7 +48,7 @@ const accountWithUnexpectedError = {
|
|||
|
||||
const accountWithKeyEnabled = {
|
||||
...MOCK_ACCOUNT,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
createRecoveryKey: () => recoveryKeyRaw,
|
||||
deleteRecoveryKey: () => true,
|
||||
} as unknown as Account;
|
||||
|
|
|
@ -43,13 +43,13 @@ window.URL.createObjectURL = jest.fn();
|
|||
|
||||
const accountWithoutKey = {
|
||||
...MOCK_ACCOUNT,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
createRecoveryKey: jest.fn().mockResolvedValue(new Uint8Array(20)),
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithKey = {
|
||||
...MOCK_ACCOUNT,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
createRecoveryKey: jest.fn().mockResolvedValue(new Uint8Array(20)),
|
||||
deleteRecoveryKey: jest.fn().mockResolvedValue(true),
|
||||
} as unknown as Account;
|
||||
|
|
|
@ -32,7 +32,7 @@ export const PageRecoveryKeyCreate = (props: RouteComponentProps) => {
|
|||
const [currentStep, setCurrentStep] = useState<number>(1);
|
||||
const [formattedRecoveryKey, setFormattedRecoveryKey] = useState<string>('');
|
||||
|
||||
const action = recoveryKey
|
||||
const action = recoveryKey.exists
|
||||
? RecoveryKeyAction.Change
|
||||
: RecoveryKeyAction.Create;
|
||||
const goHome = () =>
|
||||
|
|
|
@ -30,7 +30,7 @@ const coldStartAccount = {
|
|||
...MOCK_ACCOUNT,
|
||||
displayName: null,
|
||||
avatar: { id: null, url: null },
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
totp: { exists: false, verified: false },
|
||||
attachedClients: [SERVICES_NON_MOBILE[0]],
|
||||
} as unknown as Account;
|
||||
|
|
|
@ -32,7 +32,7 @@ const storyWithAccount = (account: Partial<Account>, storyName?: string) => {
|
|||
};
|
||||
|
||||
export const Default = storyWithAccount({
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
totp: { exists: false, verified: false },
|
||||
hasPassword: true,
|
||||
passwordCreated: 1651860173938,
|
||||
|
@ -40,7 +40,7 @@ export const Default = storyWithAccount({
|
|||
|
||||
export const SecurityFeaturesEnabled = storyWithAccount(
|
||||
{
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
totp: { verified: true, exists: true },
|
||||
hasPassword: true,
|
||||
passwordCreated: 1651860173938,
|
||||
|
@ -50,7 +50,7 @@ export const SecurityFeaturesEnabled = storyWithAccount(
|
|||
|
||||
export const NoPassword = storyWithAccount(
|
||||
{
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
totp: { verified: false, exists: false },
|
||||
hasPassword: false,
|
||||
},
|
||||
|
|
|
@ -26,7 +26,9 @@ describe('Security', () => {
|
|||
displayName: 'Jody',
|
||||
passwordCreated: 123456789,
|
||||
hasPassword: true,
|
||||
recoveryKey: false,
|
||||
recoveryKey: {
|
||||
exists: false,
|
||||
},
|
||||
totp: { exists: false },
|
||||
} as unknown as Account;
|
||||
renderWithRouter(
|
||||
|
@ -51,7 +53,7 @@ describe('Security', () => {
|
|||
emails: [],
|
||||
displayName: 'Jody',
|
||||
passwordCreated: 0,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
totp: { exists: true, verified: true },
|
||||
} as unknown as Account;
|
||||
renderWithRouter(
|
||||
|
@ -67,7 +69,7 @@ describe('Security', () => {
|
|||
describe('Password row', () => {
|
||||
it('renders as expected when account has a password', async () => {
|
||||
const account = {
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
totp: { exists: false },
|
||||
primaryEmail: {
|
||||
email: 'jody@mozilla.com',
|
||||
|
@ -99,7 +101,7 @@ describe('Security', () => {
|
|||
|
||||
it('renders as expected when account does not have a password', async () => {
|
||||
const account = {
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
totp: { exists: false },
|
||||
primaryEmail: {
|
||||
email: 'jody@mozilla.com',
|
||||
|
|
|
@ -19,17 +19,17 @@ export default {
|
|||
|
||||
const accountHasRecoveryKey = {
|
||||
hasPassword: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exist: true },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithoutRecoveryKey = {
|
||||
hasPassword: true,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithoutPassword = {
|
||||
hasPassword: false,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const storyWithContext = (
|
||||
|
|
|
@ -16,17 +16,17 @@ jest.mock('../../../lib/metrics', () => ({
|
|||
|
||||
const accountHasRecoveryKey = {
|
||||
hasPassword: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithoutRecoveryKey = {
|
||||
hasPassword: true,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const accountWithoutPassword = {
|
||||
hasPassword: false,
|
||||
recoveryKey: false,
|
||||
recoveryKey: { exists: false },
|
||||
} as unknown as Account;
|
||||
|
||||
const renderWithContext = (
|
||||
|
@ -102,7 +102,7 @@ describe('UnitRowRecoveryKey', () => {
|
|||
it('emits correct submit and success metrics on successful deletion', async () => {
|
||||
const accountHasRecoveryKeyWithDeleteSuccess = {
|
||||
hasPassword: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
deleteRecoveryKey: jest.fn().mockResolvedValue(true),
|
||||
} as unknown as Account;
|
||||
|
||||
|
@ -123,7 +123,7 @@ describe('UnitRowRecoveryKey', () => {
|
|||
it('emits expected submit and failure metrics on failed deletion', async () => {
|
||||
const accountHasRecoveryKeyWithDeleteFailure = {
|
||||
hasPassword: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
deleteRecoveryKey: jest.fn().mockRejectedValue(false),
|
||||
} as unknown as Account;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import GleanMetrics from '../../../lib/glean';
|
|||
export const UnitRowRecoveryKey = () => {
|
||||
const account = useAccount();
|
||||
|
||||
const recoveryKey = account.recoveryKey;
|
||||
const recoveryKey = account.recoveryKey.exists;
|
||||
const alertBar = useAlertBar();
|
||||
const [modalRevealed, revealModal, hideModal] = useBooleanState();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
|
|
@ -46,7 +46,7 @@ const mockConfig: Config['glean'] = {
|
|||
let mockMetricsFlow: MetricsFlow | null = null;
|
||||
const mockAccount = {
|
||||
metricsEnabled: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
totpActive: true,
|
||||
hasSecondaryVerifiedEmail: false,
|
||||
} as unknown as ReturnType<typeof useAccount>;
|
||||
|
|
|
@ -46,7 +46,7 @@ jest.mock('../models', () => ({
|
|||
// Keep in mind that jest.mock is hoisted, so importing MOCK_ACCOUNT in a
|
||||
// regular fashion "before" this will not work.
|
||||
.mockReturnValue({
|
||||
recoveryKey: true,
|
||||
recoveryKey: { exists: true },
|
||||
hasSecondaryVerifiedEmail: false,
|
||||
totpActive: true,
|
||||
}),
|
||||
|
@ -91,7 +91,7 @@ function initFlow(enabled = true) {
|
|||
});
|
||||
initUserPreferences({
|
||||
hasSecondaryVerifiedEmail: MOCK_ACCOUNT.emails.length > 1,
|
||||
recoveryKey: MOCK_ACCOUNT.recoveryKey,
|
||||
recoveryKey: MOCK_ACCOUNT.recoveryKey.exists,
|
||||
totpActive: MOCK_ACCOUNT.totp.exists && MOCK_ACCOUNT.totp.verified,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -92,7 +92,10 @@ export interface AccountData {
|
|||
accountCreated: number;
|
||||
passwordCreated: number;
|
||||
hasPassword: boolean;
|
||||
recoveryKey: boolean;
|
||||
recoveryKey: {
|
||||
exists: boolean;
|
||||
estimatedSyncDeviceCount?: number;
|
||||
};
|
||||
metricsEnabled: boolean;
|
||||
primaryEmail: Email;
|
||||
emails: Email[];
|
||||
|
@ -145,7 +148,10 @@ export const GET_ACCOUNT = gql`
|
|||
}
|
||||
accountCreated
|
||||
passwordCreated
|
||||
recoveryKey
|
||||
recoveryKey {
|
||||
exists
|
||||
estimatedSyncDeviceCount
|
||||
}
|
||||
metricsEnabled
|
||||
primaryEmail @client
|
||||
emails {
|
||||
|
@ -222,7 +228,9 @@ export const GET_CONNECTED_CLIENTS = gql`
|
|||
export const GET_RECOVERY_KEY_EXISTS = gql`
|
||||
query GetRecoveryKeyExists {
|
||||
account {
|
||||
recoveryKey
|
||||
recoveryKey {
|
||||
exists
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -421,7 +429,7 @@ export class Account implements AccountData {
|
|||
async hasRecoveryKey(email: string): Promise<boolean> {
|
||||
// Users may not be logged in (no session token) so we currently can't use GQL here
|
||||
return this.withLoadingStatus(
|
||||
(await this.authClient.recoveryKeyExists(sessionToken()!, email)).exists
|
||||
await this.authClient.recoveryKeyExists(sessionToken()!, email)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1022,8 +1030,11 @@ export class Account implements AccountData {
|
|||
cache.modify({
|
||||
id: cache.identify({ __typename: 'Account' }),
|
||||
fields: {
|
||||
recoveryKey() {
|
||||
return false;
|
||||
recoveryKey(existingData) {
|
||||
return {
|
||||
exists: false,
|
||||
estimatedSyncDeviceCount: existingData.estimatedSyncDeviceCount,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1186,8 +1197,11 @@ export class Account implements AccountData {
|
|||
cache.modify({
|
||||
id: cache.identify({ __typename: 'Account' }),
|
||||
fields: {
|
||||
recoveryKey() {
|
||||
return true;
|
||||
recoveryKey(existingData) {
|
||||
return {
|
||||
exists: true,
|
||||
estimatedSyncDeviceCount: existingData.estimatedSyncDeviceCount,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1281,8 +1295,11 @@ export class Account implements AccountData {
|
|||
cache.modify({
|
||||
id: cache.identify({ __typename: 'Account' }),
|
||||
fields: {
|
||||
recoveryKey() {
|
||||
return false;
|
||||
recoveryKey(existingData) {
|
||||
return {
|
||||
exists: false,
|
||||
estimatedSyncDeviceCount: existingData.estimatedSyncDeviceCount,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -71,7 +71,10 @@ export function defaultAppContext(context?: AppContextValue) {
|
|||
accountCreated: 123456789,
|
||||
passwordCreated: 123456789,
|
||||
hasPassword: true,
|
||||
recoveryKey: true,
|
||||
recoveryKey: {
|
||||
exists: true,
|
||||
estimatedSyncDeviceCount: 0,
|
||||
},
|
||||
metricsEnabled: true,
|
||||
attachedClients: [],
|
||||
subscriptions: [],
|
||||
|
|
|
@ -22,7 +22,10 @@ export const INITIAL_SETTINGS_QUERY = gql`
|
|||
}
|
||||
accountCreated
|
||||
passwordCreated
|
||||
recoveryKey
|
||||
recoveryKey {
|
||||
exists
|
||||
estimatedSyncDeviceCount
|
||||
}
|
||||
metricsEnabled
|
||||
primaryEmail @client
|
||||
emails {
|
||||
|
|
Загрузка…
Ссылка в новой задаче