146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
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;
|
|
}
|