eas-cli 18.3.0 → 18.5.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/README.md +98 -95
- package/build/build/android/prepareJob.js +2 -2
- package/build/build/build.js +27 -6
- package/build/build/ios/prepareJob.js +2 -2
- package/build/build/metadata.js +2 -1
- package/build/commandUtils/EasCommand.js +23 -2
- package/build/commandUtils/context/contextUtils/getProjectIdAsync.js +2 -0
- package/build/commandUtils/flags.d.ts +1 -0
- package/build/commandUtils/flags.js +12 -0
- package/build/commandUtils/workflow/fetchLogs.js +11 -2
- package/build/commandUtils/workflow/types.d.ts +5 -1
- package/build/commandUtils/workflow/utils.js +22 -16
- package/build/commands/build/dev.d.ts +1 -0
- package/build/commands/build/dev.js +9 -1
- package/build/commands/metadata/pull.d.ts +1 -0
- package/build/commands/metadata/pull.js +11 -4
- package/build/commands/metadata/push.d.ts +1 -0
- package/build/commands/metadata/push.js +11 -4
- package/build/commands/project/onboarding.js +3 -0
- package/build/commands/workflow/logs.js +12 -12
- package/build/graphql/generated.d.ts +673 -46
- package/build/graphql/generated.js +58 -20
- package/build/graphql/queries/UserQuery.js +3 -0
- package/build/graphql/types/Update.js +3 -0
- package/build/metadata/apple/config/reader.d.ts +5 -1
- package/build/metadata/apple/config/reader.js +8 -0
- package/build/metadata/apple/config/writer.d.ts +5 -1
- package/build/metadata/apple/config/writer.js +13 -0
- package/build/metadata/apple/data.d.ts +6 -2
- package/build/metadata/apple/rules/infoRestrictedWords.js +6 -1
- package/build/metadata/apple/tasks/age-rating.d.ts +1 -1
- package/build/metadata/apple/tasks/age-rating.js +19 -3
- package/build/metadata/apple/tasks/app-review-detail.js +7 -2
- package/build/metadata/apple/tasks/index.js +4 -0
- package/build/metadata/apple/tasks/previews.d.ts +18 -0
- package/build/metadata/apple/tasks/previews.js +208 -0
- package/build/metadata/apple/tasks/screenshots.d.ts +18 -0
- package/build/metadata/apple/tasks/screenshots.js +224 -0
- package/build/metadata/apple/types.d.ts +34 -1
- package/build/metadata/auth.d.ts +11 -1
- package/build/metadata/auth.js +96 -2
- package/build/metadata/download.d.ts +5 -1
- package/build/metadata/download.js +16 -8
- package/build/metadata/upload.d.ts +5 -1
- package/build/metadata/upload.js +11 -4
- package/build/project/projectUtils.d.ts +0 -2
- package/build/project/projectUtils.js +0 -12
- package/build/project/workflow.js +1 -1
- package/build/sentry.d.ts +2 -0
- package/build/sentry.js +22 -0
- package/build/update/utils.d.ts +2 -2
- package/build/update/utils.js +1 -0
- package/build/user/User.d.ts +2 -2
- package/build/user/User.js +3 -0
- package/build/user/expoBrowserAuthFlowLauncher.js +70 -13
- package/oclif.manifest.json +1995 -1918
- package/package.json +12 -11
- package/schema/metadata-0.json +36 -0
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { SubmitProfile } from '@expo/eas-json';
|
|
3
3
|
import { Analytics } from '../analytics/AnalyticsManager';
|
|
4
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
4
5
|
import { CredentialsContext } from '../credentials/context';
|
|
5
6
|
/**
|
|
6
7
|
* Generate a local store configuration from the stores.
|
|
7
8
|
* Note, only App Store is supported at this time.
|
|
8
9
|
*/
|
|
9
|
-
export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
|
|
10
|
+
export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
|
|
10
11
|
projectDir: string;
|
|
11
12
|
profile: SubmitProfile;
|
|
12
13
|
exp: ExpoConfig;
|
|
13
14
|
analytics: Analytics;
|
|
14
15
|
credentialsCtx: CredentialsContext;
|
|
16
|
+
nonInteractive: boolean;
|
|
17
|
+
graphqlClient: ExpoGraphqlClient;
|
|
18
|
+
projectId: string;
|
|
15
19
|
}): Promise<string>;
|
|
@@ -16,16 +16,21 @@ const prompts_1 = require("../prompts");
|
|
|
16
16
|
* Generate a local store configuration from the stores.
|
|
17
17
|
* Note, only App Store is supported at this time.
|
|
18
18
|
*/
|
|
19
|
-
async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
|
|
19
|
+
async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
|
|
20
20
|
const filePath = (0, resolve_1.getStaticConfigFilePath)({ projectDir, profile });
|
|
21
21
|
const fileExists = await fs_extra_1.default.pathExists(filePath);
|
|
22
22
|
if (fileExists) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (nonInteractive) {
|
|
24
|
+
log_1.default.log(`Overwriting existing store config at "${path_1.default.relative(projectDir, filePath)}".`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const filePathRelative = path_1.default.relative(projectDir, filePath);
|
|
28
|
+
const overwrite = await (0, prompts_1.confirmAsync)({
|
|
29
|
+
message: `Do you want to overwrite the existing "${filePathRelative}"?`,
|
|
30
|
+
});
|
|
31
|
+
if (!overwrite) {
|
|
32
|
+
throw new errors_1.MetadataValidationError(`Store config already exists at "${filePath}"`);
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
|
|
@@ -33,6 +38,9 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
|
|
|
33
38
|
credentialsCtx,
|
|
34
39
|
projectDir,
|
|
35
40
|
profile,
|
|
41
|
+
nonInteractive,
|
|
42
|
+
graphqlClient,
|
|
43
|
+
projectId,
|
|
36
44
|
});
|
|
37
45
|
const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_DOWNLOAD, { app, auth });
|
|
38
46
|
log_1.default.addNewLineIfNone();
|
|
@@ -40,7 +48,7 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
|
|
|
40
48
|
const errors = [];
|
|
41
49
|
const config = (0, resolve_1.createAppleWriter)();
|
|
42
50
|
const tasks = (0, tasks_1.createAppleTasks)();
|
|
43
|
-
const taskCtx = { app };
|
|
51
|
+
const taskCtx = { app, projectDir };
|
|
44
52
|
for (const task of tasks) {
|
|
45
53
|
try {
|
|
46
54
|
await task.prepareAsync({ context: taskCtx });
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { SubmitProfile } from '@expo/eas-json';
|
|
3
3
|
import { Analytics } from '../analytics/AnalyticsManager';
|
|
4
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
4
5
|
import { CredentialsContext } from '../credentials/context';
|
|
5
6
|
/**
|
|
6
7
|
* Sync a local store configuration with the stores.
|
|
7
8
|
* Note, only App Store is supported at this time.
|
|
8
9
|
*/
|
|
9
|
-
export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
|
|
10
|
+
export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
|
|
10
11
|
projectDir: string;
|
|
11
12
|
profile: SubmitProfile;
|
|
12
13
|
exp: ExpoConfig;
|
|
13
14
|
analytics: Analytics;
|
|
14
15
|
credentialsCtx: CredentialsContext;
|
|
16
|
+
nonInteractive: boolean;
|
|
17
|
+
graphqlClient: ExpoGraphqlClient;
|
|
18
|
+
projectId: string;
|
|
15
19
|
}): Promise<{
|
|
16
20
|
appleLink: string;
|
|
17
21
|
}>;
|
package/build/metadata/upload.js
CHANGED
|
@@ -14,13 +14,16 @@ const prompts_1 = require("../prompts");
|
|
|
14
14
|
* Sync a local store configuration with the stores.
|
|
15
15
|
* Note, only App Store is supported at this time.
|
|
16
16
|
*/
|
|
17
|
-
async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
|
|
18
|
-
const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile);
|
|
17
|
+
async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
|
|
18
|
+
const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive);
|
|
19
19
|
const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
|
|
20
20
|
exp,
|
|
21
21
|
credentialsCtx,
|
|
22
22
|
projectDir,
|
|
23
23
|
profile,
|
|
24
|
+
nonInteractive,
|
|
25
|
+
graphqlClient,
|
|
26
|
+
projectId,
|
|
24
27
|
});
|
|
25
28
|
const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth });
|
|
26
29
|
log_1.default.addNewLineIfNone();
|
|
@@ -32,7 +35,7 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
|
|
|
32
35
|
// This version is the parent model of all changes we are going to push.
|
|
33
36
|
version: config.getVersion()?.versionString,
|
|
34
37
|
});
|
|
35
|
-
const taskCtx = { app };
|
|
38
|
+
const taskCtx = { app, projectDir };
|
|
36
39
|
for (const task of tasks) {
|
|
37
40
|
try {
|
|
38
41
|
await task.prepareAsync({ context: taskCtx });
|
|
@@ -55,12 +58,16 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
|
|
|
55
58
|
}
|
|
56
59
|
return { appleLink: `https://appstoreconnect.apple.com/apps/${app.id}/appstore` };
|
|
57
60
|
}
|
|
58
|
-
async function loadConfigWithValidationPromptAsync(projectDir, profile) {
|
|
61
|
+
async function loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive) {
|
|
59
62
|
try {
|
|
60
63
|
return await (0, resolve_1.loadConfigAsync)({ projectDir, profile });
|
|
61
64
|
}
|
|
62
65
|
catch (error) {
|
|
63
66
|
if (error instanceof errors_1.MetadataValidationError) {
|
|
67
|
+
if (nonInteractive) {
|
|
68
|
+
(0, errors_1.logMetadataValidationError)(error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
64
71
|
(0, errors_1.logMetadataValidationError)(error);
|
|
65
72
|
log_1.default.newLine();
|
|
66
73
|
log_1.default.warn('Without further updates, the current store configuration can fail to be synchronized with the App Store or pass App Store review.');
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
3
3
|
import { AccountFragment } from '../graphql/generated';
|
|
4
|
-
import { Actor } from '../user/User';
|
|
5
|
-
export declare function getUsernameForBuildMetadataAndBuildJob(user: Actor): string | undefined;
|
|
6
4
|
/**
|
|
7
5
|
* Return a useful name describing the project config.
|
|
8
6
|
* - dynamic: app.config.js
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUsernameForBuildMetadataAndBuildJob = getUsernameForBuildMetadataAndBuildJob;
|
|
4
3
|
exports.getProjectConfigDescription = getProjectConfigDescription;
|
|
5
4
|
exports.isExpoUpdatesInstalled = isExpoUpdatesInstalled;
|
|
6
5
|
exports.isExpoNotificationsInstalled = isExpoNotificationsInstalled;
|
|
@@ -26,17 +25,6 @@ const api_1 = require("../api");
|
|
|
26
25
|
const AppQuery_1 = require("../graphql/queries/AppQuery");
|
|
27
26
|
const log_1 = tslib_1.__importStar(require("../log"));
|
|
28
27
|
const expoCli_1 = require("../utils/expoCli");
|
|
29
|
-
function getUsernameForBuildMetadataAndBuildJob(user) {
|
|
30
|
-
switch (user.__typename) {
|
|
31
|
-
case 'User':
|
|
32
|
-
return user.username;
|
|
33
|
-
case 'SSOUser':
|
|
34
|
-
return user.username;
|
|
35
|
-
case 'Robot':
|
|
36
|
-
// robot users don't have usernames
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
28
|
/**
|
|
41
29
|
* Return a useful name describing the project config.
|
|
42
30
|
* - dynamic: app.config.js
|
|
@@ -44,7 +44,7 @@ async function hasIgnoredIosProjectAsync(projectDir, vcsClient) {
|
|
|
44
44
|
const pbxProjectPath = config_plugins_1.IOSConfig.Paths.getPBXProjectPath(projectDir);
|
|
45
45
|
return await vcsClient.isFileIgnoredAsync(path_1.default.relative(vcsRootPath, pbxProjectPath));
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
catch {
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
50
|
}
|
package/build/sentry.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const Sentry = tslib_1.__importStar(require("@sentry/node"));
|
|
5
|
+
const easCli_1 = require("./utils/easCli");
|
|
6
|
+
Sentry.init({
|
|
7
|
+
dsn: process.env.EAS_CLI_SENTRY_DSN,
|
|
8
|
+
enabled: !!process.env.EAS_CLI_SENTRY_DSN,
|
|
9
|
+
environment: getSentryEnvironment(),
|
|
10
|
+
release: easCli_1.easCliVersion ? `eas-cli@${easCli_1.easCliVersion}` : undefined,
|
|
11
|
+
});
|
|
12
|
+
Sentry.setTag('source', 'eas-cli');
|
|
13
|
+
function getSentryEnvironment() {
|
|
14
|
+
if (process.env.EXPO_LOCAL) {
|
|
15
|
+
return 'local';
|
|
16
|
+
}
|
|
17
|
+
else if (process.env.EXPO_STAGING) {
|
|
18
|
+
return 'staging';
|
|
19
|
+
}
|
|
20
|
+
return 'production';
|
|
21
|
+
}
|
|
22
|
+
exports.default = Sentry;
|
package/build/update/utils.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
3
|
-
import { AppPlatform, Robot, SsoUser, Update, UpdateBranchFragment, UpdateFragment, UpdatePublishMutation, User } from '../graphql/generated';
|
|
3
|
+
import { AppPlatform, PartnerActor, Robot, SsoUser, Update, UpdateBranchFragment, UpdateFragment, UpdatePublishMutation, User } from '../graphql/generated';
|
|
4
4
|
import { RequestedPlatform } from '../platform';
|
|
5
5
|
export type FormatUpdateParameter = Pick<Update, 'id' | 'createdAt' | 'message'> & {
|
|
6
|
-
actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | null;
|
|
6
|
+
actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | Pick<PartnerActor, '__typename' | 'username'> | null;
|
|
7
7
|
};
|
|
8
8
|
export type UpdateJsonInfo = {
|
|
9
9
|
branch: string;
|
package/build/update/utils.js
CHANGED
package/build/user/User.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { CurrentUserQuery, Robot, SsoUser, User } from '../graphql/generated';
|
|
1
|
+
import { CurrentUserQuery, PartnerActor, Robot, SsoUser, User } from '../graphql/generated';
|
|
2
2
|
export type Actor = NonNullable<CurrentUserQuery['meActor']>;
|
|
3
3
|
/**
|
|
4
4
|
* Resolve the name of the actor, either normal user, sso user or robot user.
|
|
5
5
|
* This should be used whenever the "current user" needs to be displayed.
|
|
6
6
|
* The display name CANNOT be used as project owner.
|
|
7
7
|
*/
|
|
8
|
-
export declare function getActorDisplayName(actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | null): string;
|
|
8
|
+
export declare function getActorDisplayName(actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | Pick<PartnerActor, '__typename' | 'username'> | null): string;
|
|
9
9
|
export declare function getActorUsername(actor?: Actor): string | null;
|
package/build/user/User.js
CHANGED
|
@@ -15,6 +15,8 @@ function getActorDisplayName(actor) {
|
|
|
15
15
|
return actor.firstName ? `${actor.firstName} (robot)` : 'robot';
|
|
16
16
|
case 'SSOUser':
|
|
17
17
|
return actor.username;
|
|
18
|
+
case 'PartnerActor':
|
|
19
|
+
return actor.username;
|
|
18
20
|
case undefined:
|
|
19
21
|
return 'unknown';
|
|
20
22
|
}
|
|
@@ -23,6 +25,7 @@ function getActorUsername(actor) {
|
|
|
23
25
|
switch (actor?.__typename) {
|
|
24
26
|
case 'User':
|
|
25
27
|
case 'SSOUser':
|
|
28
|
+
case 'PartnerActor':
|
|
26
29
|
return actor.username;
|
|
27
30
|
case 'Robot':
|
|
28
31
|
case undefined:
|
|
@@ -4,20 +4,65 @@ exports.getSessionUsingBrowserAuthFlowAsync = getSessionUsingBrowserAuthFlowAsyn
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
6
6
|
const better_opn_1 = tslib_1.__importDefault(require("better-opn"));
|
|
7
|
+
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
7
8
|
const http_1 = tslib_1.__importDefault(require("http"));
|
|
8
|
-
const querystring_1 = tslib_1.__importDefault(require("querystring"));
|
|
9
9
|
const api_1 = require("../api");
|
|
10
|
+
const fetch_1 = tslib_1.__importDefault(require("../fetch"));
|
|
10
11
|
const log_1 = tslib_1.__importDefault(require("../log"));
|
|
12
|
+
const CLIENT_ID = 'eas-cli';
|
|
13
|
+
function generateCodeVerifier() {
|
|
14
|
+
return crypto_1.default.randomBytes(32).toString('base64url');
|
|
15
|
+
}
|
|
16
|
+
function generateCodeChallenge(codeVerifier) {
|
|
17
|
+
return crypto_1.default.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
18
|
+
}
|
|
19
|
+
function generateState() {
|
|
20
|
+
return crypto_1.default.randomBytes(32).toString('base64url');
|
|
21
|
+
}
|
|
22
|
+
async function exchangeCodeForSessionSecretAsync({ code, codeVerifier, redirectUri, }) {
|
|
23
|
+
const tokenUrl = `${(0, api_1.getExpoApiBaseUrl)()}/v2/auth/token`;
|
|
24
|
+
const response = await (0, fetch_1.default)(tokenUrl, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
grant_type: 'authorization_code',
|
|
31
|
+
code,
|
|
32
|
+
redirect_uri: redirectUri,
|
|
33
|
+
code_verifier: codeVerifier,
|
|
34
|
+
client_id: CLIENT_ID,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
const result = await response.json();
|
|
38
|
+
const sessionSecret = result?.data?.session_secret;
|
|
39
|
+
if (!sessionSecret) {
|
|
40
|
+
throw new Error('Failed to obtain session secret from token exchange.');
|
|
41
|
+
}
|
|
42
|
+
return sessionSecret;
|
|
43
|
+
}
|
|
11
44
|
async function getSessionUsingBrowserAuthFlowAsync({ sso = false }) {
|
|
12
45
|
const scheme = 'http';
|
|
13
46
|
const hostname = 'localhost';
|
|
14
|
-
const
|
|
47
|
+
const callbackPath = '/auth/callback';
|
|
15
48
|
const expoWebsiteUrl = (0, api_1.getExpoWebsiteBaseUrl)();
|
|
49
|
+
const codeVerifier = generateCodeVerifier();
|
|
50
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
51
|
+
const state = generateState();
|
|
52
|
+
const buildRedirectUri = (port) => `${scheme}://${hostname}:${port}${callbackPath}`;
|
|
16
53
|
const buildExpoLoginUrl = (port, sso) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
54
|
+
// Note: we avoid URLSearchParams here because better-opn calls encodeURI()
|
|
55
|
+
// on the URL before passing it to AppleScript, which would double-encode
|
|
56
|
+
// the percent-encoded values from URLSearchParams.toString().
|
|
57
|
+
const params = [
|
|
58
|
+
`client_id=${CLIENT_ID}`,
|
|
59
|
+
`redirect_uri=${buildRedirectUri(port)}`,
|
|
60
|
+
`response_type=code`,
|
|
61
|
+
`code_challenge=${codeChallenge}`,
|
|
62
|
+
`code_challenge_method=S256`,
|
|
63
|
+
`state=${state}`,
|
|
64
|
+
`confirm_account=true`,
|
|
65
|
+
].join('&');
|
|
21
66
|
return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
|
|
22
67
|
};
|
|
23
68
|
// Start server and begin auth flow
|
|
@@ -34,22 +79,34 @@ async function getSessionUsingBrowserAuthFlowAsync({ sso = false }) {
|
|
|
34
79
|
connection.destroy();
|
|
35
80
|
}
|
|
36
81
|
};
|
|
37
|
-
|
|
82
|
+
const handleRequestAsync = async () => {
|
|
38
83
|
if (!(request.method === 'GET' && request.url?.includes('/auth/callback'))) {
|
|
39
84
|
throw new Error('Unexpected login response.');
|
|
40
85
|
}
|
|
41
86
|
const url = new URL(request.url, `http:${request.headers.host}`);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
87
|
+
const code = url.searchParams.get('code');
|
|
88
|
+
const returnedState = url.searchParams.get('state');
|
|
89
|
+
if (!code) {
|
|
90
|
+
throw new Error('Request missing code search parameter.');
|
|
91
|
+
}
|
|
92
|
+
if (returnedState !== state) {
|
|
93
|
+
throw new Error('State mismatch. Possible CSRF attack.');
|
|
45
94
|
}
|
|
95
|
+
const address = server.address();
|
|
96
|
+
(0, assert_1.default)(address !== null && typeof address === 'object');
|
|
97
|
+
const redirectUri = buildRedirectUri(address.port);
|
|
98
|
+
const sessionSecret = await exchangeCodeForSessionSecretAsync({
|
|
99
|
+
code,
|
|
100
|
+
codeVerifier,
|
|
101
|
+
redirectUri,
|
|
102
|
+
});
|
|
46
103
|
resolve(sessionSecret);
|
|
47
104
|
redirectAndCleanup('success');
|
|
48
|
-
}
|
|
49
|
-
catch
|
|
105
|
+
};
|
|
106
|
+
handleRequestAsync().catch(error => {
|
|
50
107
|
redirectAndCleanup('error');
|
|
51
108
|
reject(error);
|
|
52
|
-
}
|
|
109
|
+
});
|
|
53
110
|
});
|
|
54
111
|
server.listen(0, hostname, () => {
|
|
55
112
|
log_1.default.log('Waiting for browser login...');
|