Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
234 lines
5.0 KiB
JavaScript
234 lines
5.0 KiB
JavaScript
"use strict";
|
|
const usm = require("./url-state-machine");
|
|
const urlencoded = require("./urlencoded");
|
|
const URLSearchParams = require("./URLSearchParams");
|
|
|
|
exports.implementation = class URLImpl {
|
|
// Unlike the spec, we duplicate some code between the constructor and canParse, because we want to give useful error
|
|
// messages in the constructor that distinguish between the different causes of failure.
|
|
constructor(globalObject, [url, base]) {
|
|
let parsedBase = null;
|
|
if (base !== undefined) {
|
|
parsedBase = usm.basicURLParse(base);
|
|
if (parsedBase === null) {
|
|
throw new TypeError(`Invalid base URL: ${base}`);
|
|
}
|
|
}
|
|
|
|
const parsedURL = usm.basicURLParse(url, { baseURL: parsedBase });
|
|
if (parsedURL === null) {
|
|
throw new TypeError(`Invalid URL: ${url}`);
|
|
}
|
|
|
|
const query = parsedURL.query !== null ? parsedURL.query : "";
|
|
|
|
this._url = parsedURL;
|
|
|
|
// We cannot invoke the "new URLSearchParams object" algorithm without going through the constructor, which strips
|
|
// question mark by default. Therefore the doNotStripQMark hack is used.
|
|
this._query = URLSearchParams.createImpl(globalObject, [query], { doNotStripQMark: true });
|
|
this._query._url = this;
|
|
}
|
|
|
|
static parse(globalObject, input, base) {
|
|
try {
|
|
return new URLImpl(globalObject, [input, base]);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static canParse(url, base) {
|
|
let parsedBase = null;
|
|
if (base !== undefined) {
|
|
parsedBase = usm.basicURLParse(base);
|
|
if (parsedBase === null) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const parsedURL = usm.basicURLParse(url, { baseURL: parsedBase });
|
|
if (parsedURL === null) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
get href() {
|
|
return usm.serializeURL(this._url);
|
|
}
|
|
|
|
set href(v) {
|
|
const parsedURL = usm.basicURLParse(v);
|
|
if (parsedURL === null) {
|
|
throw new TypeError(`Invalid URL: ${v}`);
|
|
}
|
|
|
|
this._url = parsedURL;
|
|
|
|
this._query._list.splice(0);
|
|
const { query } = parsedURL;
|
|
if (query !== null) {
|
|
this._query._list = urlencoded.parseUrlencodedString(query);
|
|
}
|
|
}
|
|
|
|
get origin() {
|
|
return usm.serializeURLOrigin(this._url);
|
|
}
|
|
|
|
get protocol() {
|
|
return `${this._url.scheme}:`;
|
|
}
|
|
|
|
set protocol(v) {
|
|
usm.basicURLParse(`${v}:`, { url: this._url, stateOverride: "scheme start" });
|
|
}
|
|
|
|
get username() {
|
|
return this._url.username;
|
|
}
|
|
|
|
set username(v) {
|
|
if (usm.cannotHaveAUsernamePasswordPort(this._url)) {
|
|
return;
|
|
}
|
|
|
|
usm.setTheUsername(this._url, v);
|
|
}
|
|
|
|
get password() {
|
|
return this._url.password;
|
|
}
|
|
|
|
set password(v) {
|
|
if (usm.cannotHaveAUsernamePasswordPort(this._url)) {
|
|
return;
|
|
}
|
|
|
|
usm.setThePassword(this._url, v);
|
|
}
|
|
|
|
get host() {
|
|
const url = this._url;
|
|
|
|
if (url.host === null) {
|
|
return "";
|
|
}
|
|
|
|
if (url.port === null) {
|
|
return usm.serializeHost(url.host);
|
|
}
|
|
|
|
return `${usm.serializeHost(url.host)}:${usm.serializeInteger(url.port)}`;
|
|
}
|
|
|
|
set host(v) {
|
|
if (usm.hasAnOpaquePath(this._url)) {
|
|
return;
|
|
}
|
|
|
|
usm.basicURLParse(v, { url: this._url, stateOverride: "host" });
|
|
}
|
|
|
|
get hostname() {
|
|
if (this._url.host === null) {
|
|
return "";
|
|
}
|
|
|
|
return usm.serializeHost(this._url.host);
|
|
}
|
|
|
|
set hostname(v) {
|
|
if (usm.hasAnOpaquePath(this._url)) {
|
|
return;
|
|
}
|
|
|
|
usm.basicURLParse(v, { url: this._url, stateOverride: "hostname" });
|
|
}
|
|
|
|
get port() {
|
|
if (this._url.port === null) {
|
|
return "";
|
|
}
|
|
|
|
return usm.serializeInteger(this._url.port);
|
|
}
|
|
|
|
set port(v) {
|
|
if (usm.cannotHaveAUsernamePasswordPort(this._url)) {
|
|
return;
|
|
}
|
|
|
|
if (v === "") {
|
|
this._url.port = null;
|
|
} else {
|
|
usm.basicURLParse(v, { url: this._url, stateOverride: "port" });
|
|
}
|
|
}
|
|
|
|
get pathname() {
|
|
return usm.serializePath(this._url);
|
|
}
|
|
|
|
set pathname(v) {
|
|
if (usm.hasAnOpaquePath(this._url)) {
|
|
return;
|
|
}
|
|
|
|
this._url.path = [];
|
|
usm.basicURLParse(v, { url: this._url, stateOverride: "path start" });
|
|
}
|
|
|
|
get search() {
|
|
if (this._url.query === null || this._url.query === "") {
|
|
return "";
|
|
}
|
|
|
|
return `?${this._url.query}`;
|
|
}
|
|
|
|
set search(v) {
|
|
const url = this._url;
|
|
|
|
if (v === "") {
|
|
url.query = null;
|
|
this._query._list = [];
|
|
return;
|
|
}
|
|
|
|
const input = v[0] === "?" ? v.substring(1) : v;
|
|
url.query = "";
|
|
usm.basicURLParse(input, { url, stateOverride: "query" });
|
|
this._query._list = urlencoded.parseUrlencodedString(input);
|
|
}
|
|
|
|
get searchParams() {
|
|
return this._query;
|
|
}
|
|
|
|
get hash() {
|
|
if (this._url.fragment === null || this._url.fragment === "") {
|
|
return "";
|
|
}
|
|
|
|
return `#${this._url.fragment}`;
|
|
}
|
|
|
|
set hash(v) {
|
|
if (v === "") {
|
|
this._url.fragment = null;
|
|
return;
|
|
}
|
|
|
|
const input = v[0] === "#" ? v.substring(1) : v;
|
|
this._url.fragment = "";
|
|
usm.basicURLParse(input, { url: this._url, stateOverride: "fragment" });
|
|
}
|
|
|
|
toJSON() {
|
|
return this.href;
|
|
}
|
|
};
|