Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cli] Added auth commands to expo (#16087)
* Added auth commands to expo/expo * Added api tests Update otp.ts Update actions.ts * Update generated.ts * Update index-test.ts * Added module loading tests drop amplitude Update rudderstackClient.ts * Apply suggestions from code review Co-authored-by: James Ide <ide@users.noreply.github.com> Co-authored-by: Cedric van Putten <me@bycedric.com> * clean up lazy load analytics Update prompts.ts Update actions.ts * fix tests * Update user.ts * Fix up code Update registerAsync.ts Remove unused settings Drop unused Update otp.ts Update whoamiAsync.ts * Update api-test.ts * Apply suggestions from code review Co-authored-by: kgc00 <kirby@expo.io> * Update rudderstackClient.ts * Update rudderstackClient.ts Co-authored-by: James Ide <ide@users.noreply.github.com> Co-authored-by: Cedric van Putten <me@bycedric.com> Co-authored-by: kgc00 <kirby@expo.io>
- Loading branch information
1 parent
df0caa1
commit 08f1ae9
Showing
34 changed files
with
2,885 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#!/usr/bin/env node | ||
import chalk from 'chalk'; | ||
|
||
import { Command } from '../../bin/cli'; | ||
import * as Log from '../log'; | ||
import { assertArgs } from '../utils/args'; | ||
import { logCmdError } from '../utils/errors'; | ||
|
||
export const expoLogin: Command = async (argv) => { | ||
const args = assertArgs( | ||
{ | ||
// Types | ||
'--help': Boolean, | ||
'--username': String, | ||
'--password': String, | ||
'--otp': String, | ||
// Aliases | ||
'-h': '--help', | ||
'-u': '--username', | ||
'-p': '--password', | ||
}, | ||
argv | ||
); | ||
|
||
if (args['--help']) { | ||
Log.exit( | ||
chalk` | ||
{bold Description} | ||
Log in to an Expo account | ||
{bold Usage} | ||
$ npx expo login | ||
Options | ||
-u, --username <string> Username | ||
-p, --password <string> Password | ||
--otp <string> One-time password from your 2FA device | ||
-h, --help Output usage information | ||
`, | ||
0 | ||
); | ||
} | ||
|
||
const { showLoginPromptAsync } = await import('../utils/user/actions'); | ||
return showLoginPromptAsync({ | ||
// Parsed options | ||
username: args['--username'], | ||
password: args['--password'], | ||
otp: args['--otp'], | ||
}).catch(logCmdError); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env node | ||
import chalk from 'chalk'; | ||
|
||
import { Command } from '../../bin/cli'; | ||
import * as Log from '../log'; | ||
import { assertArgs } from '../utils/args'; | ||
import { logCmdError } from '../utils/errors'; | ||
|
||
export const expoLogout: Command = async (argv) => { | ||
const args = assertArgs( | ||
{ | ||
// Types | ||
'--help': Boolean, | ||
// Aliases | ||
'-h': '--help', | ||
}, | ||
argv | ||
); | ||
|
||
if (args['--help']) { | ||
Log.exit( | ||
chalk` | ||
{bold Description} | ||
Log out of an Expo account | ||
{bold Usage} | ||
$ npx expo logout | ||
Options | ||
-h, --help Output usage information | ||
`, | ||
0 | ||
); | ||
} | ||
|
||
const { logoutAsync } = await import('../utils/user/user'); | ||
return logoutAsync().catch(logCmdError); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env node | ||
import chalk from 'chalk'; | ||
|
||
import { Command } from '../../bin/cli'; | ||
import * as Log from '../log'; | ||
import { assertArgs } from '../utils/args'; | ||
import { logCmdError } from '../utils/errors'; | ||
|
||
export const expoRegister: Command = async (argv) => { | ||
const args = assertArgs( | ||
{ | ||
// Types | ||
'--help': Boolean, | ||
// Aliases | ||
'-h': '--help', | ||
}, | ||
argv | ||
); | ||
|
||
if (args['--help']) { | ||
Log.exit( | ||
chalk` | ||
{bold Description} | ||
Sign up for a new Expo account | ||
{bold Usage} | ||
$ npx expo register | ||
Options | ||
-h, --help Output usage information | ||
`, | ||
0 | ||
); | ||
} | ||
|
||
const { registerAsync } = await import('./registerAsync'); | ||
return registerAsync().catch(logCmdError); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import openBrowserAsync from 'better-opn'; | ||
|
||
import { CI } from '../utils/env'; | ||
import { CommandError } from '../utils/errors'; | ||
import { learnMore } from '../utils/link'; | ||
import { ora } from '../utils/ora'; | ||
|
||
export async function registerAsync() { | ||
if (CI) { | ||
throw new CommandError( | ||
'NON_INTERACTIVE', | ||
`Cannot register an account in CI. Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore( | ||
'https://docs.expo.dev/accounts/programmatic-access/' | ||
)})` | ||
); | ||
} | ||
|
||
const registrationUrl = `https://expo.dev/signup`; | ||
const failedMessage = `Unable to open a web browser. Register an account at: ${registrationUrl}`; | ||
const spinner = ora(`Opening ${registrationUrl}`).start(); | ||
try { | ||
const opened = await openBrowserAsync(registrationUrl); | ||
|
||
if (opened) { | ||
spinner.succeed(`Opened ${registrationUrl}`); | ||
} else { | ||
spinner.fail(failedMessage); | ||
} | ||
return; | ||
} catch (error) { | ||
spinner.fail(failedMessage); | ||
throw error; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import assert from 'assert'; | ||
import { RequestError } from 'got/dist/source'; | ||
import nock from 'nock'; | ||
|
||
import { ApiV2Error, apiClient, getExpoApiBaseUrl } from '../api'; | ||
|
||
it('converts Expo APIv2 error to ApiV2Error', async () => { | ||
nock(getExpoApiBaseUrl()) | ||
.post('/v2/test') | ||
.reply(400, { | ||
errors: [ | ||
{ | ||
message: 'hellomessage', | ||
code: 'TEST_CODE', | ||
stack: 'line 1: hello', | ||
details: { who: 'world' }, | ||
metadata: { an: 'object' }, | ||
}, | ||
], | ||
}); | ||
|
||
expect.assertions(5); | ||
|
||
try { | ||
await apiClient.post('test'); | ||
} catch (error: any) { | ||
assert(error instanceof ApiV2Error); | ||
|
||
expect(error.message).toEqual('hellomessage'); | ||
expect(error.expoApiV2ErrorCode).toEqual('TEST_CODE'); | ||
expect(error.expoApiV2ErrorDetails).toEqual({ who: 'world' }); | ||
expect(error.expoApiV2ErrorMetadata).toEqual({ an: 'object' }); | ||
expect(error.expoApiV2ErrorServerStack).toEqual('line 1: hello'); | ||
} | ||
}); | ||
|
||
it('does not convert non-APIv2 error to ApiV2Error', async () => { | ||
nock(getExpoApiBaseUrl()).post('/v2/test').reply(500, 'Something went wrong'); | ||
|
||
expect.assertions(2); | ||
|
||
try { | ||
await apiClient.post('test'); | ||
} catch (error: any) { | ||
expect(error).toBeInstanceOf(RequestError); | ||
expect(error).not.toBeInstanceOf(ApiV2Error); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import RudderAnalytics from '@expo/rudder-sdk-node'; | ||
import os from 'os'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
|
||
import { EXPO_LOCAL, EXPO_STAGING, EXPO_NO_TELEMETRY } from '../env'; | ||
import UserSettings from '../user/UserSettings'; | ||
|
||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = { | ||
darwin: 'Mac', | ||
win32: 'Windows', | ||
linux: 'Linux', | ||
}; | ||
|
||
let client: RudderAnalytics | null = null; | ||
let identified = false; | ||
let identifyData: { | ||
userId: string; | ||
deviceId: string; | ||
traits: Record<string, any>; | ||
} | null = null; | ||
|
||
function getClient(): RudderAnalytics { | ||
if (client) { | ||
return client; | ||
} | ||
|
||
client = new RudderAnalytics( | ||
EXPO_STAGING || EXPO_LOCAL ? '24TKICqYKilXM480mA7ktgVDdea' : '24TKR7CQAaGgIrLTgu3Fp4OdOkI', // expo unified | ||
'https://cdp.expo.dev/v1/batch', | ||
{ | ||
flushInterval: 300, | ||
} | ||
); | ||
|
||
// Install flush on exit... | ||
process.on('SIGINT', () => client?.flush?.()); | ||
process.on('SIGTERM', () => client?.flush?.()); | ||
|
||
return client; | ||
} | ||
|
||
export async function setUserDataAsync(userId: string, traits: Record<string, any>): Promise<void> { | ||
if (EXPO_NO_TELEMETRY) { | ||
return; | ||
} | ||
const savedDeviceId = await UserSettings.getAsync('analyticsDeviceId', null); | ||
const deviceId = savedDeviceId ?? uuidv4(); | ||
if (!savedDeviceId) { | ||
await UserSettings.setAsync('analyticsDeviceId', deviceId); | ||
} | ||
|
||
identifyData = { | ||
userId, | ||
deviceId, | ||
traits, | ||
}; | ||
|
||
ensureIdentified(); | ||
} | ||
|
||
export function logEvent(event: 'action', properties: Record<string, any> = {}): void { | ||
if (EXPO_NO_TELEMETRY) { | ||
return; | ||
} | ||
ensureIdentified(); | ||
|
||
const { userId, deviceId } = identifyData ?? {}; | ||
const commonEventProperties = { source_version: process.env.__EXPO_VERSION, source: 'expo' }; | ||
|
||
const identity = { userId: userId ?? undefined, anonymousId: deviceId ?? uuidv4() }; | ||
getClient().track({ | ||
event, | ||
properties: { ...properties, ...commonEventProperties }, | ||
...identity, | ||
context: getContext(), | ||
}); | ||
} | ||
|
||
function ensureIdentified(): void { | ||
if (EXPO_NO_TELEMETRY || identified || !identifyData) { | ||
return; | ||
} | ||
|
||
getClient().identify({ | ||
userId: identifyData.userId, | ||
anonymousId: identifyData.deviceId, | ||
traits: identifyData.traits, | ||
}); | ||
identified = true; | ||
} | ||
|
||
function getContext(): Record<string, any> { | ||
const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform(); | ||
return { | ||
os: { name: platform, version: os.release() }, | ||
device: { type: platform, model: platform }, | ||
app: { name: 'expo', version: process.env.__EXPO_VERSION }, | ||
}; | ||
} |
Oops, something went wrong.