app/@overflow/core/parse-app-url.ts

146 lines
3.6 KiB
TypeScript
Raw Normal View History

2018-08-16 10:49:37 +00:00
import * as URL from 'url';
export interface IOAuthAction {
readonly name: 'oauth';
readonly code: string;
}
export interface IOpenRepositoryFromURLAction {
readonly name: 'open-repository-from-url';
/** the remote repository location associated with the "Open in Desktop" action */
readonly url: string;
/** the optional branch name which should be checked out. use the default branch otherwise. */
readonly branch: string | null;
/** the pull request number, if pull request originates from a fork of the repository */
readonly pr: string | null;
/** the file to open after cloning the repository */
readonly filepath: string | null;
}
export interface IOpenRepositoryFromPathAction {
readonly name: 'open-repository-from-path';
/** The local path to open. */
readonly path: string;
}
export interface IUnknownAction {
readonly name: 'unknown';
readonly url: string;
}
export type URLActionType =
| IOAuthAction
| IOpenRepositoryFromURLAction
| IOpenRepositoryFromPathAction
| IUnknownAction;
// eslint-disable-next-line typescript/interface-name-prefix
interface ParsedUrlQueryWithUndefined {
// `undefined` is added here to ensure we handle the missing querystring key
// See https://github.com/Microsoft/TypeScript/issues/13778 for discussion about
// why this isn't supported natively in TypeScript
[key: string]: string | string[] | undefined;
}
/**
* Parse the URL to find a given key in the querystring text.
*
* @param url The source URL containing querystring key-value pairs
* @param key The key to look for in the querystring
*/
function getQueryStringValue(
query: ParsedUrlQueryWithUndefined,
key: string
): string | null {
const value = query[key];
if (value == null) {
return null;
}
if (Array.isArray(value)) {
return value[0];
}
return value;
}
export function parseAppURL(url: string): URLActionType {
const parsedURL = URL.parse(url, true);
const hostname = parsedURL.hostname;
const unknown: IUnknownAction = { name: 'unknown', url };
if (!hostname) {
return unknown;
}
const query = parsedURL.query;
const actionName = hostname.toLowerCase();
if (actionName === 'oauth') {
const code = getQueryStringValue(query, 'code');
if (code != null) {
return { name: 'oauth', code };
} else {
return unknown;
}
}
// we require something resembling a URL first
// - bail out if it's not defined
// - bail out if you only have `/`
const pathName = parsedURL.pathname;
if (!pathName || pathName.length <= 1) {
return unknown;
}
// Trim the trailing / from the URL
const parsedPath = pathName.substr(1);
if (actionName === 'openrepo') {
const probablyAURL = parsedPath;
// suffix the remote URL with `.git`, for backwards compatibility
const _url = `${probablyAURL}.git`;
const pr = getQueryStringValue(query, 'pr');
const branch = getQueryStringValue(query, 'branch');
const filepath = getQueryStringValue(query, 'filepath');
if (pr != null) {
if (!/^\d+$/.test(pr)) {
return unknown;
}
// we also expect the branch for a forked PR to be a given ref format
if (branch != null && !/^pr\/\d+$/.test(branch)) {
return unknown;
}
}
// if (branch != null && testForInvalidChars(branch)) {
// return unknown;
// }
return {
name: 'open-repository-from-url',
url: _url,
branch,
pr,
filepath,
};
}
if (actionName === 'openlocalrepo') {
return {
name: 'open-repository-from-path',
path: decodeURIComponent(parsedPath),
};
}
return unknown;
}