Skip to content

Commit

Permalink
Merge pull request #411 from Kitware/drop-whatwg-url
Browse files Browse the repository at this point in the history
Drop whatwg-url
  • Loading branch information
floryst authored Nov 13, 2023
2 parents 59a3cea + 6b5fca8 commit 58eb163
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 50 deletions.
52 changes: 19 additions & 33 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"vue": "^3.3.4",
"vue-toastification": "^2.0.0-rc.5",
"vuetify": "^3.1.14",
"whatwg-url": "^12.0.1",
"zod": "^3.22.3"
},
"devDependencies": {
Expand All @@ -64,7 +63,6 @@
"@types/mocha": "^9.1.1",
"@types/sinon": "^10.0.11",
"@types/sinon-chai": "^3.2.8",
"@types/whatwg-url": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "^4.2.3",
Expand Down
6 changes: 4 additions & 2 deletions src/components/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ import {
import { storeToRefs } from 'pinia';
import { UrlParams } from '@vueuse/core';
import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract';
import { URL } from 'whatwg-url';
import { useDisplay } from 'vuetify';
import { basename } from '@/src/utils/path';
Expand All @@ -231,6 +230,7 @@ import type { Vector3 } from '@kitware/vtk.js/types';
import { ViewTypes } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants';
import WelcomePage from '@/src/components/WelcomePage.vue';
import { useDICOMStore } from '@/src/store/datasets-dicom';
import { parseUrl } from '@/src/utils/url';
import ToolButton from './ToolButton.vue';
import LayoutGrid from './LayoutGrid.vue';
import ModulePanel from './ModulePanel.vue';
Expand Down Expand Up @@ -317,7 +317,9 @@ async function loadRemoteFilesFromURLParams(
const sources = urls.map((url, idx) =>
uriToDataSource(
url,
names[idx] || basename(new URL(url, window.location.href).pathname) || url
names[idx] ||
basename(parseUrl(url, window.location.href).pathname) ||
url
)
);
Expand Down
4 changes: 2 additions & 2 deletions src/core/remote/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { nanoid } from 'nanoid';
import { Socket, io } from 'socket.io-client';
import { z } from 'zod';
import * as ChunkedParser from '@/src/core/remote/chunkedParser';
import { URL } from 'whatwg-url';
import { parseUrl } from '@/src/utils/url';

const CLIENT_ID_SIZE = 24;
const RPC_ID_SIZE = 24;
Expand Down Expand Up @@ -109,7 +109,7 @@ export interface RpcClientOptions {
}

function justHostUrl(url: string) {
const parts = new URL(url);
const parts = parseUrl(url);
parts.pathname = '';
return String(parts);
}
Expand Down
6 changes: 3 additions & 3 deletions src/io/amazonS3.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { parseUrl } from '@/src/utils/url';
import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';
import { URL } from 'whatwg-url';

/**
* Detects `s3://` uri.
* @param uri
* @returns
*/
export const isAmazonS3Uri = (uri: string) =>
new URL(uri, window.location.origin).protocol === 's3:';
parseUrl(uri, window.location.origin).protocol === 's3:';

export type ObjectAvailableCallback = (url: string, name: string) => void;

Expand Down Expand Up @@ -67,7 +67,7 @@ async function fetchObjectsWithPagination(
* @returns
*/
export const extractBucketAndPrefixFromS3Uri = (uri: string) => {
const { hostname: bucket, pathname } = new URL(uri);
const { hostname: bucket, pathname } = parseUrl(uri);
// drop the leading forward slash
const objectName = pathname.replace(/^\//, '');
return [bucket, objectName] as const;
Expand Down
8 changes: 4 additions & 4 deletions src/io/googleCloudStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { URL } from 'whatwg-url';
import { parseUrl } from '@/src/utils/url';
import { fetchJSON } from '../utils/fetch';

export interface GcsObject {
Expand All @@ -23,15 +23,15 @@ interface GcsObjectListResult {
* @returns
*/
export const isGoogleCloudStorageUri = (uri: string) =>
new URL(uri, window.location.origin).protocol === 'gs:';
parseUrl(uri, window.location.origin).protocol === 'gs:';

/**
* Extracts bucket and prefix from `gs://` URIs
* @param uri
* @returns
*/
export const extractBucketAndPrefixFromGsUri = (uri: string) => {
const { hostname: bucket, pathname } = new URL(uri);
const { hostname: bucket, pathname } = parseUrl(uri);
// drop the leading forward slash
const objectName = pathname.replace(/^\//, '');
return [bucket, objectName] as const;
Expand All @@ -50,7 +50,7 @@ async function fetchObjectsWithPagination(
const objects: GcsObject[] = [];

const paginate = async (nextToken?: string) => {
const url = new URL(getObjectEndpoint(bucket));
const url = parseUrl(getObjectEndpoint(bucket));
url.searchParams.append('prefix', prefix);
url.searchParams.append('maxResults', '1000');
if (nextToken) {
Expand Down
15 changes: 15 additions & 0 deletions src/utils/__tests__/url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { parseUrl } from '@/src/utils/url';
import { describe, expect, it } from 'vitest';

describe('utils/url', () => {
describe('parseUrl', () => {
it('should parse a URL', () => {
expect(parseUrl('https://example.com/path').pathname).to.equal('/path');
expect(parseUrl('gs://bucket/').protocol).to.equal('gs:');
expect(parseUrl('gs://bucket/path/object').pathname).to.equal(
'/path/object'
);
expect(parseUrl('path/object', 'gs://bucket').protocol).to.equal('gs:');
});
});
});
4 changes: 2 additions & 2 deletions src/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parseUrl } from '@/src/utils/url';
import { Awaitable } from '@vueuse/core';
import { URL } from 'whatwg-url';

/**
* Percent is in [0, 1]. If it's Infinity, then the progress is indeterminate.
Expand All @@ -25,7 +25,7 @@ interface URLHandler {
*/
const HTTPHandler: URLHandler = {
testURL: (url) => {
const { protocol } = new URL(url, window.location.href);
const { protocol } = parseUrl(url, window.location.href);
return protocol === 'http:' || protocol === 'https:';
},
fetchURL: async (url, options = {}) => {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { URL } from 'whatwg-url';
import { z } from 'zod';
import { TypedArray } from 'itk-wasm';
import { parseUrl } from '@/src/utils/url';
import { EPSILON } from '../constants';
import { Maybe } from '../types';

Expand Down Expand Up @@ -203,7 +203,7 @@ export function wrapInArray<T>(maybeArray: T | T[]): T[] {
* Extracts the basename from a URL.
*/
export function getURLBasename(url: string) {
return new URL(url, window.location.href).pathname.split('/').at(-1) ?? '';
return parseUrl(url, window.location.href).pathname.split('/').at(-1) ?? '';
}

// from https://stackoverflow.com/a/18650828
Expand Down
67 changes: 67 additions & 0 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const SPECIAL_SCHEME_RE = /^(https?|ftp|file|wss?):$/;

/**
* Represents a URL object with relaxed writing semantics.
*
* The protocol field is now unconditionally writeable,
* regardless of whether the protocol is special.
*/
export class WriteableURL extends URL {
_proto: string;

constructor(url: string, base?: string) {
super(url, base);
this._proto = this.protocol;
}

set protocol(proto: string) {
this._proto = proto;
}

get protocol() {
return this._proto;
}

private replaceProtocol(url: string) {
const re = new RegExp(`^${super.protocol}`);
return url.replace(re, this._proto);
}

get origin() {
return this.replaceProtocol(super.origin);
}

get href() {
return this.replaceProtocol(super.href);
}

toString() {
return this.replaceProtocol(super.toString());
}
}

/**
* Parses a URL.
*
* Chrome, Firefox, and Safari parse URLS differently for
* non-special schemes. parseUrl's goal is to have uniform
* output across browsers for non-special schemes.
*
* @param url
* @param base
* @returns
*/
export function parseUrl(url: string, base?: string) {
let parsed = new URL(url, base);
if (SPECIAL_SCHEME_RE.test(parsed.protocol)) {
return parsed;
}

// replace the scheme with a special scheme to get uniform parsing.
const proto = parsed.protocol;
const re = new RegExp(`^${proto}`);
parsed = new WriteableURL(url.replace(re, 'ws:'), base?.replace(re, 'ws:'));
// This line doesn't work with the standard URL API.
parsed.protocol = proto;
return parsed;
}

0 comments on commit 58eb163

Please sign in to comment.