-
Notifications
You must be signed in to change notification settings - Fork 5.5k
feat(gator-permissions): add Gator Permissions deep link #40995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1702c18
7776f4d
bdbfb72
f6cb520
33374f1
648ae27
3bea1d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import { | ||
| gatorPermissions, | ||
| GatorPermissionsQueryParams, | ||
| } from './gator-permissions'; | ||
| import { Destination } from './route'; | ||
|
|
||
| function assertPathDestination( | ||
| result: Destination, | ||
| ): asserts result is Extract<Destination, { path: string }> { | ||
| expect('path' in result).toBe(true); | ||
| } | ||
|
|
||
| describe('gatorPermissionsRoute', () => { | ||
| describe('handler with valid parameters', () => { | ||
| it('returns encoded path with valid type and site', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'token-transfer', | ||
| [GatorPermissionsQueryParams.Site]: 'https://example.com', | ||
| }); | ||
|
|
||
| const result = gatorPermissions.handler(params); | ||
|
|
||
| assertPathDestination(result); | ||
| expect(result.path).toBe( | ||
| '/gator-permissions/token-transfer/https%3A%2F%2Fexample.com', | ||
| ); | ||
| expect(result.query.toString()).toBe(''); | ||
| }); | ||
|
|
||
| it('handles http protocol in site parameter', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'token-transfer', | ||
| [GatorPermissionsQueryParams.Site]: 'http://example.com', | ||
| }); | ||
|
|
||
| const result = gatorPermissions.handler(params); | ||
|
|
||
| assertPathDestination(result); | ||
| expect(result.path).toBe( | ||
| '/gator-permissions/token-transfer/http%3A%2F%2Fexample.com', | ||
| ); | ||
| }); | ||
|
|
||
| it('handles site with port number', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'token-transfer', | ||
| [GatorPermissionsQueryParams.Site]: 'https://example.com:8080', | ||
| }); | ||
|
|
||
| const result = gatorPermissions.handler(params); | ||
|
|
||
| assertPathDestination(result); | ||
| expect(result.path).toBe( | ||
| '/gator-permissions/token-transfer/https%3A%2F%2Fexample.com%3A8080', | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('handler with missing parameters', () => { | ||
| it('throws when type parameter is missing', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Site]: 'https://example.com', | ||
| }); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Missing type parameter', | ||
| ); | ||
| }); | ||
|
|
||
| it('throws when site parameter is missing', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'token-transfer', | ||
| }); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Missing site parameter', | ||
| ); | ||
| }); | ||
|
|
||
| it('throws when both type and site are missing', () => { | ||
| const params = new URLSearchParams(); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Missing type parameter', | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('handler with invalid type parameter', () => { | ||
| it('throws when type is not "token-transfer"', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'invalid-type', | ||
| [GatorPermissionsQueryParams.Site]: 'https://example.com', | ||
| }); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Invalid type parameter', | ||
| ); | ||
| }); | ||
|
|
||
| it('throws for empty type parameter', () => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: '', | ||
| [GatorPermissionsQueryParams.Site]: 'https://example.com', | ||
| }); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Missing type parameter', | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('handler with invalid site parameter', () => { | ||
| type InvalidSiteTestCase = { | ||
| site: string; | ||
| description: string; | ||
| }; | ||
|
|
||
| const invalidSiteCases: InvalidSiteTestCase[] = [ | ||
| { site: 'not-a-url', description: 'invalid URL format' }, | ||
| { site: 'ftp://example.com', description: 'unsupported protocol (ftp)' }, | ||
| { site: 'data:text/html,<h1>test</h1>', description: 'data URI' }, | ||
| // eslint-disable-next-line no-script-url | ||
| { site: 'javascript:alert(1)', description: 'javascript protocol' }, | ||
| { site: 'https://example.com:notaport', description: 'invalid port' }, | ||
| { | ||
| site: 'https://example.com/path', | ||
| description: 'URL with path instead of origin', | ||
| }, | ||
| { | ||
| site: 'https://example.com?query=1', | ||
| description: 'URL with query params instead of origin', | ||
| }, | ||
| { | ||
| site: 'https://example.com#hash', | ||
| description: 'URL with hash instead of origin', | ||
| }, | ||
| ]; | ||
|
|
||
| // @ts-expect-error This function is missing from the Mocha type definitions | ||
| it.each(invalidSiteCases)( | ||
| 'throws for invalid site: $description', | ||
| ({ site }: InvalidSiteTestCase) => { | ||
| const params = new URLSearchParams({ | ||
| [GatorPermissionsQueryParams.Type]: 'token-transfer', | ||
| [GatorPermissionsQueryParams.Site]: site, | ||
| }); | ||
|
|
||
| expect(() => gatorPermissions.handler(params)).toThrow( | ||
| 'Invalid site parameter', | ||
| ); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe('getTitle', () => { | ||
| it('returns the correct title key', () => { | ||
| const params = new URLSearchParams(); | ||
| const title = gatorPermissions.getTitle(params); | ||
|
|
||
| expect(title).toBe('deepLink_theGatorPermissionsPage'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('pathname', () => { | ||
| it('has the correct pathname', () => { | ||
| expect(gatorPermissions.pathname).toBe('/gator-permissions'); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { Route } from './route'; | ||
|
|
||
| export enum GatorPermissionsQueryParams { | ||
| Type = 'type', | ||
| Site = 'site', | ||
| } | ||
|
|
||
| function isValidOrigin(originString: string): boolean { | ||
| try { | ||
| const url = new URL(originString); | ||
| // The URL constructor ensures proper URL formatting. | ||
| if (url.protocol !== 'http:' && url.protocol !== 'https:') { | ||
| return false; | ||
| } | ||
| return originString === url.origin; | ||
| } catch (e) { | ||
| // If new URL() throws a TypeError, the string is not a valid URL format | ||
| return false; | ||
| } | ||
|
Check warning on line 19 in shared/lib/deep-links/routes/gator-permissions.ts
|
||
| } | ||
|
|
||
| export const gatorPermissions = new Route({ | ||
| pathname: '/gator-permissions', | ||
| getTitle: (_: URLSearchParams) => 'deepLink_theGatorPermissionsPage', | ||
| handler: function handler(params: URLSearchParams) { | ||
| const type = params.get(GatorPermissionsQueryParams.Type); | ||
| const site = params.get(GatorPermissionsQueryParams.Site); | ||
| const query = new URLSearchParams(); | ||
| if (!type) { | ||
| throw new Error('Missing type parameter'); | ||
| } | ||
|
|
||
| if (type !== 'token-transfer') { | ||
| throw new Error('Invalid type parameter'); | ||
| } | ||
|
|
||
| if (!site) { | ||
| throw new Error('Missing site parameter'); | ||
| } | ||
|
|
||
| if (!isValidOrigin(site)) { | ||
| throw new Error('Invalid site parameter'); | ||
| } | ||
|
|
||
| const encodedSite = encodeURIComponent(site); | ||
| return { | ||
| path: `/gator-permissions/${type}/${encodedSite}`, | ||
| query, | ||
| }; | ||
| }, | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely only a nit: should we lean into the public naming
/advanced-permissions/?