@spacefast/common 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/connect-targets.d.ts +133 -0
- package/dist/agents/connect-targets.js +224 -0
- package/dist/brand.d.ts +1 -0
- package/dist/brand.js +5 -0
- package/dist/config/domains.d.ts +12 -6
- package/dist/config/domains.js +30 -38
- package/dist/contracts/access.d.ts +825 -125
- package/dist/contracts/access.js +771 -148
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +28 -2
- package/dist/contracts/annotations.d.ts +2 -4
- package/dist/contracts/api-keys.d.ts +3 -4
- package/dist/contracts/api-keys.js +9 -6
- package/dist/contracts/archives.d.ts +8 -2
- package/dist/contracts/archives.js +11 -4
- package/dist/contracts/billing.d.ts +0 -9
- package/dist/contracts/builds.d.ts +25 -21
- package/dist/contracts/builds.js +12 -0
- package/dist/contracts/common.d.ts +2 -0
- package/dist/contracts/common.js +4 -0
- package/dist/contracts/continuation.d.ts +0 -3
- package/dist/contracts/countries.d.ts +2 -0
- package/dist/contracts/countries.js +261 -0
- package/dist/contracts/deployments.d.ts +34 -34
- package/dist/contracts/device-auth.d.ts +4 -11
- package/dist/contracts/device-auth.js +4 -0
- package/dist/contracts/docs.d.ts +119 -0
- package/dist/contracts/docs.js +51 -0
- package/dist/contracts/domains.d.ts +96 -54
- package/dist/contracts/domains.js +26 -4
- package/dist/contracts/enums.d.ts +15 -16
- package/dist/contracts/enums.js +25 -14
- package/dist/contracts/error-code-meta.d.ts +72 -0
- package/dist/contracts/error-code-meta.js +96 -10
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +14 -0
- package/dist/contracts/events.d.ts +1 -1
- package/dist/contracts/features.d.ts +76 -6
- package/dist/contracts/features.js +178 -13
- package/dist/contracts/git.d.ts +2 -8
- package/dist/contracts/ids.d.ts +5 -0
- package/dist/contracts/ids.js +5 -0
- package/dist/contracts/mcp.d.ts +8 -10
- package/dist/contracts/me.js +1 -1
- package/dist/contracts/oauth-resources.d.ts +19 -8
- package/dist/contracts/oauth-resources.js +44 -13
- package/dist/contracts/platform.d.ts +0 -102
- package/dist/contracts/platform.js +0 -50
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.d.ts +2 -0
- package/dist/contracts/quotas.js +8 -0
- package/dist/contracts/repository-connections.d.ts +6 -8
- package/dist/contracts/resources.d.ts +97 -13
- package/dist/contracts/resources.js +26 -2
- package/dist/contracts/routes.d.ts +0 -2
- package/dist/contracts/runtime-api.d.ts +361 -39
- package/dist/contracts/runtime-api.js +275 -6
- package/dist/contracts/sites.d.ts +19 -2
- package/dist/contracts/sites.js +21 -0
- package/dist/contracts/space-config.d.ts +201 -2
- package/dist/contracts/space-config.js +135 -5
- package/dist/contracts/spaces.d.ts +364 -193
- package/dist/contracts/spaces.js +40 -8
- package/dist/contracts/superadmin-emails.d.ts +4 -2
- package/dist/contracts/superadmin-emails.js +8 -0
- package/dist/contracts/superadmin-queues.d.ts +26 -26
- package/dist/contracts/superadmin-spaces.d.ts +170 -42
- package/dist/contracts/superadmin.d.ts +6 -93
- package/dist/contracts/superadmin.js +6 -14
- package/dist/contracts/tags.d.ts +10 -9
- package/dist/contracts/tags.js +2 -1
- package/dist/contracts/teams.d.ts +21 -22
- package/dist/contracts/teams.js +0 -1
- package/dist/contracts/theme-json.d.ts +30 -0
- package/dist/contracts/theme-json.js +48 -0
- package/dist/contracts/variables.d.ts +0 -2
- package/dist/contracts/variables.js +0 -5
- package/dist/contracts/webhooks.d.ts +2 -0
- package/dist/contracts/webhooks.js +1 -1
- package/dist/contracts/zero.d.ts +5 -10
- package/dist/docs/agent-prose.d.ts +43 -3
- package/dist/docs/agent-prose.js +146 -7
- package/dist/docs/agent-setup.js +45 -13
- package/dist/docs/agent-solutions.d.ts +5 -5
- package/dist/docs/agent-solutions.js +2 -2
- package/dist/docs/catalog.d.ts +650 -0
- package/dist/docs/catalog.js +758 -0
- package/dist/docs/error-docs.d.ts +0 -2
- package/dist/docs/error-docs.js +77 -35
- package/dist/docs/index-build.d.ts +17 -0
- package/dist/docs/index-build.js +24 -0
- package/dist/docs/search.d.ts +54 -0
- package/dist/docs/search.js +96 -0
- package/dist/docs/skill-distribution.d.ts +5 -3
- package/dist/docs/skill-distribution.js +8 -8
- package/dist/utils/access-match.d.ts +39 -0
- package/dist/utils/access-match.js +296 -0
- package/dist/utils/auth-redirect.d.ts +3 -1
- package/dist/utils/auth-redirect.js +21 -1
- package/dist/utils/build-settings.d.ts +6 -0
- package/dist/utils/build-settings.js +429 -46
- package/dist/utils/claim-token.d.ts +8 -0
- package/dist/utils/claim-token.js +23 -2
- package/dist/utils/content-type.d.ts +1 -0
- package/dist/utils/content-type.js +1 -1
- package/dist/utils/credential-policy.d.ts +54 -0
- package/dist/utils/{access-policy.js → credential-policy.js} +28 -2
- package/dist/utils/gate-theme.d.ts +61 -0
- package/dist/utils/gate-theme.js +217 -0
- package/dist/utils/git-repository.d.ts +0 -1
- package/dist/utils/git-repository.js +0 -3
- package/dist/utils/id-hints.d.ts +8 -0
- package/dist/utils/id-hints.js +61 -0
- package/dist/utils/local-space-state.d.ts +91 -0
- package/dist/utils/local-space-state.js +251 -0
- package/dist/utils/oauth-signed-query.d.ts +8 -0
- package/dist/utils/oauth-signed-query.js +26 -0
- package/dist/utils/publish-detection.d.ts +0 -8
- package/dist/utils/publish-detection.js +0 -93
- package/dist/utils/publish-policy.d.ts +6 -5
- package/dist/utils/publish-policy.js +71 -7
- package/dist/utils/query-keys.d.ts +15 -4
- package/dist/utils/query-keys.js +27 -4
- package/dist/utils/runtime-paths.d.ts +0 -1
- package/dist/utils/runtime-paths.js +0 -1
- package/dist/utils/runtime-upload-batch.d.ts +18 -0
- package/dist/utils/runtime-upload-batch.js +90 -0
- package/dist/utils/runtime-upload.d.ts +2 -0
- package/dist/utils/runtime-upload.js +5 -0
- package/dist/utils/space-config.d.ts +4 -0
- package/dist/utils/space-config.js +10 -1
- package/dist/utils/space-theme.d.ts +2 -0
- package/dist/utils/space-theme.js +9 -0
- package/dist/utils/static-runtime-policy.d.ts +0 -21
- package/dist/utils/static-runtime-policy.js +19 -103
- package/dist/utils/upload-session.d.ts +1 -1
- package/dist/utils/wpcom-auth-redirects.d.ts +32 -2
- package/dist/utils/wpcom-auth-redirects.js +26 -10
- package/dist/vocabulary.d.ts +130 -30
- package/dist/vocabulary.js +77 -36
- package/package.json +16 -1
- package/dist/contracts/account.d.ts +0 -28
- package/dist/contracts/account.js +0 -54
- package/dist/contracts/intercom.d.ts +0 -6
- package/dist/contracts/intercom.js +0 -5
- package/dist/contracts/telemetry.d.ts +0 -2
- package/dist/contracts/telemetry.js +0 -1
- package/dist/utils/access-policy.d.ts +0 -39
- package/dist/utils/error-display.d.ts +0 -7
- package/dist/utils/error-display.js +0 -42
package/dist/utils/query-keys.js
CHANGED
|
@@ -4,8 +4,11 @@ export const qk = {
|
|
|
4
4
|
accountSessions: () => ["account", "sessions"],
|
|
5
5
|
accountConnections: () => ["account", "connections"],
|
|
6
6
|
accountInvitations: () => ["account", "invitations"],
|
|
7
|
-
|
|
7
|
+
// Results are team-filtered, so the team lands in the key; calling with no
|
|
8
|
+
// teamId yields the bare prefix (personal scope, and invalidate-all sweeps).
|
|
9
|
+
apiTokens: (teamId) => teamId === undefined ? ["api-tokens"] : ["api-tokens", teamId],
|
|
8
10
|
agentAccounts: () => ["agent-accounts"],
|
|
11
|
+
myShelf: () => ["my-shelf"],
|
|
9
12
|
mcpDashboard: () => ["mcp", "dashboard"],
|
|
10
13
|
mcpApproval: (id) => ["mcp", "approval", id],
|
|
11
14
|
// The teams LIST gets its own leaf so invalidating it never cascades into
|
|
@@ -31,13 +34,24 @@ export const qk = {
|
|
|
31
34
|
spaceByRef: (teamRef, spaceRef) => ["spaces", "by-ref", teamRef, spaceRef],
|
|
32
35
|
spaceVersions: (id) => ["spaces", id, "versions"],
|
|
33
36
|
spaceCollaboration: (id) => ["spaces", id, "collaboration"],
|
|
37
|
+
spaceCollaborationEmbedConfig: (id) => ["spaces", id, "collaboration", "embed-config"],
|
|
34
38
|
spaceAnnotations: (id) => ["spaces", id, "annotations"],
|
|
35
39
|
spaceActivity: (id, limit) => ["spaces", id, "activity", limit],
|
|
36
40
|
spaceDeployments: (id) => ["spaces", id, "deployments"],
|
|
37
41
|
spaceTags: (id) => ["spaces", id, "tags"],
|
|
38
|
-
spaceTagVersion: (id, versionId) => ["spaces", id, "tags",
|
|
42
|
+
spaceTagVersion: (id, versionId) => ["spaces", id, "tags", versionId],
|
|
43
|
+
spaceTagDiff: (id, versionId) => ["spaces", id, "tags", versionId, "diff"],
|
|
44
|
+
spaceTagReleases: (id) => ["spaces", id, "tags", "releases"],
|
|
45
|
+
// The merged (system+team+space) read-only view; never a write base — see
|
|
46
|
+
// `fetchSpaceReleasedGraph` in apps/my/src/lib/space-queries.ts.
|
|
47
|
+
spaceTagsEffective: (id) => ["spaces", id, "tags", "effective"],
|
|
39
48
|
build: (id) => ["builds", id],
|
|
40
|
-
|
|
49
|
+
// Observers with different limits must not share a cache entry; calling with
|
|
50
|
+
// no limit yields the bare prefix for invalidating every limit at once.
|
|
51
|
+
buildLogs: (id, limit) => limit === undefined
|
|
52
|
+
? ["builds", id, "logs"]
|
|
53
|
+
: ["builds", id, "logs", limit],
|
|
54
|
+
spaceBuilds: (id) => ["spaces", id, "builds"],
|
|
41
55
|
spaceRepositoryConnection: (id, connectionType = "connected") => ["spaces", id, "repository-connection", connectionType],
|
|
42
56
|
repositoryInstallations: (spaceId) => ["repository-connections", "installations", spaceId],
|
|
43
57
|
repositoryInstallationRepositories: (installationId) => ["repository-connections", "installations", installationId, "repositories"],
|
|
@@ -47,7 +61,11 @@ export const qk = {
|
|
|
47
61
|
webhook: (webhookId) => ["webhooks", webhookId],
|
|
48
62
|
webhookDeliveries: (webhookId) => ["webhooks", webhookId, "deliveries"],
|
|
49
63
|
spaceVersion: (id, versionId) => ["spaces", id, "versions", versionId],
|
|
50
|
-
|
|
64
|
+
// Calling with no status yields the bare prefix, for invalidating every
|
|
65
|
+
// status-scoped annotations list at once.
|
|
66
|
+
spaceVersionAnnotations: (id, versionId, status) => status === undefined
|
|
67
|
+
? ["spaces", id, "versions", versionId, "annotations"]
|
|
68
|
+
: ["spaces", id, "versions", versionId, "annotations", status],
|
|
51
69
|
spaceVersionFiles: (id, versionId) => ["spaces", id, "versions", versionId, "files"],
|
|
52
70
|
spaceVersionFile: (id, versionId, path) => ["spaces", id, "versions", versionId, "files", path],
|
|
53
71
|
spaceVersionFileDirectUrl: (id, versionId, path) => ["spaces", id, "versions", versionId, "files", path, "direct-url"],
|
|
@@ -68,6 +86,11 @@ export const qk = {
|
|
|
68
86
|
spaceCertificates: (id) => ["spaces", id, "runtime", "certificates"],
|
|
69
87
|
spaceLogs: (id, kind) => ["spaces", id, "logs", kind],
|
|
70
88
|
spaceAnalytics: (id, window) => ["spaces", id, "analytics", window],
|
|
89
|
+
spaceFirewall: (id) => ["spaces", id, "access", "firewall"],
|
|
90
|
+
spaceAccessViews: (id, since) => ["spaces", id, "access", "views", since ?? ""],
|
|
91
|
+
teamServiceTokens: (teamId) => ["teams", teamId, "access", "service-tokens"],
|
|
92
|
+
teamAccessDefaults: (teamId) => ["teams", teamId, "access", "defaults"],
|
|
93
|
+
teamAccessConnections: (teamId) => ["teams", teamId, "access", "connections"],
|
|
71
94
|
domain: (teamId, ref) => ["teams", teamId, "domains", ref],
|
|
72
95
|
domainDiagnostics: (teamId, domainId) => ["teams", teamId, "domains", domainId, "diagnostics"],
|
|
73
96
|
domainDnsCapabilities: (domainId) => ["domains", domainId, "dns", "capabilities"],
|
|
@@ -2,6 +2,5 @@ export declare const SPACEFAST_RUNTIME_NAMESPACE: "/__spacefast";
|
|
|
2
2
|
export declare const RUNTIME_MANAGEMENT_API_PATH: "/__spacefast/api.php";
|
|
3
3
|
export declare const RUNTIME_UPLOAD_API_PATH: "/__spacefast/upload.php";
|
|
4
4
|
export declare const RUNTIME_HEALTH_PATH: "/__spacefast/health.php";
|
|
5
|
-
export declare const RUNTIME_ACCESS_CALLBACK_PATH: "/__spacefast/access-callback.php";
|
|
6
5
|
export declare function runtimeManagementPath(suffix: string): string;
|
|
7
6
|
export declare function runtimeUploadPath(params: URLSearchParams | Record<string, string>): string;
|
|
@@ -2,7 +2,6 @@ export const SPACEFAST_RUNTIME_NAMESPACE = "/__spacefast";
|
|
|
2
2
|
export const RUNTIME_MANAGEMENT_API_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/api.php`;
|
|
3
3
|
export const RUNTIME_UPLOAD_API_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/upload.php`;
|
|
4
4
|
export const RUNTIME_HEALTH_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/health.php`;
|
|
5
|
-
export const RUNTIME_ACCESS_CALLBACK_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/access-callback.php`;
|
|
6
5
|
export function runtimeManagementPath(suffix) {
|
|
7
6
|
const route = normalizeRuntimeRoute(suffix);
|
|
8
7
|
if (route === "/") {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { RUNTIME_UPLOAD_BATCH_MAX_BYTES, RUNTIME_UPLOAD_BATCH_MAX_FILES } from "./runtime-upload.js";
|
|
3
|
+
import { type UploadTarget } from "./upload-session.js";
|
|
4
|
+
export { RUNTIME_UPLOAD_BATCH_MAX_BYTES, RUNTIME_UPLOAD_BATCH_MAX_FILES };
|
|
5
|
+
export type RuntimeBatchArchiveEntry = {
|
|
6
|
+
path: string;
|
|
7
|
+
getBytes(): Buffer | Promise<Buffer>;
|
|
8
|
+
};
|
|
9
|
+
export declare function runtimeBatchUploadUnsupported(error: unknown): boolean;
|
|
10
|
+
export declare function batchTargetForUploadTarget(uploadId: string, target: UploadTarget): UploadTarget | null;
|
|
11
|
+
export declare function tarPathParts(filePath: string): {
|
|
12
|
+
name: string;
|
|
13
|
+
prefix: string;
|
|
14
|
+
} | null;
|
|
15
|
+
export declare function tarEndSize(): number;
|
|
16
|
+
export declare function tarEntrySize(path: string, size: number): number | null;
|
|
17
|
+
export declare function tarPadding(size: number): number;
|
|
18
|
+
export declare function tarArchive(entries: readonly RuntimeBatchArchiveEntry[], pathTooLongError: (path: string) => Error): Promise<Buffer<ArrayBuffer>>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { RUNTIME_UPLOAD_BATCH_MAX_BYTES, RUNTIME_UPLOAD_BATCH_MAX_FILES, } from "./runtime-upload.js";
|
|
3
|
+
import { UploadTargetError } from "./upload-session.js";
|
|
4
|
+
// Batch planning + tar-writing wire contract shared by the CLI and the control
|
|
5
|
+
// plane's inline publish path. This is the sender half of the runtime's tar
|
|
6
|
+
// batch parser (runtime/engine/admin/upload.php); any change here must stay in
|
|
7
|
+
// lockstep with that file.
|
|
8
|
+
export { RUNTIME_UPLOAD_BATCH_MAX_BYTES, RUNTIME_UPLOAD_BATCH_MAX_FILES };
|
|
9
|
+
export function runtimeBatchUploadUnsupported(error) {
|
|
10
|
+
return error instanceof UploadTargetError && (error.status === 404 || error.status === 405);
|
|
11
|
+
}
|
|
12
|
+
export function batchTargetForUploadTarget(uploadId, target) {
|
|
13
|
+
const url = new URL(target.url);
|
|
14
|
+
if (!url.pathname.endsWith("/upload.php")) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
url.search = new URLSearchParams({ op: "batch", upload_id: uploadId }).toString();
|
|
18
|
+
return {
|
|
19
|
+
...target,
|
|
20
|
+
path: "__spacefast_batch.tar",
|
|
21
|
+
method: "POST",
|
|
22
|
+
url: url.toString(),
|
|
23
|
+
headers: {
|
|
24
|
+
...target.headers,
|
|
25
|
+
"content-type": "application/x-tar",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function tarPathParts(filePath) {
|
|
30
|
+
if (Buffer.byteLength(filePath) <= 100) {
|
|
31
|
+
return { name: filePath, prefix: "" };
|
|
32
|
+
}
|
|
33
|
+
const segments = filePath.split("/");
|
|
34
|
+
const name = segments.pop() ?? "";
|
|
35
|
+
const prefix = segments.join("/");
|
|
36
|
+
if (!name || Buffer.byteLength(name) > 100 || Buffer.byteLength(prefix) > 155) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return { name, prefix };
|
|
40
|
+
}
|
|
41
|
+
export function tarEndSize() {
|
|
42
|
+
return 1024;
|
|
43
|
+
}
|
|
44
|
+
export function tarEntrySize(path, size) {
|
|
45
|
+
if (!tarPathParts(path)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return 512 + size + tarPadding(size);
|
|
49
|
+
}
|
|
50
|
+
export function tarPadding(size) {
|
|
51
|
+
return (512 - (size % 512)) % 512;
|
|
52
|
+
}
|
|
53
|
+
export async function tarArchive(entries, pathTooLongError) {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const parts = tarPathParts(entry.path);
|
|
57
|
+
if (!parts) {
|
|
58
|
+
throw pathTooLongError(entry.path);
|
|
59
|
+
}
|
|
60
|
+
const body = Buffer.from(await entry.getBytes());
|
|
61
|
+
const header = Buffer.alloc(512);
|
|
62
|
+
header.write(parts.name, 0, Math.min(100, Buffer.byteLength(parts.name)), "utf8");
|
|
63
|
+
header.write("0000644\0", 100, 8, "ascii");
|
|
64
|
+
header.write("0000000\0", 108, 8, "ascii");
|
|
65
|
+
header.write("0000000\0", 116, 8, "ascii");
|
|
66
|
+
header.write(body.length.toString(8).padStart(11, "0") + "\0", 124, 12, "ascii");
|
|
67
|
+
header.write("00000000000\0", 136, 12, "ascii");
|
|
68
|
+
header.fill(0x20, 148, 156);
|
|
69
|
+
header[156] = "0".charCodeAt(0);
|
|
70
|
+
header.write("ustar", 257, 5, "ascii");
|
|
71
|
+
header.write("00", 263, 2, "ascii");
|
|
72
|
+
if (parts.prefix) {
|
|
73
|
+
header.write(parts.prefix, 345, Math.min(155, Buffer.byteLength(parts.prefix)), "utf8");
|
|
74
|
+
}
|
|
75
|
+
let checksum = 0;
|
|
76
|
+
for (const byte of header) {
|
|
77
|
+
checksum += byte;
|
|
78
|
+
}
|
|
79
|
+
header.write(checksum.toString(8).padStart(6, "0"), 148, 6, "ascii");
|
|
80
|
+
header[154] = 0;
|
|
81
|
+
header[155] = 0x20;
|
|
82
|
+
chunks.push(header, body);
|
|
83
|
+
const padding = tarPadding(body.length);
|
|
84
|
+
if (padding > 0) {
|
|
85
|
+
chunks.push(Buffer.alloc(padding));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
chunks.push(Buffer.alloc(tarEndSize()));
|
|
89
|
+
return Buffer.concat(chunks);
|
|
90
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const RUNTIME_UPLOAD_METHOD: "PUT";
|
|
2
|
+
export declare const RUNTIME_UPLOAD_BATCH_MAX_FILES = 500;
|
|
3
|
+
export declare const RUNTIME_UPLOAD_BATCH_MAX_BYTES: number;
|
|
2
4
|
export declare function runtimeUploadCanonicalPath(path: string): string;
|
|
3
5
|
export declare function runtimeUploadSessionBaseUrl(input: {
|
|
4
6
|
baseUrl: string;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { runtimeUploadPath } from "./runtime-paths.js";
|
|
2
2
|
export const RUNTIME_UPLOAD_METHOD = "PUT";
|
|
3
|
+
// Mirrors STATTIC_RUNTIME_UPLOAD_BATCH_MAX_FILES/_MAX_BYTES in
|
|
4
|
+
// runtime/engine/admin/upload.php (parity-guarded in
|
|
5
|
+
// apps/control-plane/src/runtime/php-policy-parity.test.ts).
|
|
6
|
+
export const RUNTIME_UPLOAD_BATCH_MAX_FILES = 500;
|
|
7
|
+
export const RUNTIME_UPLOAD_BATCH_MAX_BYTES = 8 * 1024 * 1024;
|
|
3
8
|
export function runtimeUploadCanonicalPath(path) {
|
|
4
9
|
// Canonical path form (spec "Upload Contract"): UTF-8, Unicode NFC,
|
|
5
10
|
// percent-encoded once. The runtime rejects non-canonical encodings.
|
|
@@ -37,9 +37,13 @@ export type ResolvedServingConfig = {
|
|
|
37
37
|
path: string;
|
|
38
38
|
status: 200 | 404;
|
|
39
39
|
} | null;
|
|
40
|
+
/** W7.1 clean URLs: serve flat `<name>.html` at its extensionless URL. */
|
|
41
|
+
cleanUrls: boolean;
|
|
40
42
|
listing: boolean;
|
|
41
43
|
viewer: boolean;
|
|
42
44
|
meta: NonNullable<SpaceConfig["meta"]> | null;
|
|
45
|
+
/** Gate-page theme tokens (access-plan §5.7 tier 1); null when unset. */
|
|
46
|
+
pagesTheme: NonNullable<NonNullable<SpaceConfig["pages"]>["theme"]> | null;
|
|
43
47
|
/** Hidden experimental build-pipeline flag; false keeps today's behavior. */
|
|
44
48
|
experimentalGutenberg: boolean;
|
|
45
49
|
/** Resolved inject snippets; only honored when experimentalGutenberg is true. */
|
|
@@ -303,7 +303,9 @@ const PRIVATE_SERVING_FILES = new Set([
|
|
|
303
303
|
...SPACE_CONFIG_PRIVATE_FILES,
|
|
304
304
|
]);
|
|
305
305
|
function privateServingPath(path) {
|
|
306
|
-
if (PRIVATE_SERVING_FILES.has(path) ||
|
|
306
|
+
if (PRIVATE_SERVING_FILES.has(path) ||
|
|
307
|
+
path === ".well-known/spacefast-runtime" ||
|
|
308
|
+
path === ".well-known/stattic-runtime") {
|
|
307
309
|
return true;
|
|
308
310
|
}
|
|
309
311
|
const segments = path.toLowerCase().split("/");
|
|
@@ -381,10 +383,17 @@ export function resolveEffectiveSpaceConfig(input) {
|
|
|
381
383
|
const serving = {
|
|
382
384
|
index: merged.index ?? inferredRootHtmlIndex ?? false,
|
|
383
385
|
fallback,
|
|
386
|
+
// Clean URLs (runtime W7.1): an explicit config value wins; the default is
|
|
387
|
+
// ON for plain static sites and OFF when a 200-status SPA fallback owns
|
|
388
|
+
// extensionless routes (the fallback answers them instead). The runtime
|
|
389
|
+
// resolver (management.php) applies the same default for artifacts
|
|
390
|
+
// finalized without the key.
|
|
391
|
+
cleanUrls: merged.cleanUrls ?? !(fallback && fallback.status === 200),
|
|
384
392
|
listing,
|
|
385
393
|
// The single-file viewer rides the listing bundle (artifact-style spaces).
|
|
386
394
|
viewer: listing,
|
|
387
395
|
meta: merged.meta ?? null,
|
|
396
|
+
pagesTheme: merged.pages?.theme && Object.keys(merged.pages.theme).length > 0 ? merged.pages.theme : null,
|
|
388
397
|
experimentalGutenberg: merged.experimental_gutenberg === true,
|
|
389
398
|
inject: merged.inject ?? null,
|
|
390
399
|
};
|
|
@@ -13,4 +13,6 @@ export type CompiledSpaceTheme = {
|
|
|
13
13
|
};
|
|
14
14
|
/** Engine-enforced ceiling for the compiled declaration block. */
|
|
15
15
|
export declare const SPACE_THEME_CSS_MAX_BYTES: number;
|
|
16
|
+
export declare function isSafeThemeColor(value: string): boolean;
|
|
17
|
+
export declare function isSafeThemeFontFamily(value: string): boolean;
|
|
16
18
|
export declare function compileSpaceTheme(theme: SpaceTheme | null | undefined): CompiledSpaceTheme;
|
|
@@ -25,6 +25,15 @@ function safeValue(value, pattern) {
|
|
|
25
25
|
const trimmed = value.trim();
|
|
26
26
|
return trimmed.length > 0 && !FORBIDDEN_VALUE_PATTERN.test(trimmed) && pattern.test(trimmed);
|
|
27
27
|
}
|
|
28
|
+
// Exported so other compilers that consume the same preset grammar (the gate-page
|
|
29
|
+
// theme cascade's `theme.json`/`sf.theme.jsonc` tiers, access-plan §8) reuse this
|
|
30
|
+
// ONE allowlist instead of re-deriving it — same posture, same forbidden set.
|
|
31
|
+
export function isSafeThemeColor(value) {
|
|
32
|
+
return safeValue(value, COLOR_PATTERN);
|
|
33
|
+
}
|
|
34
|
+
export function isSafeThemeFontFamily(value) {
|
|
35
|
+
return safeValue(value, FONT_FAMILY_PATTERN);
|
|
36
|
+
}
|
|
28
37
|
export function compileSpaceTheme(theme) {
|
|
29
38
|
if (!theme) {
|
|
30
39
|
return { css: null, warnings: [] };
|
|
@@ -7,27 +7,6 @@ export declare function pathExtension(filePath: string): string;
|
|
|
7
7
|
/** Basename without its extension (the "stem"), posix semantics. */
|
|
8
8
|
export declare function pathStem(filePath: string): string;
|
|
9
9
|
export declare function isPhpLikeStaticPath(filePath: string): boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Classifies a candidate hash token — the final `.`/`-`-delimited tail of a
|
|
12
|
-
* filename stem (see isImmutableAssetPath) — as a real bundler content hash.
|
|
13
|
-
*
|
|
14
|
-
* Immutable caching is a one-year pin, so the test is asymmetric on purpose: a
|
|
15
|
-
* missed hash costs a cheap 304 revalidation, a false positive serves stale
|
|
16
|
-
* content for a year. When a token is ambiguous we say no. The discriminator is
|
|
17
|
-
* NOT character case — modern bundlers emit BOTH lowercase-hex and mixed-case
|
|
18
|
-
* base64url hashes — it is entropy plus the absence of dictionary-word shape.
|
|
19
|
-
*
|
|
20
|
-
* IRREDUCIBLE LIMIT: an 8-char base64url token is the same size as a short
|
|
21
|
-
* CamelCase-with-version-digit human name, and every default Vite/Rollup hash we
|
|
22
|
-
* must accept carries only one digit (BzeDGMM3, 4A_iFExJ). So a token like
|
|
23
|
-
* `D3GeoMap`, `McKayCV2`, or `v2-WebGL` is character-for-character indistinguishable
|
|
24
|
-
* from a real hash and is accepted by clause B. No filename-only rule can separate
|
|
25
|
-
* these without also rejecting the bundler hashes the spec mandates. The durable
|
|
26
|
-
* fix is to gate immutable caching on path convention (Vite `assets/`, Next
|
|
27
|
-
* `_next/static/`, SvelteKit `_app/immutable/`, …) rather than guessing from the
|
|
28
|
-
* stem; this heuristic is the best-effort fallback when that context is absent.
|
|
29
|
-
*/
|
|
30
|
-
export declare function looksLikeBundlerFingerprint(segment: string): boolean;
|
|
31
10
|
export declare function isImmutableAssetPath(filePath: string): boolean;
|
|
32
11
|
/** Forced inert serving policy for a committed static file. PHP-like files are never executable. */
|
|
33
12
|
export declare function runtimeFileSafety(filePath: string): {
|
|
@@ -46,19 +46,17 @@ export const PLATFORM_MANAGED_RESPONSE_HEADERS = [
|
|
|
46
46
|
export const NON_IMMUTABLE_EXTENSIONS = ["html", "htm", "php", "txt", "xml"];
|
|
47
47
|
const PHP_LIKE_EXTENSION_SET = new Set(PHP_LIKE_EXTENSIONS);
|
|
48
48
|
const NON_IMMUTABLE_EXTENSION_SET = new Set(NON_IMMUTABLE_EXTENSIONS);
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// filename heuristic conservatively MISSES (no-digit / single-case base64url draws like
|
|
61
|
-
// `oton0Guf`, `DYyuCeEr`) without loosening the heuristic itself.
|
|
49
|
+
// Immutable caching is decided by DETERMINISTIC signals only — reserved framework
|
|
50
|
+
// build directories below, plus user-declared `_headers` Cache-Control rules that
|
|
51
|
+
// override the baked default at serve time. There is deliberately NO filename-entropy
|
|
52
|
+
// heuristic: an 8-char base64url bundler hash is character-for-character
|
|
53
|
+
// indistinguishable from a short human name (D3GeoMap, McKayCV2), so any guess either
|
|
54
|
+
// pins a user's mutable file for a year (stale site nobody can fix — browsers have no
|
|
55
|
+
// bust channel) or silently downgrades real hashes to revalidate-every-load, which
|
|
56
|
+
// under the wp.cloud edge per-IP rate limiter turned dashboard page loads into 429
|
|
57
|
+
// storms (2026-07-06). Unrecognized files default to the safe direction:
|
|
58
|
+
// `max-age=0, must-revalidate` for browsers, long s-maxage + purge-on-publish at the
|
|
59
|
+
// edge.
|
|
62
60
|
//
|
|
63
61
|
// Each prefix is HIGH-CONFIDENCE guaranteed-hashed:
|
|
64
62
|
// - `_next/static/` Next.js immutable build assets (chunks/css/media), all
|
|
@@ -71,9 +69,10 @@ const BASE64URL_FINGERPRINT = /^[A-Za-z0-9_-]{8,}$/;
|
|
|
71
69
|
//
|
|
72
70
|
// DELIBERATELY EXCLUDED: generic user-writable dirs (Vite's `assets/`, CRA/Gatsby's
|
|
73
71
|
// `static/`). Users drop NON-hashed files there, so widening to them would create
|
|
74
|
-
// year-long false positives.
|
|
75
|
-
//
|
|
76
|
-
//
|
|
72
|
+
// year-long false positives. Deployments of bundler output declare those dirs immutable
|
|
73
|
+
// themselves via a `_headers` rule (e.g. `/assets/*` → `Cache-Control: public,
|
|
74
|
+
// max-age=31536000, immutable`), which the publisher — a build we ran, an agent, a CI
|
|
75
|
+
// deploy — writes because it KNOWS the output is content-hashed.
|
|
77
76
|
export const GUARANTEED_IMMUTABLE_PREFIXES = [
|
|
78
77
|
"_next/static/",
|
|
79
78
|
"_app/immutable/",
|
|
@@ -109,97 +108,14 @@ export function pathStem(filePath) {
|
|
|
109
108
|
export function isPhpLikeStaticPath(filePath) {
|
|
110
109
|
return PHP_LIKE_EXTENSION_SET.has(pathExtension(filePath));
|
|
111
110
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Classifies a candidate hash token — the final `.`/`-`-delimited tail of a
|
|
114
|
-
* filename stem (see isImmutableAssetPath) — as a real bundler content hash.
|
|
115
|
-
*
|
|
116
|
-
* Immutable caching is a one-year pin, so the test is asymmetric on purpose: a
|
|
117
|
-
* missed hash costs a cheap 304 revalidation, a false positive serves stale
|
|
118
|
-
* content for a year. When a token is ambiguous we say no. The discriminator is
|
|
119
|
-
* NOT character case — modern bundlers emit BOTH lowercase-hex and mixed-case
|
|
120
|
-
* base64url hashes — it is entropy plus the absence of dictionary-word shape.
|
|
121
|
-
*
|
|
122
|
-
* IRREDUCIBLE LIMIT: an 8-char base64url token is the same size as a short
|
|
123
|
-
* CamelCase-with-version-digit human name, and every default Vite/Rollup hash we
|
|
124
|
-
* must accept carries only one digit (BzeDGMM3, 4A_iFExJ). So a token like
|
|
125
|
-
* `D3GeoMap`, `McKayCV2`, or `v2-WebGL` is character-for-character indistinguishable
|
|
126
|
-
* from a real hash and is accepted by clause B. No filename-only rule can separate
|
|
127
|
-
* these without also rejecting the bundler hashes the spec mandates. The durable
|
|
128
|
-
* fix is to gate immutable caching on path convention (Vite `assets/`, Next
|
|
129
|
-
* `_next/static/`, SvelteKit `_app/immutable/`, …) rather than guessing from the
|
|
130
|
-
* stem; this heuristic is the best-effort fallback when that context is absent.
|
|
131
|
-
*/
|
|
132
|
-
export function looksLikeBundlerFingerprint(segment) {
|
|
133
|
-
// (A) Lowercase-hex hash (git short-SHA, webpack/Vite/Gatsby `[contenthash]`):
|
|
134
|
-
// a1b2c3d, 3f9a2b7e, d41d8cd98f00b204e980. Always require >=1 hex letter and
|
|
135
|
-
// >=1 digit so all-letter words (deadbeef) and digit-only runs (1234567) are out.
|
|
136
|
-
if (HEX_FINGERPRINT.test(segment) && /[a-f]/.test(segment) && /[0-9]/.test(segment)) {
|
|
137
|
-
// A long all-hex token is unambiguous: no human authors a 16+ char hex name, so
|
|
138
|
-
// length alone is entropy proof and the anti-word guards would only lose real
|
|
139
|
-
// hashes (most 16/20-char contenthashes contain an incidental 4-run).
|
|
140
|
-
if (segment.length >= 16) {
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
// Short hex (7-15) overlaps with hex-spelled words/years/colors, so require >=2
|
|
144
|
-
// digits and forbid a run of 4+ hex letters (deadbeef, cafe, face, decade) or
|
|
145
|
-
// 4+ digits (2024, 1234) — low-entropy human false positives.
|
|
146
|
-
if ((segment.match(/[0-9]/g) ?? []).length >= 2 &&
|
|
147
|
-
!/[a-f]{4}/.test(segment) &&
|
|
148
|
-
!/[0-9]{4}/.test(segment)) {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// (B) Base64url hash (Vite/Rollup/esbuild/Next): BzeDGMM3, 4A_iFExJ, P1Tx-Cyu,
|
|
153
|
-
// Z-cBR0Xb, 9-ygPMte. Random base64url draws — but immutable is a one-year pin, so
|
|
154
|
-
// against the human names that mimic this shape (acronym data/UI names, version
|
|
155
|
-
// and fiscal labels, dates, resolutions) we demand the full BALANCED-entropy
|
|
156
|
-
// signature of a real hash, and accept the safe cost of missing the minority of
|
|
157
|
-
// genuine hashes that fall short (a cheap 304):
|
|
158
|
-
// - >=2 lowercase AND >=2 uppercase letters. Real base64url hashes carry several
|
|
159
|
-
// of each; acronym-dominant human names (USMap-AKHIORWA, Map-USA_CA_TX,
|
|
160
|
-
// MyCSS2DUI) have at most one lowercase token and are rejected here. This also
|
|
161
|
-
// conservatively drops single-case and no-/one-lowercase hashes (DYyuCeEr,
|
|
162
|
-
// ChJ_j-JJ) — the safe miss direction.
|
|
163
|
-
// - at least one digit. ~1/4 of base64url hashes lack one and become cheap 304s,
|
|
164
|
-
// but requiring it removes the all-letter acronym names that are otherwise
|
|
165
|
-
// indistinguishable from a hash (USMap-AKHIORWA has no digit).
|
|
166
|
-
// - no run of 3+ lowercase letters: a dictionary-word body (MyComponent
|
|
167
|
-
// "omponent", LoginOAuth2 "ogin", HTTPClient2 "lient"). A packed hash breaks
|
|
168
|
-
// lowercase up with caps/digits.
|
|
169
|
-
// - no run of 2+ digits: clustered digits are human years/resolutions/sequences/
|
|
170
|
-
// dates (2024, 1080, Win10, 01); a hash scatters single digits between letters.
|
|
171
|
-
// <8 chars (Widget2, API2, v2) never qualifies via the regex length floor.
|
|
172
|
-
if (BASE64URL_FINGERPRINT.test(segment) &&
|
|
173
|
-
(segment.match(/[a-z]/g) ?? []).length >= 2 &&
|
|
174
|
-
(segment.match(/[A-Z]/g) ?? []).length >= 2 &&
|
|
175
|
-
/[0-9]/.test(segment) &&
|
|
176
|
-
!/[a-z]{3}/.test(segment) &&
|
|
177
|
-
!/[0-9]{2}/.test(segment)) {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
111
|
export function isImmutableAssetPath(filePath) {
|
|
183
112
|
if (NON_IMMUTABLE_EXTENSION_SET.has(pathExtension(filePath))) {
|
|
184
113
|
return false;
|
|
185
114
|
}
|
|
186
|
-
// Path-convention layer
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
// Fallback for assets OUTSIDE those dirs: the filename-entropy heuristic.
|
|
193
|
-
const stem = pathStem(filePath);
|
|
194
|
-
for (let index = 0; index < stem.length; index += 1) {
|
|
195
|
-
if (index > 0 && stem[index - 1] !== "." && stem[index - 1] !== "-") {
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (looksLikeBundlerFingerprint(stem.slice(index))) {
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return false;
|
|
115
|
+
// Path-convention layer: anything under a guaranteed-content-hashed framework build
|
|
116
|
+
// dir is immutable regardless of filename. The extension gate above still applies,
|
|
117
|
+
// so an `index.html` accidentally under `_next/static/` is never pinned.
|
|
118
|
+
return isUnderGuaranteedImmutablePrefix(filePath);
|
|
203
119
|
}
|
|
204
120
|
/** Forced inert serving policy for a committed static file. PHP-like files are never executable. */
|
|
205
121
|
export function runtimeFileSafety(filePath) {
|
|
@@ -28,7 +28,7 @@ export declare function executeUploadSession(input: {
|
|
|
28
28
|
session: UploadSession;
|
|
29
29
|
targets?: readonly UploadTarget[] | undefined;
|
|
30
30
|
concurrency?: number | undefined;
|
|
31
|
-
fetch?: typeof fetch | undefined;
|
|
31
|
+
fetch?: ((...args: Parameters<typeof fetch>) => ReturnType<typeof fetch>) | undefined;
|
|
32
32
|
prepareTarget?: ((target: UploadTarget) => Promise<UploadTargetBody> | UploadTargetBody) | undefined;
|
|
33
33
|
onEvent?: ((event: UploadSessionEvent) => void) | undefined;
|
|
34
34
|
}): Promise<void>;
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
export type LoginIntentSearch = {
|
|
2
2
|
direct?: boolean | undefined;
|
|
3
3
|
returnTo?: string | undefined;
|
|
4
|
+
/** Route straight to WordPress.com's signup page instead of its login page. */
|
|
5
|
+
signup?: boolean | undefined;
|
|
4
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Optional query params WordPress.com's OAuth2 authorize endpoint understands
|
|
9
|
+
* (wpcom `wp-content/mu-plugins/oauth2/oauth2.php`). All are cosmetic/flow
|
|
10
|
+
* hints — dropping them never breaks the OAuth handshake.
|
|
11
|
+
*/
|
|
12
|
+
export type WpcomAuthorizeExtras = {
|
|
13
|
+
/** Prefills the email field on the WP.com login screen (`user_email`). */
|
|
14
|
+
userEmail?: string | undefined;
|
|
15
|
+
/** Localizes the WP.com login/consent/signup screens (`locale`). */
|
|
16
|
+
locale?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* `implicit=false`: WP.com signs the user out of WordPress.com and shows a
|
|
19
|
+
* fresh login instead of silently reusing the current WP.com session. The
|
|
20
|
+
* only way to let a user switch WP.com accounts — without it, sign-out on
|
|
21
|
+
* our side loops them straight back into the same account.
|
|
22
|
+
*/
|
|
23
|
+
switchAccount?: boolean | undefined;
|
|
24
|
+
/** `new-user=1`: routes straight to the WP.com signup page. */
|
|
25
|
+
signup?: boolean | undefined;
|
|
26
|
+
};
|
|
27
|
+
export declare function decorateWpcomAuthorizeUrl(authorizeUrl: string, extras?: WpcomAuthorizeExtras): string;
|
|
5
28
|
export declare function buildDashboardLoginPath(input: LoginIntentSearch): string;
|
|
6
|
-
export declare function buildWpcomAuthStartPath(input: LoginIntentSearch
|
|
29
|
+
export declare function buildWpcomAuthStartPath(input: LoginIntentSearch & {
|
|
30
|
+
/**
|
|
31
|
+
* A signed OAuth authorize query (Better Auth's login-page redirect).
|
|
32
|
+
* Carried through the WordPress.com round trip so the auth server can
|
|
33
|
+
* resume the authorization flow the moment the session exists. Opaque
|
|
34
|
+
* here — the auth server verifies its signature.
|
|
35
|
+
*/
|
|
36
|
+
oauthQuery?: string | undefined;
|
|
37
|
+
}): string;
|
|
7
38
|
export declare function buildPostAuthPath(input: LoginIntentSearch): string;
|
|
8
|
-
export declare function buildWpcomLoginUrl(authorizeUrl: string): string;
|
|
9
39
|
export declare function buildWpcomSignInOptions(input: LoginIntentSearch, dashboardOrigin: string): {
|
|
10
40
|
readonly provider: "wpcom";
|
|
11
41
|
readonly callbackURL: string;
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { sanitizeDashboardReturnTo } from "@spacefast/common/utils/auth-redirect";
|
|
2
|
+
export function decorateWpcomAuthorizeUrl(authorizeUrl, extras = {}) {
|
|
3
|
+
const url = new URL(authorizeUrl);
|
|
4
|
+
const email = extras.userEmail?.trim();
|
|
5
|
+
if (email && email.includes("@") && email.length <= 254) {
|
|
6
|
+
url.searchParams.set("user_email", email);
|
|
7
|
+
}
|
|
8
|
+
// WP.com validates locales against its own list and falls back to English,
|
|
9
|
+
// so a light shape check is enough here. English is the default — omit it.
|
|
10
|
+
const locale = extras.locale?.trim().toLowerCase().replace("_", "-");
|
|
11
|
+
if (locale && /^[a-z]{2}(-[a-z]{2})?$/.test(locale) && locale !== "en") {
|
|
12
|
+
url.searchParams.set("locale", locale);
|
|
13
|
+
}
|
|
14
|
+
if (extras.switchAccount) {
|
|
15
|
+
url.searchParams.set("implicit", "false");
|
|
16
|
+
}
|
|
17
|
+
if (extras.signup) {
|
|
18
|
+
url.searchParams.set("new-user", "1");
|
|
19
|
+
}
|
|
20
|
+
return url.toString();
|
|
21
|
+
}
|
|
2
22
|
export function buildDashboardLoginPath(input) {
|
|
3
23
|
const url = new URL("/sign-in", "http://dashboard.local");
|
|
4
24
|
if (input.direct) {
|
|
@@ -16,6 +36,12 @@ export function buildWpcomAuthStartPath(input) {
|
|
|
16
36
|
if (returnTo) {
|
|
17
37
|
url.searchParams.set("returnTo", returnTo);
|
|
18
38
|
}
|
|
39
|
+
if (input.signup) {
|
|
40
|
+
url.searchParams.set("signup", "1");
|
|
41
|
+
}
|
|
42
|
+
if (input.oauthQuery) {
|
|
43
|
+
url.searchParams.set("oauth_query", input.oauthQuery);
|
|
44
|
+
}
|
|
19
45
|
return `${url.pathname}${url.search}`;
|
|
20
46
|
}
|
|
21
47
|
export function buildPostAuthPath(input) {
|
|
@@ -27,16 +53,6 @@ export function buildPostAuthPath(input) {
|
|
|
27
53
|
}
|
|
28
54
|
return `${url.pathname}${url.search}`;
|
|
29
55
|
}
|
|
30
|
-
export function buildWpcomLoginUrl(authorizeUrl) {
|
|
31
|
-
const authUrl = new URL(authorizeUrl);
|
|
32
|
-
const loginUrl = new URL("https://wordpress.com/log-in");
|
|
33
|
-
const clientId = authUrl.searchParams.get("client_id");
|
|
34
|
-
if (clientId) {
|
|
35
|
-
loginUrl.searchParams.set("client_id", clientId);
|
|
36
|
-
}
|
|
37
|
-
loginUrl.searchParams.set("redirect_to", authUrl.toString());
|
|
38
|
-
return loginUrl.toString();
|
|
39
|
-
}
|
|
40
56
|
export function buildWpcomSignInOptions(input, dashboardOrigin) {
|
|
41
57
|
const errorCallbackUrl = new URL(buildDashboardLoginPath(input), dashboardOrigin);
|
|
42
58
|
errorCallbackUrl.searchParams.set("claim_error", "login");
|