Skip to content

Commit 5ebe34c

Browse files
committed
added custom domain
1 parent 78084c8 commit 5ebe34c

11 files changed

Lines changed: 143 additions & 28 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Please visit [Twilio Docs](https://www.twilio.com/docs/flex/developer/plugins) f
3636
* [Plugins API](https://www.twilio.com/docs/flex/developer/plugins/api)
3737
* [Troubleshooting and FAQ](faq.md)
3838

39-
> **Important:** Starting with the latest version, local development uses `flex.local.com` instead of `localhost`. Please see the [FAQ](faq.md) for setup instructions.
39+
> **Note:** When using SSO v2 (OAuth flow), you may need to configure a custom domain for local development using the `--domain` flag to avoid authentication issues. See the [FAQ](faq.md) for setup instructions.
4040
4141
## Changelog
4242

faq.md

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,60 @@
11
# Troubleshooting & FAQ
22

3-
#### Local Development URLs Changed from localhost to flex.local.com
3+
#### Configuring Custom Domain for Local Development
44

5-
Starting with this version, local plugin development uses `flex.local.com` instead of `localhost` to avoid login-related issues. You need to configure your local hosts file to point `flex.local.com` to `127.0.0.1`.
5+
You can configure a custom domain for local plugin development using the `--domain` flag. This is particularly important when using SSO v2 (OAuth flow), as `localhost` can cause authentication issues during login due to OAuth redirect URI restrictions.
66

7-
**On macOS/Linux:**
8-
1. Open Terminal
9-
2. Run: `sudo vim /etc/hosts` (or use your preferred text editor)
10-
3. Add this line: `127.0.0.1 flex.local.com`
11-
4. Save the file
7+
**Why use a custom domain?**
8+
- **SSO v2 OAuth Flow**: If you're using Twilio Flex with SSO v2 (OAuth authentication flow), `localhost` URLs may not be accepted as valid redirect URIs, causing login authentication failures
9+
- **CORS Issues**: Some authentication systems restrict `localhost` for security reasons
10+
- **Development Consistency**: Using a custom domain provides a more production-like development environment
1211

13-
**On Windows:**
14-
1. Open Notepad as Administrator
15-
2. Open the file: `C:\Windows\System32\drivers\etc\hosts`
16-
3. Add this line: `127.0.0.1 flex.local.com`
17-
4. Save the file
12+
**Usage:**
13+
```bash
14+
twilio flex:plugins:start --domain flex.local.com
15+
```
16+
17+
**Setting up the custom domain:**
18+
1. **macOS/Linux**: Add the domain to `/etc/hosts`
19+
```bash
20+
sudo echo "127.0.0.1 flex.local.com" >> /etc/hosts
21+
```
22+
23+
2. **Windows**: Add the domain to `C:\Windows\System32\drivers\etc\hosts`
24+
```
25+
127.0.0.1 flex.local.com
26+
```
27+
28+
**Available Options:**
29+
- Without `--domain`: Uses `localhost` (default behavior, may cause issues with SSO v2)
30+
- With `--domain flex.local.com`: Uses `flex.local.com`
31+
- With `--domain my-custom.dev`: Uses any custom domain you prefer
1832

19-
After making this change, your local plugin development server will be accessible at `http://flex.local.com:3000` instead of `http://localhost:3000`.
33+
**When is this required?**
34+
- **SSO v2 Users**: If your Twilio Flex instance uses SSO v2 with OAuth authentication, you will likely encounter login failures when using `localhost`
35+
- **CORS-restricted environments**: Some authentication providers block `localhost` requests
36+
- **Custom OAuth configurations**: If your OAuth provider has strict redirect URI policies
2037

21-
**Quick Setup Script (macOS/Linux only):**
22-
You can also run the provided script to automatically configure your hosts file:
38+
After configuring your hosts file, your local plugin development server will be accessible at the specified domain (e.g., `http://flex.local.com:3000`).
39+
40+
#### SSO v2 OAuth Authentication Issues with localhost
41+
42+
If you're experiencing login failures during local development, especially with error messages related to OAuth redirects or authentication, this is likely due to SSO v2 OAuth flow restrictions with `localhost` URLs.
43+
44+
**Common Error Symptoms:**
45+
- Login redirects fail or loop infinitely
46+
- OAuth callback errors mentioning invalid redirect URI
47+
- Authentication timeouts during local development
48+
- CORS errors related to authentication endpoints
49+
50+
**Solution:**
51+
Use the `--domain` flag with a custom domain:
2352
```bash
24-
./scripts/setup-hosts.sh
53+
twilio flex:plugins:start --domain flex.local.com
2554
```
2655

56+
Make sure to add the domain to your hosts file as described above. This resolves the OAuth redirect URI validation issues that occur with `localhost`.
57+
2758
#### npm install fails on NPM 7
2859

2960
If you are using `npm 7` (you can find out the version by typing `npm -v` in your terminal), then you may get the following error when you run `npm install`:

packages/flex-dev-utils/src/__tests__/urls.test.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,26 @@ describe('urls', () => {
3737

3838
beforeEach(() => {
3939
process.env = { ...OLD_ENV };
40+
delete process.env.DOMAIN; // Ensure clean state
4041
});
4142

42-
it('should return http', () => {
43+
it('should return http with localhost (default)', () => {
44+
const ip = jest.spyOn(address, 'ip').mockReturnValue('192.0.0.0');
45+
const result = urls.getLocalAndNetworkUrls(1234);
46+
47+
expect(result.local.host).toEqual('0.0.0.0');
48+
expect(result.local.port).toEqual(1234);
49+
expect(result.local.url).toEqual('http://localhost:1234/');
50+
51+
expect(result.network.host).toEqual('192.0.0.0');
52+
expect(result.network.port).toEqual(1234);
53+
expect(result.network.url).toEqual('http://192.0.0.0:1234/');
54+
55+
ip.mockRestore();
56+
});
57+
58+
it('should return http with custom domain', () => {
59+
process.env.DOMAIN = 'flex.local.com';
4360
const ip = jest.spyOn(address, 'ip').mockReturnValue('192.0.0.0');
4461
const result = urls.getLocalAndNetworkUrls(1234);
4562

@@ -54,14 +71,31 @@ describe('urls', () => {
5471
ip.mockRestore();
5572
});
5673

57-
it('should return https', () => {
74+
it('should return https with localhost (default)', () => {
75+
process.env.HTTPS = 'true';
76+
const ip = jest.spyOn(address, 'ip').mockReturnValue('192.0.0.0');
77+
const result = urls.getLocalAndNetworkUrls(1234);
78+
79+
expect(result.local.host).toEqual('0.0.0.0');
80+
expect(result.local.port).toEqual(1234);
81+
expect(result.local.url).toEqual('https://localhost:1234/');
82+
83+
expect(result.network.host).toEqual('192.0.0.0');
84+
expect(result.network.port).toEqual(1234);
85+
expect(result.network.url).toEqual('https://192.0.0.0:1234/');
86+
87+
ip.mockRestore();
88+
});
89+
90+
it('should return https with custom domain', () => {
5891
process.env.HTTPS = 'true';
92+
process.env.DOMAIN = 'secure.local.dev';
5993
const ip = jest.spyOn(address, 'ip').mockReturnValue('192.0.0.0');
6094
const result = urls.getLocalAndNetworkUrls(1234);
6195

6296
expect(result.local.host).toEqual('0.0.0.0');
6397
expect(result.local.port).toEqual(1234);
64-
expect(result.local.url).toEqual('https://flex.local.com:1234/');
98+
expect(result.local.url).toEqual('https://secure.local.dev:1234/');
6599

66100
expect(result.network.host).toEqual('192.0.0.0');
67101
expect(result.network.port).toEqual(1234);

packages/flex-dev-utils/src/urls.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,21 @@ export const findPort = async (startPort: number = 3000): Promise<number> => {
7777
/**
7878
* Returns the local and network urls
7979
* @param port the port the server is running on
80-
* @note Changed from localhost to flex.local.com to avoid login-related issues
80+
* @note Uses configurable domain instead of localhost to avoid SSO v2 OAuth authentication issues
8181
*/
8282
export const getLocalAndNetworkUrls = (port: number): InternalServiceUrls => {
8383
const protocol = env.isHTTPS() ? 'https' : 'http';
84+
const customDomain = env.getDomain();
85+
86+
let localHostname = 'localhost';
87+
if (customDomain) {
88+
localHostname = customDomain;
89+
}
8490

8591
const localUrl = url.format({
8692
protocol,
8793
port,
88-
hostname: 'flex.local.com',
94+
hostname: localHostname,
8995
pathname: '/',
9096
});
9197
const networkUrl = url.format({
@@ -99,7 +105,7 @@ export const getLocalAndNetworkUrls = (port: number): InternalServiceUrls => {
99105
local: {
100106
url: localUrl,
101107
port,
102-
host: '0.0.0.0',
108+
host: '0.0.0.0', // Keep binding host as 0.0.0.0 for proper network access
103109
},
104110
network: {
105111
url: networkUrl,

packages/flex-plugin-webpack/src/devServer/pluginServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const _getLocalPlugin = (name: string): FlexConfigurationPlugin | undefin
5555
// eslint-disable-next-line import/no-unused-modules
5656
export const _getLocalPlugins = (port: Port, names: string[]): Plugin[] => {
5757
const protocol = `http${env.isHTTPS() ? 's' : ''}://`;
58+
const domain = env.getDomain() || 'localhost';
5859

5960
return names.map((name) => {
6061
const match = _getLocalPlugin(name);
@@ -63,7 +64,7 @@ export const _getLocalPlugins = (port: Port, names: string[]): Plugin[] => {
6364
return {
6465
phase: 3,
6566
name,
66-
src: `${protocol}localhost:${port}/plugins/${name}.js`,
67+
src: `${protocol}${domain}:${port}/plugins/${name}.js`,
6768
};
6869
}
6970

packages/flex-plugin-webpack/src/webpack/webpack.dev.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ import { WebpackType } from '..';
1212
// eslint-disable-next-line import/no-unused-modules
1313
export const _getBase = (): Configuration => {
1414
const { local } = getLocalAndNetworkUrls(env.getPort());
15+
const customDomain = env.getDomain();
1516

1617
return {
1718
compress: true,
1819
static: {},
1920
client: {
2021
logging: 'none',
2122
webSocketURL: {
22-
hostname: local.host,
23+
hostname: customDomain || local.host,
2324
pathname: local.url,
2425
port: env.getPort(),
2526
},
2627
},
27-
host: env.getHost(),
28+
host: env.getHost() || '0.0.0.0', // Keep binding to 0.0.0.0 for network access
2829
port: env.getPort(),
2930
};
3031
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/* eslint-disable import/no-unused-modules */
22

3-
export { default, default as env, Environment, Lifecycle, Region } from './lib/env';
3+
export { default, default as env, Environment, Lifecycle, Region, setDomain, getDomain, hasDomain } from './lib/env';

packages/flex-plugins-utils-env/src/lib/__tests__/env.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,33 @@ describe('env', () => {
325325
});
326326
});
327327

328+
describe('domain', () => {
329+
it('should return domain', () => {
330+
process.env.DOMAIN = 'flex.local.com';
331+
expect(env.getDomain()).toEqual('flex.local.com');
332+
});
333+
334+
it('getDomain should return nothing', () => {
335+
expect(env.getDomain()).toEqual(undefined);
336+
});
337+
338+
it('should set domain', () => {
339+
expect(env.getDomain()).toEqual(undefined);
340+
env.setDomain('my-custom.domain');
341+
expect(env.getDomain()).toEqual('my-custom.domain');
342+
});
343+
344+
it('hasDomain should return true when domain is set', () => {
345+
env.setDomain('test.domain');
346+
expect(env.hasDomain()).toBe(true);
347+
});
348+
349+
it('hasDomain should return false when domain is not set', () => {
350+
delete process.env.DOMAIN;
351+
expect(env.hasDomain()).toBe(false);
352+
});
353+
});
354+
328355
describe('port', () => {
329356
it('should return port', () => {
330357
process.env.PORT = '1234';

packages/flex-plugins-utils-env/src/lib/env.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export const getAccountSid = (): string | undefined => getProcessEnv('TWILIO_ACC
8686
export const getAuthToken = (): string | undefined => getProcessEnv('TWILIO_AUTH_TOKEN');
8787
export const hasHost = (): boolean => isDefined(getProcessEnv('HOST'));
8888
export const getHost = (): string | undefined => getProcessEnv('HOST');
89+
export const hasDomain = (): boolean => isDefined(getProcessEnv('DOMAIN'));
90+
export const getDomain = (): string | undefined => getProcessEnv('DOMAIN');
91+
export const setDomain = (domain: string): void => setProcessEnv('DOMAIN', domain);
8992
export const setHost = (host: string): void => setProcessEnv('HOST', host);
9093
export const hasPort = (): boolean => isDefined(getProcessEnv('PORT'));
9194
export const getPort = (): number => Number(getProcessEnv('PORT'));
@@ -257,6 +260,9 @@ export default {
257260
hasHost,
258261
getHost,
259262
setHost,
263+
hasDomain,
264+
getDomain,
265+
setDomain,
260266
hasPort,
261267
getPort,
262268
setPort,

packages/plugin-flex/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
105105
"flags": {
106106
"name": "The name of the plugin you would like to run. You can provide multiple to run them all concurrently. You can include specific active remote plugins using \"--name 'plugin-name@remote'\" or \"--name 'plugin-name@0.0.0'\" for a specific remote version.",
107107
"includeRemote": "Use this flag to include all remote plugins in your build.",
108-
"port": "The port to start your local development server on."
108+
"port": "The port to start your local development server on.",
109+
"domain": "The domain to start your local development server on. Required when using SSO v2 OAuth flow to avoid authentication issues."
109110
}
110111
},
111112
"flex:plugins:deploy": {

0 commit comments

Comments
 (0)