@aglyn/plugins-redirects 1.0.0-beta.230 → 1.0.0-beta.231
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-redirects",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.231",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
36
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
37
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.231",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.231",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.231",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.231",
|
|
32
|
+
"@aglyn/shared-util-email": "1.0.0-beta.231",
|
|
33
|
+
"@aglyn/shared-util-http": "1.0.0-beta.231",
|
|
34
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.231",
|
|
35
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.231",
|
|
36
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.231",
|
|
37
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.231",
|
|
38
38
|
"@swc/helpers": "0.5.23"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/** What a save is asked to write. */
|
|
19
|
+
export interface RedirectSaveDraft {
|
|
20
|
+
/** The rule being edited; absent for a new rule. */
|
|
21
|
+
id?: string;
|
|
22
|
+
kind?: string;
|
|
23
|
+
source: string;
|
|
24
|
+
destination: string;
|
|
25
|
+
statusCode?: number;
|
|
26
|
+
}
|
|
27
|
+
/** The answer: the page's refusal in its own words, or the rule as the page would store it. */
|
|
28
|
+
export type RedirectSaveCheck = {
|
|
29
|
+
ok: false;
|
|
30
|
+
problem: string;
|
|
31
|
+
} | {
|
|
32
|
+
ok: true;
|
|
33
|
+
source: string;
|
|
34
|
+
destination: string;
|
|
35
|
+
statusCode: number;
|
|
36
|
+
kind: string;
|
|
37
|
+
notice: string | null;
|
|
38
|
+
};
|
|
39
|
+
type Rule = {
|
|
40
|
+
$id: string;
|
|
41
|
+
source?: string;
|
|
42
|
+
destination?: string;
|
|
43
|
+
kind?: string;
|
|
44
|
+
deletedAt?: unknown;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The Redirects page's save checks, in its order and its words
|
|
48
|
+
* (`redirects-console-page.tsx` `handleSave`): the shared rule validation
|
|
49
|
+
* (match mode, from-path or pattern, destination, another company's address),
|
|
50
|
+
* a path redirected to itself, a duplicate rule, a chain that loops back, and
|
|
51
|
+
* the published-page notice that warns without refusing. One source of truth
|
|
52
|
+
* for every client that saves a rule: the native apps ask it here rather than
|
|
53
|
+
* carry their own copy of the phishing screen and the pattern engine.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkRedirectSave(draft: RedirectSaveDraft, host: {
|
|
56
|
+
cname?: unknown;
|
|
57
|
+
subdomain?: unknown;
|
|
58
|
+
screens?: unknown;
|
|
59
|
+
}, rules: readonly Rule[]): RedirectSaveCheck;
|
|
60
|
+
/**
|
|
61
|
+
* `POST /api/redirects/check`: runs {@link checkRedirectSave} for a member
|
|
62
|
+
* who may publish on the site (the role the Firestore rules gate redirect
|
|
63
|
+
* writes on). It reads and answers; it writes nothing, so the save itself
|
|
64
|
+
* stays the same write the page makes, under the same rules.
|
|
65
|
+
*/
|
|
66
|
+
export declare const redirectCheckHandler: PluginApiHandler;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import { TENANT_APEX } from "@aglyn/aglyn/app-utils/tenant-apex";
|
|
19
|
+
import { findDuplicateRedirect, isSelfRedirect, normalizeRedirectDestination, normalizeRedirectSource, redirectSourceIsLivePage, REDIRECT_STATUS_CODES, validateRedirectRule, walkRedirectChain } from "../model/redirects.js";
|
|
20
|
+
/**
|
|
21
|
+
* The Redirects page's save checks, in its order and its words
|
|
22
|
+
* (`redirects-console-page.tsx` `handleSave`): the shared rule validation
|
|
23
|
+
* (match mode, from-path or pattern, destination, another company's address),
|
|
24
|
+
* a path redirected to itself, a duplicate rule, a chain that loops back, and
|
|
25
|
+
* the published-page notice that warns without refusing. One source of truth
|
|
26
|
+
* for every client that saves a rule: the native apps ask it here rather than
|
|
27
|
+
* carry their own copy of the phishing screen and the pattern engine.
|
|
28
|
+
*/ export function checkRedirectSave(draft, host, rules) {
|
|
29
|
+
const kind = draft.kind || 'exact';
|
|
30
|
+
const problem = validateRedirectRule({
|
|
31
|
+
kind,
|
|
32
|
+
source: draft.source,
|
|
33
|
+
destination: draft.destination
|
|
34
|
+
}, {
|
|
35
|
+
ownDomains: [
|
|
36
|
+
typeof host.cname === 'string' ? host.cname : null,
|
|
37
|
+
typeof host.subdomain === 'string' && host.subdomain ? `${host.subdomain}.${TENANT_APEX}` : null
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
if (problem) return {
|
|
41
|
+
ok: false,
|
|
42
|
+
problem
|
|
43
|
+
};
|
|
44
|
+
const source = kind === 'regex' ? draft.source.trim() : normalizeRedirectSource(draft.source);
|
|
45
|
+
const destination = normalizeRedirectDestination(draft.destination);
|
|
46
|
+
if (kind !== 'regex' && isSelfRedirect({
|
|
47
|
+
source,
|
|
48
|
+
destination
|
|
49
|
+
})) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
problem: 'That would redirect the path to itself'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const live = rules.filter((rule)=>!rule.deletedAt);
|
|
56
|
+
if (findDuplicateRedirect(live, {
|
|
57
|
+
$id: draft.id,
|
|
58
|
+
source,
|
|
59
|
+
kind
|
|
60
|
+
})) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
problem: `A rule for ${source} already exists`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (walkRedirectChain(live, {
|
|
67
|
+
$id: draft.id,
|
|
68
|
+
source,
|
|
69
|
+
destination
|
|
70
|
+
}).loop) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
problem: 'That destination chains back to this rule — a redirect loop'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const statusCode = REDIRECT_STATUS_CODES.includes(Number(draft.statusCode)) ? Number(draft.statusCode) : 302;
|
|
77
|
+
const notice = redirectSourceIsLivePage(host.screens, source) ? `${source} is a published page — the redirect takes precedence` : null;
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
source,
|
|
81
|
+
destination,
|
|
82
|
+
statusCode,
|
|
83
|
+
kind,
|
|
84
|
+
notice
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Rules a check reads, past the page's 200-rule window, so a duplicate or loop beyond it is still caught. */ const RULES_WINDOW = 500;
|
|
88
|
+
/**
|
|
89
|
+
* `POST /api/redirects/check`: runs {@link checkRedirectSave} for a member
|
|
90
|
+
* who may publish on the site (the role the Firestore rules gate redirect
|
|
91
|
+
* writes on). It reads and answers; it writes nothing, so the save itself
|
|
92
|
+
* stays the same write the page makes, under the same rules.
|
|
93
|
+
*/ export const redirectCheckHandler = async (req, res)=>{
|
|
94
|
+
var _req_headers_authorization, _req_body, _body_hostId, _body_source, _body_destination;
|
|
95
|
+
if (req.method !== 'POST') return res.status(405).json({
|
|
96
|
+
error: 'Method not allowed'
|
|
97
|
+
});
|
|
98
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
99
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : '';
|
|
100
|
+
if (!idToken) return res.status(401).json({
|
|
101
|
+
error: 'Unauthenticated'
|
|
102
|
+
});
|
|
103
|
+
const body = typeof req.body === 'string' ? JSON.parse(req.body || '{}') : (_req_body = req.body) != null ? _req_body : {};
|
|
104
|
+
const hostId = String((_body_hostId = body.hostId) != null ? _body_hostId : '');
|
|
105
|
+
if (!hostId || /^__.*__$/.test(hostId) || hostId.includes('/')) return res.status(400).json({
|
|
106
|
+
error: 'Missing hostId'
|
|
107
|
+
});
|
|
108
|
+
const draft = {
|
|
109
|
+
id: typeof body.id === 'string' && body.id ? body.id : undefined,
|
|
110
|
+
kind: typeof body.kind === 'string' ? body.kind : undefined,
|
|
111
|
+
source: String((_body_source = body.source) != null ? _body_source : '').slice(0, 2000),
|
|
112
|
+
destination: String((_body_destination = body.destination) != null ? _body_destination : '').slice(0, 2000),
|
|
113
|
+
statusCode: Number(body.statusCode)
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
var _host_get, _host_data;
|
|
117
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
118
|
+
const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId);
|
|
119
|
+
const host = await hostRef.get();
|
|
120
|
+
if (!host.exists) return res.status(404).json({
|
|
121
|
+
error: 'Unknown site'
|
|
122
|
+
});
|
|
123
|
+
const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[decoded.uid];
|
|
124
|
+
if (role !== 'admin' && role !== 'editor') {
|
|
125
|
+
return res.status(403).json({
|
|
126
|
+
error: 'Changing a redirect needs a publishing role — ask an editor or admin'
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const snapshot = await hostRef.collection('redirects').limit(RULES_WINDOW).get();
|
|
130
|
+
const rules = snapshot.docs.map((doc)=>_extends({
|
|
131
|
+
$id: doc.id
|
|
132
|
+
}, doc.data()));
|
|
133
|
+
return res.status(200).json(checkRedirectSave(draft, (_host_data = host.data()) != null ? _host_data : {}, rules));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error('[redirects/check]', error);
|
|
136
|
+
return res.status(500).json({
|
|
137
|
+
error: 'The redirect could not be checked'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=redirect-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/redirects/src/lib/server/redirect-check.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { TENANT_APEX } from '@aglyn/aglyn/app-utils/tenant-apex'\nimport {\n findDuplicateRedirect,\n isSelfRedirect,\n normalizeRedirectDestination,\n normalizeRedirectSource,\n redirectSourceIsLivePage,\n REDIRECT_STATUS_CODES,\n validateRedirectRule,\n walkRedirectChain,\n} from '../model/redirects'\n\n/** What a save is asked to write. */\nexport interface RedirectSaveDraft {\n /** The rule being edited; absent for a new rule. */\n id?: string\n kind?: string\n source: string\n destination: string\n statusCode?: number\n}\n\n/** The answer: the page's refusal in its own words, or the rule as the page would store it. */\nexport type RedirectSaveCheck =\n | { ok: false; problem: string }\n | { ok: true; source: string; destination: string; statusCode: number; kind: string; notice: string | null }\n\ntype Rule = { $id: string; source?: string; destination?: string; kind?: string; deletedAt?: unknown }\n\n/**\n * The Redirects page's save checks, in its order and its words\n * (`redirects-console-page.tsx` `handleSave`): the shared rule validation\n * (match mode, from-path or pattern, destination, another company's address),\n * a path redirected to itself, a duplicate rule, a chain that loops back, and\n * the published-page notice that warns without refusing. One source of truth\n * for every client that saves a rule: the native apps ask it here rather than\n * carry their own copy of the phishing screen and the pattern engine.\n */\nexport function checkRedirectSave(\n draft: RedirectSaveDraft,\n host: { cname?: unknown; subdomain?: unknown; screens?: unknown },\n rules: readonly Rule[],\n): RedirectSaveCheck {\n const kind = draft.kind || 'exact'\n const problem = validateRedirectRule(\n { kind, source: draft.source, destination: draft.destination },\n {\n ownDomains: [\n typeof host.cname === 'string' ? host.cname : null,\n typeof host.subdomain === 'string' && host.subdomain ? `${host.subdomain}.${TENANT_APEX}` : null,\n ],\n },\n )\n if (problem) return { ok: false, problem }\n const source = kind === 'regex' ? draft.source.trim() : (normalizeRedirectSource(draft.source) as string)\n const destination = normalizeRedirectDestination(draft.destination) as string\n if (kind !== 'regex' && isSelfRedirect({ source, destination })) {\n return { ok: false, problem: 'That would redirect the path to itself' }\n }\n const live = rules.filter((rule) => !rule.deletedAt)\n if (findDuplicateRedirect(live as never, { $id: draft.id, source, kind })) {\n return { ok: false, problem: `A rule for ${source} already exists` }\n }\n if (walkRedirectChain(live as never, { $id: draft.id, source, destination }).loop) {\n return { ok: false, problem: 'That destination chains back to this rule — a redirect loop' }\n }\n const statusCode = (REDIRECT_STATUS_CODES as readonly number[]).includes(Number(draft.statusCode))\n ? Number(draft.statusCode)\n : 302\n const notice = redirectSourceIsLivePage(host.screens as never, source)\n ? `${source} is a published page — the redirect takes precedence`\n : null\n return { ok: true, source, destination, statusCode, kind, notice }\n}\n\n/** Rules a check reads, past the page's 200-rule window, so a duplicate or loop beyond it is still caught. */\nconst RULES_WINDOW = 500\n\n/**\n * `POST /api/redirects/check`: runs {@link checkRedirectSave} for a member\n * who may publish on the site (the role the Firestore rules gate redirect\n * writes on). It reads and answers; it writes nothing, so the save itself\n * stays the same write the page makes, under the same rules.\n */\nexport const redirectCheckHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body = typeof req.body === 'string' ? JSON.parse(req.body || '{}') : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n if (!hostId || /^__.*__$/.test(hostId) || hostId.includes('/')) return res.status(400).json({ error: 'Missing hostId' })\n const draft: RedirectSaveDraft = {\n id: typeof body.id === 'string' && body.id ? body.id : undefined,\n kind: typeof body.kind === 'string' ? body.kind : undefined,\n source: String(body.source ?? '').slice(0, 2000),\n destination: String(body.destination ?? '').slice(0, 2000),\n statusCode: Number(body.statusCode),\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId)\n const host = await hostRef.get()\n if (!host.exists) return res.status(404).json({ error: 'Unknown site' })\n const role = (host.get('memberRoles') ?? {})[decoded.uid]\n if (role !== 'admin' && role !== 'editor') {\n return res.status(403).json({ error: 'Changing a redirect needs a publishing role — ask an editor or admin' })\n }\n const snapshot = await hostRef.collection('redirects').limit(RULES_WINDOW).get()\n const rules = snapshot.docs.map((doc: { id: string; data: () => Record<string, unknown> }) => ({ $id: doc.id, ...doc.data() }))\n return res.status(200).json(checkRedirectSave(draft, host.data() ?? {}, rules))\n } catch (error) {\n console.error('[redirects/check]', error)\n return res.status(500).json({ error: 'The redirect could not be checked' })\n }\n}\n"],"names":["firebaseAdmin","TENANT_APEX","findDuplicateRedirect","isSelfRedirect","normalizeRedirectDestination","normalizeRedirectSource","redirectSourceIsLivePage","REDIRECT_STATUS_CODES","validateRedirectRule","walkRedirectChain","checkRedirectSave","draft","host","rules","kind","problem","source","destination","ownDomains","cname","subdomain","ok","trim","live","filter","rule","deletedAt","$id","id","loop","statusCode","includes","Number","notice","screens","RULES_WINDOW","redirectCheckHandler","req","res","body","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","length","JSON","parse","hostId","test","undefined","decoded","app","auth","verifyIdToken","hostRef","firestore","collection","doc","get","exists","role","uid","snapshot","limit","docs","map","data","console"],"mappings":"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"}
|
package/src/lib/server.d.ts
CHANGED
|
@@ -21,3 +21,9 @@
|
|
|
21
21
|
* the plugin behind the core site-page hook.
|
|
22
22
|
*/
|
|
23
23
|
export declare function registerRedirectsApi(): void;
|
|
24
|
+
/**
|
|
25
|
+
* The redirects plugin's CONSOLE API: `POST /api/redirects/check`, the
|
|
26
|
+
* Redirects page's save checks for the native apps (`server/redirect-check.ts`).
|
|
27
|
+
* The handler loads with its first call, so neither boot pays for it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerRedirectsConsoleApi(): void;
|
package/src/lib/server.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import { registerSiteRedirectResolver } from "@aglyn/aglyn/server";
|
|
16
|
+
*/ import { registerPluginApiRoute, registerSiteRedirectResolver } from "@aglyn/aglyn/server";
|
|
17
17
|
import { resolveRedirect } from "./server/resolve-redirect.js";
|
|
18
18
|
/**
|
|
19
19
|
* Registers the redirects plugin's tenant-side hooks (AGL-418): the rule
|
|
@@ -29,5 +29,12 @@ import { resolveRedirect } from "./server/resolve-redirect.js";
|
|
|
29
29
|
} : undefined;
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The redirects plugin's CONSOLE API: `POST /api/redirects/check`, the
|
|
34
|
+
* Redirects page's save checks for the native apps (`server/redirect-check.ts`).
|
|
35
|
+
* The handler loads with its first call, so neither boot pays for it.
|
|
36
|
+
*/ export function registerRedirectsConsoleApi() {
|
|
37
|
+
registerPluginApiRoute('redirects/check', async (req, res)=>(await import("./server/redirect-check.js")).redirectCheckHandler(req, res));
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
//# sourceMappingURL=server.js.map
|
package/src/lib/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { registerSiteRedirectResolver } from '@aglyn/aglyn/server'\nimport { resolveRedirect } from './server/resolve-redirect'\n\n/**\n * Registers the redirects plugin's tenant-side hooks (AGL-418): the rule\n * engine (AGL-155) answers site requests with a redirect before any route\n * resolution — relocated from the tenant app's resolve-redirect util into\n * the plugin behind the core site-page hook.\n */\nexport function registerRedirectsApi(): void {\n registerSiteRedirectResolver(async ({ host, path }) => {\n const rule = await resolveRedirect(host, path)\n return rule\n ? { destination: rule.destination, statusCode: rule.statusCode }\n : undefined\n })\n}\n"],"names":["registerSiteRedirectResolver","resolveRedirect","registerRedirectsApi","host","path","rule","destination","statusCode","undefined"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,4BAA4B,QAAQ,sBAAqB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { registerPluginApiRoute, registerSiteRedirectResolver } from '@aglyn/aglyn/server'\nimport { resolveRedirect } from './server/resolve-redirect'\n\n/**\n * Registers the redirects plugin's tenant-side hooks (AGL-418): the rule\n * engine (AGL-155) answers site requests with a redirect before any route\n * resolution — relocated from the tenant app's resolve-redirect util into\n * the plugin behind the core site-page hook.\n */\nexport function registerRedirectsApi(): void {\n registerSiteRedirectResolver(async ({ host, path }) => {\n const rule = await resolveRedirect(host, path)\n return rule\n ? { destination: rule.destination, statusCode: rule.statusCode }\n : undefined\n })\n}\n\n/**\n * The redirects plugin's CONSOLE API: `POST /api/redirects/check`, the\n * Redirects page's save checks for the native apps (`server/redirect-check.ts`).\n * The handler loads with its first call, so neither boot pays for it.\n */\nexport function registerRedirectsConsoleApi(): void {\n registerPluginApiRoute('redirects/check', async (req, res) =>\n (await import('./server/redirect-check')).redirectCheckHandler(req, res),\n )\n}\n"],"names":["registerPluginApiRoute","registerSiteRedirectResolver","resolveRedirect","registerRedirectsApi","host","path","rule","destination","statusCode","undefined","registerRedirectsConsoleApi","req","res","redirectCheckHandler"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,EAAEC,4BAA4B,QAAQ,sBAAqB;AAC1F,SAASC,eAAe,QAAQ,+BAA2B;AAE3D;;;;;CAKC,GACD,OAAO,SAASC;IACdF,6BAA6B,OAAO,EAAEG,IAAI,EAAEC,IAAI,EAAE;QAChD,MAAMC,OAAO,MAAMJ,gBAAgBE,MAAMC;QACzC,OAAOC,OACH;YAAEC,aAAaD,KAAKC,WAAW;YAAEC,YAAYF,KAAKE,UAAU;QAAC,IAC7DC;IACN;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASC;IACdV,uBAAuB,mBAAmB,OAAOW,KAAKC,MACpD,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,oBAAoB,CAACF,KAAKC;AAExE"}
|