@aglyn/plugins-forms 1.0.0-beta.220 → 1.0.0-beta.222
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/README.md +1 -0
- package/package.json +14 -12
- package/src/lib/components/form-detail-card.js +166 -29
- package/src/lib/components/form-detail-card.js.map +1 -1
- package/src/lib/components/form-submissions-card.component.d.ts +12 -0
- package/src/lib/components/form-submissions-card.component.js +35 -2
- package/src/lib/components/form-submissions-card.component.js.map +1 -1
- package/src/lib/components/form.js +2 -1
- package/src/lib/components/form.js.map +1 -1
- package/src/lib/components/host-forms-card.component.d.ts +8 -0
- package/src/lib/components/host-forms-card.component.js +26 -1
- package/src/lib/components/host-forms-card.component.js.map +1 -1
- package/src/lib/components/site-package-form-preview.component.d.ts +13 -0
- package/src/lib/components/site-package-form-preview.component.js +57 -0
- package/src/lib/components/site-package-form-preview.component.js.map +1 -0
- package/src/lib/constants/site-package.d.ts +18 -0
- package/src/lib/constants/site-package.js +18 -0
- package/src/lib/constants/site-package.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +39 -0
- package/src/lib/declarations.console-server.js +68 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.server.js +22 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/form-door.d.ts +37 -0
- package/src/lib/model/form-door.js +50 -0
- package/src/lib/model/form-door.js.map +1 -0
- package/src/lib/model/form-submission-counts.d.ts +26 -0
- package/src/lib/model/form-submission-counts.js +35 -0
- package/src/lib/model/form-submission-counts.js.map +1 -0
- package/src/lib/model/form-submission-list.d.ts +40 -0
- package/src/lib/model/form-submission-list.js +66 -0
- package/src/lib/model/form-submission-list.js.map +1 -0
- package/src/lib/model/form-submission-record.d.ts +50 -0
- package/src/lib/model/form-submission-record.js +68 -0
- package/src/lib/model/form-submission-record.js.map +1 -0
- package/src/lib/model/form-unavailable.d.ts +83 -0
- package/src/lib/model/form-unavailable.js +88 -0
- package/src/lib/model/form-unavailable.js.map +1 -0
- package/src/lib/model/forms-record-lists.d.ts +31 -0
- package/src/lib/model/forms-record-lists.js +50 -0
- package/src/lib/model/forms-record-lists.js.map +1 -0
- package/src/lib/plan-entitlements.js +6 -6
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +34 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/form-submissions.d.ts +34 -0
- package/src/lib/server/api-v1/form-submissions.js +273 -0
- package/src/lib/server/api-v1/form-submissions.js.map +1 -0
- package/src/lib/server/api-v1/openapi.d.ts +26 -0
- package/src/lib/server/api-v1/openapi.js +133 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/form-intake-gate.d.ts +34 -0
- package/src/lib/server/form-intake-gate.js +44 -0
- package/src/lib/server/form-intake-gate.js.map +1 -0
- package/src/lib/server/form-submission-index.d.ts +26 -0
- package/src/lib/server/form-submission-index.js +51 -0
- package/src/lib/server/form-submission-index.js.map +1 -0
- package/src/lib/server/form-submit.d.ts +43 -0
- package/src/lib/server/form-submit.js +769 -0
- package/src/lib/server/form-submit.js.map +1 -0
- package/src/lib/server/increment-form-stats.d.ts +70 -0
- package/src/lib/server/increment-form-stats.js +112 -0
- package/src/lib/server/increment-form-stats.js.map +1 -0
- package/src/lib/server.d.ts +10 -0
- package/src/lib/server.js +15 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/transfer/form-submissions-transfer-key.d.ts +37 -0
- package/src/lib/transfer/form-submissions-transfer-key.js +40 -0
- package/src/lib/transfer/form-submissions-transfer-key.js.map +1 -0
- package/src/lib/transfer/form-submissions-transfer.d.ts +98 -0
- package/src/lib/transfer/form-submissions-transfer.js +563 -0
- package/src/lib/transfer/form-submissions-transfer.js.map +1 -0
- package/src/lib/transfer/form-submissions-transfer.server.d.ts +26 -0
- package/src/lib/transfer/form-submissions-transfer.server.js +31 -0
- package/src/lib/transfer/form-submissions-transfer.server.js.map +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* What `/api/forms/submit` answers a submission to a site that switched Forms
|
|
19
|
+
* off (AGL-3029). A visitor reads it, so it names no plugin and no setting.
|
|
20
|
+
*/
|
|
21
|
+
export declare const FORMS_OFF_FOR_SITE_REFUSAL = "This site is not accepting form submissions";
|
|
22
|
+
/**
|
|
23
|
+
* The plugin whose door a submission to `/api/forms/submit` came through
|
|
24
|
+
* (AGL-3029) — the plugin that must run on the site for it to be accepted.
|
|
25
|
+
*
|
|
26
|
+
* The endpoint is shared. A form element posts to it, and so does a Marketing
|
|
27
|
+
* popup's email capture, which is a Marketing element rather than a form: a
|
|
28
|
+
* site that switched Forms off and still shows its popup must not silently
|
|
29
|
+
* lose every address the popup collects. The popup names its door in the
|
|
30
|
+
* body; everything else is a form's.
|
|
31
|
+
*
|
|
32
|
+
* A body that names a form ENTITY or a dataset binding is a form's, whatever
|
|
33
|
+
* door it claims. Those are what a form element sends and a popup never does,
|
|
34
|
+
* so the popup door cannot be used to file rows under a form, or into a
|
|
35
|
+
* dataset, on a site that switched Forms off.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formSubmissionDoorPlugin(body: Record<string, unknown> | null | undefined): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
* Which plugin's switch decides a submission to this plugin's door, and what
|
|
18
|
+
* the door says when that switch is off (AGL-3029). The door's own words:
|
|
19
|
+
* read by the door (`server/form-submit.ts`) and nothing else.
|
|
20
|
+
*/ import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
21
|
+
/**
|
|
22
|
+
* What `/api/forms/submit` answers a submission to a site that switched Forms
|
|
23
|
+
* off (AGL-3029). A visitor reads it, so it names no plugin and no setting.
|
|
24
|
+
*/ export const FORMS_OFF_FOR_SITE_REFUSAL = 'This site is not accepting form submissions';
|
|
25
|
+
/**
|
|
26
|
+
* The marketing plugin's id, as the door names the plugin whose switch judges
|
|
27
|
+
* a popup's capture. This plugin holds no import of it; the catalog is where
|
|
28
|
+
* the string is defined.
|
|
29
|
+
*/ const MARKETING_PLUGIN_ID = 'marketing';
|
|
30
|
+
/**
|
|
31
|
+
* The plugin whose door a submission to `/api/forms/submit` came through
|
|
32
|
+
* (AGL-3029) — the plugin that must run on the site for it to be accepted.
|
|
33
|
+
*
|
|
34
|
+
* The endpoint is shared. A form element posts to it, and so does a Marketing
|
|
35
|
+
* popup's email capture, which is a Marketing element rather than a form: a
|
|
36
|
+
* site that switched Forms off and still shows its popup must not silently
|
|
37
|
+
* lose every address the popup collects. The popup names its door in the
|
|
38
|
+
* body; everything else is a form's.
|
|
39
|
+
*
|
|
40
|
+
* A body that names a form ENTITY or a dataset binding is a form's, whatever
|
|
41
|
+
* door it claims. Those are what a form element sends and a popup never does,
|
|
42
|
+
* so the popup door cannot be used to file rows under a form, or into a
|
|
43
|
+
* dataset, on a site that switched Forms off.
|
|
44
|
+
*/ export function formSubmissionDoorPlugin(body) {
|
|
45
|
+
var _ref;
|
|
46
|
+
const namesFormData = Boolean(String((_ref = body == null ? void 0 : body['formId']) != null ? _ref : '').trim()) || Boolean(body == null ? void 0 : body['datasetBinding']);
|
|
47
|
+
return (body == null ? void 0 : body['door']) === 'popup' && !namesFormData ? MARKETING_PLUGIN_ID : BUNDLE_ID;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=form-door.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-door.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\n/**\n * Which plugin's switch decides a submission to this plugin's door, and what\n * the door says when that switch is off (AGL-3029). The door's own words:\n * read by the door (`server/form-submit.ts`) and nothing else.\n */\n\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * What `/api/forms/submit` answers a submission to a site that switched Forms\n * off (AGL-3029). A visitor reads it, so it names no plugin and no setting.\n */\nexport const FORMS_OFF_FOR_SITE_REFUSAL = 'This site is not accepting form submissions'\n\n/**\n * The marketing plugin's id, as the door names the plugin whose switch judges\n * a popup's capture. This plugin holds no import of it; the catalog is where\n * the string is defined.\n */\nconst MARKETING_PLUGIN_ID = 'marketing'\n\n/**\n * The plugin whose door a submission to `/api/forms/submit` came through\n * (AGL-3029) — the plugin that must run on the site for it to be accepted.\n *\n * The endpoint is shared. A form element posts to it, and so does a Marketing\n * popup's email capture, which is a Marketing element rather than a form: a\n * site that switched Forms off and still shows its popup must not silently\n * lose every address the popup collects. The popup names its door in the\n * body; everything else is a form's.\n *\n * A body that names a form ENTITY or a dataset binding is a form's, whatever\n * door it claims. Those are what a form element sends and a popup never does,\n * so the popup door cannot be used to file rows under a form, or into a\n * dataset, on a site that switched Forms off.\n */\nexport function formSubmissionDoorPlugin(body: Record<string, unknown> | null | undefined): string {\n const namesFormData =\n Boolean(String(body?.['formId'] ?? '').trim()) || Boolean(body?.['datasetBinding'])\n return body?.['door'] === 'popup' && !namesFormData ? MARKETING_PLUGIN_ID : BUNDLE_ID\n}\n"],"names":["BUNDLE_ID","FORMS_OFF_FOR_SITE_REFUSAL","MARKETING_PLUGIN_ID","formSubmissionDoorPlugin","body","namesFormData","Boolean","String","trim"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GAED,SAASA,SAAS,QAAQ,gCAA4B;AAEtD;;;CAGC,GACD,OAAO,MAAMC,6BAA6B,8CAA6C;AAEvF;;;;CAIC,GACD,MAAMC,sBAAsB;AAE5B;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,yBAAyBC,IAAgD;;IACvF,MAAMC,gBACJC,QAAQC,eAAOH,wBAAAA,IAAM,CAAC,SAAS,mBAAI,IAAII,IAAI,OAAOF,QAAQF,wBAAAA,IAAM,CAAC,iBAAiB;IACpF,OAAOA,CAAAA,wBAAAA,IAAM,CAAC,OAAO,MAAK,WAAW,CAACC,gBAAgBH,sBAAsBF;AAC9E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 PluginRecordCountSource } from '@aglyn/aglyn/plugin-manager/plugin-record-counts';
|
|
18
|
+
/**
|
|
19
|
+
* How many submissions a site received, counted in the console for another
|
|
20
|
+
* plugin's figure (AGL-3080) — a campaign's conversions out of every
|
|
21
|
+
* submission the site took in. Every one is the site's own, so the count
|
|
22
|
+
* never crosses sites; a reader with no site counts none.
|
|
23
|
+
*/
|
|
24
|
+
export declare const formSubmissionRecordCountSource: PluginRecordCountSource;
|
|
25
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */
|
|
26
|
+
export declare function registerFormSubmissionCounts(): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { registerPluginRecordCountSource } from "@aglyn/aglyn/plugin-manager/plugin-record-counts";
|
|
17
|
+
import { collection } from "firebase/firestore";
|
|
18
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
19
|
+
/**
|
|
20
|
+
* How many submissions a site received, counted in the console for another
|
|
21
|
+
* plugin's figure (AGL-3080) — a campaign's conversions out of every
|
|
22
|
+
* submission the site took in. Every one is the site's own, so the count
|
|
23
|
+
* never crosses sites; a reader with no site counts none.
|
|
24
|
+
*/ export const formSubmissionRecordCountSource = {
|
|
25
|
+
query (firestore, request) {
|
|
26
|
+
return request.hostId ? collection(firestore, 'hosts', request.hostId, 'formSubmissions') : null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */ export function registerFormSubmissionCounts() {
|
|
30
|
+
registerPluginRecordCountSource('formSubmission', formSubmissionRecordCountSource, {
|
|
31
|
+
pluginId: BUNDLE_ID
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=form-submission-counts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-submission-counts.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 {\n registerPluginRecordCountSource,\n type PluginRecordCountSource,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-counts'\nimport { collection } from 'firebase/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * How many submissions a site received, counted in the console for another\n * plugin's figure (AGL-3080) — a campaign's conversions out of every\n * submission the site took in. Every one is the site's own, so the count\n * never crosses sites; a reader with no site counts none.\n */\nexport const formSubmissionRecordCountSource: PluginRecordCountSource = {\n query(firestore, request) {\n return request.hostId ? collection(firestore, 'hosts', request.hostId, 'formSubmissions') : null\n },\n}\n\n/** Called from the console registrar, owner named for a spec that calls it directly. */\nexport function registerFormSubmissionCounts(): void {\n registerPluginRecordCountSource('formSubmission', formSubmissionRecordCountSource, {\n pluginId: BUNDLE_ID,\n })\n}\n"],"names":["registerPluginRecordCountSource","collection","BUNDLE_ID","formSubmissionRecordCountSource","query","firestore","request","hostId","registerFormSubmissionCounts","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,+BAA+B,QAE1B,mDAAkD;AACzD,SAASC,UAAU,QAAQ,qBAAoB;AAC/C,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;CAKC,GACD,OAAO,MAAMC,kCAA2D;IACtEC,OAAMC,SAAS,EAAEC,OAAO;QACtB,OAAOA,QAAQC,MAAM,GAAGN,WAAWI,WAAW,SAASC,QAAQC,MAAM,EAAE,qBAAqB;IAC9F;AACF,EAAC;AAED,sFAAsF,GACtF,OAAO,SAASC;IACdR,gCAAgC,kBAAkBG,iCAAiC;QACjFM,UAAUP;IACZ;AACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 PluginRecordListSource } from '@aglyn/aglyn/plugin-manager/plugin-record-lists';
|
|
18
|
+
/**
|
|
19
|
+
* A site's form submissions, listed in the console for the plugin that
|
|
20
|
+
* reads them (AGL-3080) — the Inbox. The record is
|
|
21
|
+
* `formSubmissionIndexedRecord`'s, and the stored fields a reader may filter,
|
|
22
|
+
* order and change are documented there.
|
|
23
|
+
*
|
|
24
|
+
* - `query` — the newest of one site's, at most `limit`, for a glance.
|
|
25
|
+
* - `walk` — the base a paged list walks: a site's submissions, or on the
|
|
26
|
+
* organization's Inbox every site's, which the reader narrows to the
|
|
27
|
+
* organization by `orgId` — the only group read the rules admit. The
|
|
28
|
+
* reader's list query adds that, its filters, its `createdAt` order and
|
|
29
|
+
* its pages.
|
|
30
|
+
* - `doc` — one site's submission, which the Inbox opens, marks read or
|
|
31
|
+
* unread, and deletes (a site admin's or editor's to do, by the rules), and
|
|
32
|
+
* under which it reads the replies it sent.
|
|
33
|
+
* - `byIds` — a site's submissions by name, at most
|
|
34
|
+
* {@link PLUGIN_RECORD_LIST_IDS_MAX} at a time, for a reader that holds ids
|
|
35
|
+
* from elsewhere: a campaign's conversion report grouping the submissions it
|
|
36
|
+
* was credited with by the page each was sent from.
|
|
37
|
+
*/
|
|
38
|
+
export declare const formSubmissionListSource: PluginRecordListSource;
|
|
39
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */
|
|
40
|
+
export declare function registerFormSubmissionList(): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
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 { PLUGIN_RECORD_LIST_IDS_MAX, registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
17
|
+
import { collection, collectionGroup, doc, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
18
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
19
|
+
import { formSubmissionIndexedRecord } from "./form-submission-record.js";
|
|
20
|
+
/** The collection this plugin keeps submissions in, under a site. */ const SUBMISSIONS = 'formSubmissions';
|
|
21
|
+
/**
|
|
22
|
+
* A site's form submissions, listed in the console for the plugin that
|
|
23
|
+
* reads them (AGL-3080) — the Inbox. The record is
|
|
24
|
+
* `formSubmissionIndexedRecord`'s, and the stored fields a reader may filter,
|
|
25
|
+
* order and change are documented there.
|
|
26
|
+
*
|
|
27
|
+
* - `query` — the newest of one site's, at most `limit`, for a glance.
|
|
28
|
+
* - `walk` — the base a paged list walks: a site's submissions, or on the
|
|
29
|
+
* organization's Inbox every site's, which the reader narrows to the
|
|
30
|
+
* organization by `orgId` — the only group read the rules admit. The
|
|
31
|
+
* reader's list query adds that, its filters, its `createdAt` order and
|
|
32
|
+
* its pages.
|
|
33
|
+
* - `doc` — one site's submission, which the Inbox opens, marks read or
|
|
34
|
+
* unread, and deletes (a site admin's or editor's to do, by the rules), and
|
|
35
|
+
* under which it reads the replies it sent.
|
|
36
|
+
* - `byIds` — a site's submissions by name, at most
|
|
37
|
+
* {@link PLUGIN_RECORD_LIST_IDS_MAX} at a time, for a reader that holds ids
|
|
38
|
+
* from elsewhere: a campaign's conversion report grouping the submissions it
|
|
39
|
+
* was credited with by the page each was sent from.
|
|
40
|
+
*/ export const formSubmissionListSource = {
|
|
41
|
+
query (firestore, request) {
|
|
42
|
+
if (!request.hostId || request.search || request.installedFrom) return null;
|
|
43
|
+
return query(collection(firestore, 'hosts', request.hostId, SUBMISSIONS), orderBy('createdAt', 'desc'), limit(request.limit));
|
|
44
|
+
},
|
|
45
|
+
walk (firestore, request) {
|
|
46
|
+
if (request.hostId) return collection(firestore, 'hosts', request.hostId, SUBMISSIONS);
|
|
47
|
+
return request.orgId ? collectionGroup(firestore, SUBMISSIONS) : null;
|
|
48
|
+
},
|
|
49
|
+
doc (firestore, request) {
|
|
50
|
+
if (!request.hostId || !request.id) return null;
|
|
51
|
+
return doc(firestore, 'hosts', request.hostId, SUBMISSIONS, request.id);
|
|
52
|
+
},
|
|
53
|
+
byIds (firestore, request) {
|
|
54
|
+
const ids = request.ids.filter(Boolean).slice(0, PLUGIN_RECORD_LIST_IDS_MAX);
|
|
55
|
+
if (!request.hostId || !ids.length) return null;
|
|
56
|
+
return query(collection(firestore, 'hosts', request.hostId, SUBMISSIONS), where(documentId(), 'in', ids));
|
|
57
|
+
},
|
|
58
|
+
record: formSubmissionIndexedRecord
|
|
59
|
+
};
|
|
60
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */ export function registerFormSubmissionList() {
|
|
61
|
+
registerPluginRecordListSource('formSubmission', formSubmissionListSource, {
|
|
62
|
+
pluginId: BUNDLE_ID
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=form-submission-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-submission-list.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 {\n PLUGIN_RECORD_LIST_IDS_MAX,\n registerPluginRecordListSource,\n type PluginRecordListSource,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport {\n collection,\n collectionGroup,\n doc,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { formSubmissionIndexedRecord } from './form-submission-record'\n\n/** The collection this plugin keeps submissions in, under a site. */\nconst SUBMISSIONS = 'formSubmissions'\n\n/**\n * A site's form submissions, listed in the console for the plugin that\n * reads them (AGL-3080) — the Inbox. The record is\n * `formSubmissionIndexedRecord`'s, and the stored fields a reader may filter,\n * order and change are documented there.\n *\n * - `query` — the newest of one site's, at most `limit`, for a glance.\n * - `walk` — the base a paged list walks: a site's submissions, or on the\n * organization's Inbox every site's, which the reader narrows to the\n * organization by `orgId` — the only group read the rules admit. The\n * reader's list query adds that, its filters, its `createdAt` order and\n * its pages.\n * - `doc` — one site's submission, which the Inbox opens, marks read or\n * unread, and deletes (a site admin's or editor's to do, by the rules), and\n * under which it reads the replies it sent.\n * - `byIds` — a site's submissions by name, at most\n * {@link PLUGIN_RECORD_LIST_IDS_MAX} at a time, for a reader that holds ids\n * from elsewhere: a campaign's conversion report grouping the submissions it\n * was credited with by the page each was sent from.\n */\nexport const formSubmissionListSource: PluginRecordListSource = {\n query(firestore, request) {\n if (!request.hostId || request.search || request.installedFrom) return null\n return query(\n collection(firestore, 'hosts', request.hostId, SUBMISSIONS),\n orderBy('createdAt', 'desc'),\n limit(request.limit),\n )\n },\n walk(firestore, request) {\n if (request.hostId) return collection(firestore, 'hosts', request.hostId, SUBMISSIONS)\n return request.orgId ? collectionGroup(firestore, SUBMISSIONS) : null\n },\n doc(firestore, request) {\n if (!request.hostId || !request.id) return null\n return doc(firestore, 'hosts', request.hostId, SUBMISSIONS, request.id)\n },\n byIds(firestore, request) {\n const ids = request.ids.filter(Boolean).slice(0, PLUGIN_RECORD_LIST_IDS_MAX)\n if (!request.hostId || !ids.length) return null\n return query(\n collection(firestore, 'hosts', request.hostId, SUBMISSIONS),\n where(documentId(), 'in', ids),\n )\n },\n record: formSubmissionIndexedRecord,\n}\n\n/** Called from the console registrar, owner named for a spec that calls it directly. */\nexport function registerFormSubmissionList(): void {\n registerPluginRecordListSource('formSubmission', formSubmissionListSource, {\n pluginId: BUNDLE_ID,\n })\n}\n"],"names":["PLUGIN_RECORD_LIST_IDS_MAX","registerPluginRecordListSource","collection","collectionGroup","doc","documentId","limit","orderBy","query","where","BUNDLE_ID","formSubmissionIndexedRecord","SUBMISSIONS","formSubmissionListSource","firestore","request","hostId","search","installedFrom","walk","orgId","id","byIds","ids","filter","Boolean","slice","length","record","registerFormSubmissionList","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,0BAA0B,EAC1BC,8BAA8B,QAEzB,kDAAiD;AACxD,SACEC,UAAU,EACVC,eAAe,EACfC,GAAG,EACHC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,2BAA2B,QAAQ,8BAA0B;AAEtE,mEAAmE,GACnE,MAAMC,cAAc;AAEpB;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMC,2BAAmD;IAC9DL,OAAMM,SAAS,EAAEC,OAAO;QACtB,IAAI,CAACA,QAAQC,MAAM,IAAID,QAAQE,MAAM,IAAIF,QAAQG,aAAa,EAAE,OAAO;QACvE,OAAOV,MACLN,WAAWY,WAAW,SAASC,QAAQC,MAAM,EAAEJ,cAC/CL,QAAQ,aAAa,SACrBD,MAAMS,QAAQT,KAAK;IAEvB;IACAa,MAAKL,SAAS,EAAEC,OAAO;QACrB,IAAIA,QAAQC,MAAM,EAAE,OAAOd,WAAWY,WAAW,SAASC,QAAQC,MAAM,EAAEJ;QAC1E,OAAOG,QAAQK,KAAK,GAAGjB,gBAAgBW,WAAWF,eAAe;IACnE;IACAR,KAAIU,SAAS,EAAEC,OAAO;QACpB,IAAI,CAACA,QAAQC,MAAM,IAAI,CAACD,QAAQM,EAAE,EAAE,OAAO;QAC3C,OAAOjB,IAAIU,WAAW,SAASC,QAAQC,MAAM,EAAEJ,aAAaG,QAAQM,EAAE;IACxE;IACAC,OAAMR,SAAS,EAAEC,OAAO;QACtB,MAAMQ,MAAMR,QAAQQ,GAAG,CAACC,MAAM,CAACC,SAASC,KAAK,CAAC,GAAG1B;QACjD,IAAI,CAACe,QAAQC,MAAM,IAAI,CAACO,IAAII,MAAM,EAAE,OAAO;QAC3C,OAAOnB,MACLN,WAAWY,WAAW,SAASC,QAAQC,MAAM,EAAEJ,cAC/CH,MAAMJ,cAAc,MAAMkB;IAE9B;IACAK,QAAQjB;AACV,EAAC;AAED,sFAAsF,GACtF,OAAO,SAASkB;IACd5B,+BAA+B,kBAAkBY,0BAA0B;QACzEiB,UAAUpB;IACZ;AACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
|
|
18
|
+
/**
|
|
19
|
+
* A FORM SUBMISSION, as this plugin shares it with another (AGL-3080).
|
|
20
|
+
*
|
|
21
|
+
* Submissions are this plugin's records: its door (`/api/forms/submit`)
|
|
22
|
+
* writes every one, its `/v1` resource serves them, its counters are what the
|
|
23
|
+
* plan bills, and they live where it keeps them —
|
|
24
|
+
* `hosts/{hostId}/formSubmissions/{id}`, which only this plugin names. The
|
|
25
|
+
* Inbox is the console's reader of them, and reads, marks and threads them
|
|
26
|
+
* through what this plugin publishes for the `formSubmission` kind: a list
|
|
27
|
+
* source in the console (`form-submission-list.ts`) and a record index on the
|
|
28
|
+
* server (`server/form-submission-index.ts`).
|
|
29
|
+
*
|
|
30
|
+
* ## The record, as a reader may rely on it
|
|
31
|
+
*
|
|
32
|
+
* Every stored document carries, from the moment the door writes it:
|
|
33
|
+
* `hostId`, and `orgId` when the site has one (both frozen by the rules);
|
|
34
|
+
* `formId` for a verified form entity; `formName` and `path`; `fields`, the
|
|
35
|
+
* values as typed, keyed by field name; `senderTokens` and `searchTokens`,
|
|
36
|
+
* the word-prefix keys a list filters and searches by; `read`, a boolean;
|
|
37
|
+
* `createdAt`, the server time it arrived; and `campaignIds`, `routing` and
|
|
38
|
+
* `rateDegraded` where they apply. A reader may change `read`, and stamp
|
|
39
|
+
* `repliedAtMs` with `read` when it answers one; it may keep its own records
|
|
40
|
+
* of what it did with one under the document (the Inbox's `replies` and
|
|
41
|
+
* `listAssignments`). Nothing else on it is a reader's to write, and the
|
|
42
|
+
* security rules hold the client half of that line.
|
|
43
|
+
*
|
|
44
|
+
* `null` for a document that holds nothing. Otherwise its id, a name — the
|
|
45
|
+
* form it was sent to, as it was called when it arrived — and `facts`:
|
|
46
|
+
* `hostId`, `formId` (`null` for a submission no form entity filed),
|
|
47
|
+
* `formName`, `path`, `fields`, `read` and `createdAtMs` (`null` before the
|
|
48
|
+
* server stamped it).
|
|
49
|
+
*/
|
|
50
|
+
export declare function formSubmissionIndexedRecord(id: string, data: Readonly<Record<string, unknown>> | undefined): PluginIndexedRecord | null;
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
* A FORM SUBMISSION, as this plugin shares it with another (AGL-3080).
|
|
18
|
+
*
|
|
19
|
+
* Submissions are this plugin's records: its door (`/api/forms/submit`)
|
|
20
|
+
* writes every one, its `/v1` resource serves them, its counters are what the
|
|
21
|
+
* plan bills, and they live where it keeps them —
|
|
22
|
+
* `hosts/{hostId}/formSubmissions/{id}`, which only this plugin names. The
|
|
23
|
+
* Inbox is the console's reader of them, and reads, marks and threads them
|
|
24
|
+
* through what this plugin publishes for the `formSubmission` kind: a list
|
|
25
|
+
* source in the console (`form-submission-list.ts`) and a record index on the
|
|
26
|
+
* server (`server/form-submission-index.ts`).
|
|
27
|
+
*
|
|
28
|
+
* ## The record, as a reader may rely on it
|
|
29
|
+
*
|
|
30
|
+
* Every stored document carries, from the moment the door writes it:
|
|
31
|
+
* `hostId`, and `orgId` when the site has one (both frozen by the rules);
|
|
32
|
+
* `formId` for a verified form entity; `formName` and `path`; `fields`, the
|
|
33
|
+
* values as typed, keyed by field name; `senderTokens` and `searchTokens`,
|
|
34
|
+
* the word-prefix keys a list filters and searches by; `read`, a boolean;
|
|
35
|
+
* `createdAt`, the server time it arrived; and `campaignIds`, `routing` and
|
|
36
|
+
* `rateDegraded` where they apply. A reader may change `read`, and stamp
|
|
37
|
+
* `repliedAtMs` with `read` when it answers one; it may keep its own records
|
|
38
|
+
* of what it did with one under the document (the Inbox's `replies` and
|
|
39
|
+
* `listAssignments`). Nothing else on it is a reader's to write, and the
|
|
40
|
+
* security rules hold the client half of that line.
|
|
41
|
+
*
|
|
42
|
+
* `null` for a document that holds nothing. Otherwise its id, a name — the
|
|
43
|
+
* form it was sent to, as it was called when it arrived — and `facts`:
|
|
44
|
+
* `hostId`, `formId` (`null` for a submission no form entity filed),
|
|
45
|
+
* `formName`, `path`, `fields`, `read` and `createdAtMs` (`null` before the
|
|
46
|
+
* server stamped it).
|
|
47
|
+
*/ export function formSubmissionIndexedRecord(id, data) {
|
|
48
|
+
if (!data) return null;
|
|
49
|
+
const text = (value)=>typeof value === 'string' ? value : '';
|
|
50
|
+
const formName = text(data['formName']);
|
|
51
|
+
const createdAt = data['createdAt'];
|
|
52
|
+
const createdAtMs = typeof (createdAt == null ? void 0 : createdAt.toMillis) === 'function' ? createdAt.toMillis() : null;
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
name: formName || 'Form',
|
|
56
|
+
facts: {
|
|
57
|
+
hostId: text(data['hostId']) || null,
|
|
58
|
+
formId: text(data['formId']) || null,
|
|
59
|
+
formName,
|
|
60
|
+
path: text(data['path']),
|
|
61
|
+
fields: data['fields'] && typeof data['fields'] === 'object' ? data['fields'] : {},
|
|
62
|
+
read: data['read'] === true,
|
|
63
|
+
createdAtMs
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=form-submission-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-submission-record.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 { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\n\n/**\n * A FORM SUBMISSION, as this plugin shares it with another (AGL-3080).\n *\n * Submissions are this plugin's records: its door (`/api/forms/submit`)\n * writes every one, its `/v1` resource serves them, its counters are what the\n * plan bills, and they live where it keeps them —\n * `hosts/{hostId}/formSubmissions/{id}`, which only this plugin names. The\n * Inbox is the console's reader of them, and reads, marks and threads them\n * through what this plugin publishes for the `formSubmission` kind: a list\n * source in the console (`form-submission-list.ts`) and a record index on the\n * server (`server/form-submission-index.ts`).\n *\n * ## The record, as a reader may rely on it\n *\n * Every stored document carries, from the moment the door writes it:\n * `hostId`, and `orgId` when the site has one (both frozen by the rules);\n * `formId` for a verified form entity; `formName` and `path`; `fields`, the\n * values as typed, keyed by field name; `senderTokens` and `searchTokens`,\n * the word-prefix keys a list filters and searches by; `read`, a boolean;\n * `createdAt`, the server time it arrived; and `campaignIds`, `routing` and\n * `rateDegraded` where they apply. A reader may change `read`, and stamp\n * `repliedAtMs` with `read` when it answers one; it may keep its own records\n * of what it did with one under the document (the Inbox's `replies` and\n * `listAssignments`). Nothing else on it is a reader's to write, and the\n * security rules hold the client half of that line.\n *\n * `null` for a document that holds nothing. Otherwise its id, a name — the\n * form it was sent to, as it was called when it arrived — and `facts`:\n * `hostId`, `formId` (`null` for a submission no form entity filed),\n * `formName`, `path`, `fields`, `read` and `createdAtMs` (`null` before the\n * server stamped it).\n */\nexport function formSubmissionIndexedRecord(\n id: string,\n data: Readonly<Record<string, unknown>> | undefined,\n): PluginIndexedRecord | null {\n if (!data) return null\n const text = (value: unknown): string => (typeof value === 'string' ? value : '')\n const formName = text(data['formName'])\n const createdAt = data['createdAt'] as { toMillis?: () => number } | null | undefined\n const createdAtMs =\n typeof createdAt?.toMillis === 'function' ? createdAt.toMillis() : null\n return {\n id,\n name: formName || 'Form',\n facts: {\n hostId: text(data['hostId']) || null,\n formId: text(data['formId']) || null,\n formName,\n path: text(data['path']),\n fields:\n data['fields'] && typeof data['fields'] === 'object' ? data['fields'] : {},\n read: data['read'] === true,\n createdAtMs,\n },\n }\n}\n"],"names":["formSubmissionIndexedRecord","id","data","text","value","formName","createdAt","createdAtMs","toMillis","name","facts","hostId","formId","path","fields","read"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BC,GACD,OAAO,SAASA,4BACdC,EAAU,EACVC,IAAmD;IAEnD,IAAI,CAACA,MAAM,OAAO;IAClB,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,QAAQ;IAC9E,MAAMC,WAAWF,KAAKD,IAAI,CAAC,WAAW;IACtC,MAAMI,YAAYJ,IAAI,CAAC,YAAY;IACnC,MAAMK,cACJ,QAAOD,6BAAAA,UAAWE,QAAQ,MAAK,aAAaF,UAAUE,QAAQ,KAAK;IACrE,OAAO;QACLP;QACAQ,MAAMJ,YAAY;QAClBK,OAAO;YACLC,QAAQR,KAAKD,IAAI,CAAC,SAAS,KAAK;YAChCU,QAAQT,KAAKD,IAAI,CAAC,SAAS,KAAK;YAChCG;YACAQ,MAAMV,KAAKD,IAAI,CAAC,OAAO;YACvBY,QACEZ,IAAI,CAAC,SAAS,IAAI,OAAOA,IAAI,CAAC,SAAS,KAAK,WAAWA,IAAI,CAAC,SAAS,GAAG,CAAC;YAC3Ea,MAAMb,IAAI,CAAC,OAAO,KAAK;YACvBK;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* What a VISITOR is told when a site's form is not accepting (AGL-1666).
|
|
19
|
+
*
|
|
20
|
+
* The door answers a flooded site's submissions with a refusal the form
|
|
21
|
+
* element recognises by its code, and the element shows the sentence below.
|
|
22
|
+
* The owner's half of the same moment — the count, the ceiling and when it
|
|
23
|
+
* lifts — is the forms plugin's `visitorDoors` declaration, which the Inbox
|
|
24
|
+
* and the staff pages read without loading this plugin. The two halves are
|
|
25
|
+
* constrained in opposite directions: the visitor is a stranger and must not
|
|
26
|
+
* be told why; the owner needs exactly that.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Refusal code carried by the abuse-ceiling 429 (AGL-1655).
|
|
30
|
+
*
|
|
31
|
+
* The Free plan's monthly wall answers 429 too, so the STATUS identifies
|
|
32
|
+
* nothing — this code is the whole discriminator, and one means "the customer
|
|
33
|
+
* needs a bigger plan" while the other means "this site is being flooded".
|
|
34
|
+
* Shared rather than restated so the producer and the consumer cannot drift.
|
|
35
|
+
*/
|
|
36
|
+
export declare const FORM_ABUSE_CEILING_CODE = "form-abuse-ceiling";
|
|
37
|
+
/** What the visitor is shown when a site's form is not accepting. */
|
|
38
|
+
export interface FormUnavailableNotice {
|
|
39
|
+
/** The whole message. Fixed copy — never the server's terse `error`. */
|
|
40
|
+
message: string;
|
|
41
|
+
/**
|
|
42
|
+
* An alternative way to reach the site, when the site publishes one
|
|
43
|
+
* (`business.supportEmail`, the token whose own description is "where
|
|
44
|
+
* visitors should write for help"). Absent when it does not.
|
|
45
|
+
*/
|
|
46
|
+
contact?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The visitor's sentence.
|
|
50
|
+
*
|
|
51
|
+
* Three things it must not do, all of which the generic error branch did or
|
|
52
|
+
* a friendlier rewrite would:
|
|
53
|
+
*
|
|
54
|
+
* 1. **Not blame the visitor.** They filled in a form correctly. Nothing
|
|
55
|
+
* about their message, their address or their retry is the reason.
|
|
56
|
+
* 2. **Not explain.** "Unusual volume", "temporarily over its limit", even
|
|
57
|
+
* "this site is receiving too many messages" all tell a stranger
|
|
58
|
+
* something about the site owner's account that the owner never chose to
|
|
59
|
+
* publish. The refusal is the owner's problem to see, not the caller's.
|
|
60
|
+
* 3. **Not sound delivered.** "We'll get back to you", or anything ending
|
|
61
|
+
* in a thank-you, converts a lost lead into a person waiting for a reply
|
|
62
|
+
* that is never coming. So it says, in as many words, that the message
|
|
63
|
+
* was not sent.
|
|
64
|
+
*
|
|
65
|
+
* What is left is short on purpose. The one genuinely useful thing to add is
|
|
66
|
+
* a door that still opens, which is why `contact` rides along.
|
|
67
|
+
*/
|
|
68
|
+
export declare const FORM_UNAVAILABLE_MESSAGE: string;
|
|
69
|
+
/**
|
|
70
|
+
* Does this refused submit mean "this site's form is not accepting", rather
|
|
71
|
+
* than "something broke"?
|
|
72
|
+
*
|
|
73
|
+
* Takes the BODY only, deliberately. The obvious signature — `(status, body)`,
|
|
74
|
+
* matching `parseLockdownRefusal` — invites a caller to gate on the status
|
|
75
|
+
* first, and the status is precisely what does not distinguish this refusal
|
|
76
|
+
* from the Free plan's monthly wall. A caller holding only this function
|
|
77
|
+
* cannot make that mistake.
|
|
78
|
+
*
|
|
79
|
+
* Returns `null` for every other body, so a caller's existing generic error
|
|
80
|
+
* branch keeps handling real failures. Dressing a 500 as a deliberate pause
|
|
81
|
+
* would be the same class of lie in the other direction.
|
|
82
|
+
*/
|
|
83
|
+
export declare function parseFormUnavailableRefusal(body: unknown): FormUnavailableNotice | null;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* What a VISITOR is told when a site's form is not accepting (AGL-1666).
|
|
19
|
+
*
|
|
20
|
+
* The door answers a flooded site's submissions with a refusal the form
|
|
21
|
+
* element recognises by its code, and the element shows the sentence below.
|
|
22
|
+
* The owner's half of the same moment — the count, the ceiling and when it
|
|
23
|
+
* lifts — is the forms plugin's `visitorDoors` declaration, which the Inbox
|
|
24
|
+
* and the staff pages read without loading this plugin. The two halves are
|
|
25
|
+
* constrained in opposite directions: the visitor is a stranger and must not
|
|
26
|
+
* be told why; the owner needs exactly that.
|
|
27
|
+
*/ /**
|
|
28
|
+
* Refusal code carried by the abuse-ceiling 429 (AGL-1655).
|
|
29
|
+
*
|
|
30
|
+
* The Free plan's monthly wall answers 429 too, so the STATUS identifies
|
|
31
|
+
* nothing — this code is the whole discriminator, and one means "the customer
|
|
32
|
+
* needs a bigger plan" while the other means "this site is being flooded".
|
|
33
|
+
* Shared rather than restated so the producer and the consumer cannot drift.
|
|
34
|
+
*/ export const FORM_ABUSE_CEILING_CODE = 'form-abuse-ceiling';
|
|
35
|
+
/**
|
|
36
|
+
* The visitor's sentence.
|
|
37
|
+
*
|
|
38
|
+
* Three things it must not do, all of which the generic error branch did or
|
|
39
|
+
* a friendlier rewrite would:
|
|
40
|
+
*
|
|
41
|
+
* 1. **Not blame the visitor.** They filled in a form correctly. Nothing
|
|
42
|
+
* about their message, their address or their retry is the reason.
|
|
43
|
+
* 2. **Not explain.** "Unusual volume", "temporarily over its limit", even
|
|
44
|
+
* "this site is receiving too many messages" all tell a stranger
|
|
45
|
+
* something about the site owner's account that the owner never chose to
|
|
46
|
+
* publish. The refusal is the owner's problem to see, not the caller's.
|
|
47
|
+
* 3. **Not sound delivered.** "We'll get back to you", or anything ending
|
|
48
|
+
* in a thank-you, converts a lost lead into a person waiting for a reply
|
|
49
|
+
* that is never coming. So it says, in as many words, that the message
|
|
50
|
+
* was not sent.
|
|
51
|
+
*
|
|
52
|
+
* What is left is short on purpose. The one genuinely useful thing to add is
|
|
53
|
+
* a door that still opens, which is why `contact` rides along.
|
|
54
|
+
*/ export const FORM_UNAVAILABLE_MESSAGE = 'This form isn’t accepting messages right now, so your message was not ' + 'sent. Please try again later.';
|
|
55
|
+
/**
|
|
56
|
+
* A site's published support address is author-written, so it is validated
|
|
57
|
+
* before being rendered as a link rather than trusted. Conservative on
|
|
58
|
+
* purpose: a rejected address costs the visitor a convenience, an accepted
|
|
59
|
+
* junk one costs them a bounced email and the site a lead it never learns it
|
|
60
|
+
* lost. The `mailto:` scheme is written by the renderer, not taken from here,
|
|
61
|
+
* so this is a plausibility check and not the injection defence.
|
|
62
|
+
*/ const CONTACT_EMAIL_PATTERN = /^[^\s@<>"']+@[^\s@<>"'.]+(\.[^\s@<>"'.]+)+$/;
|
|
63
|
+
/**
|
|
64
|
+
* Does this refused submit mean "this site's form is not accepting", rather
|
|
65
|
+
* than "something broke"?
|
|
66
|
+
*
|
|
67
|
+
* Takes the BODY only, deliberately. The obvious signature — `(status, body)`,
|
|
68
|
+
* matching `parseLockdownRefusal` — invites a caller to gate on the status
|
|
69
|
+
* first, and the status is precisely what does not distinguish this refusal
|
|
70
|
+
* from the Free plan's monthly wall. A caller holding only this function
|
|
71
|
+
* cannot make that mistake.
|
|
72
|
+
*
|
|
73
|
+
* Returns `null` for every other body, so a caller's existing generic error
|
|
74
|
+
* branch keeps handling real failures. Dressing a 500 as a deliberate pause
|
|
75
|
+
* would be the same class of lie in the other direction.
|
|
76
|
+
*/ export function parseFormUnavailableRefusal(body) {
|
|
77
|
+
if (!body || typeof body !== 'object') return null;
|
|
78
|
+
const payload = body;
|
|
79
|
+
if (payload.code !== FORM_ABUSE_CEILING_CODE) return null;
|
|
80
|
+
const contact = typeof payload.contact === 'string' && payload.contact.trim() ? payload.contact.trim() : undefined;
|
|
81
|
+
return _extends({
|
|
82
|
+
message: FORM_UNAVAILABLE_MESSAGE
|
|
83
|
+
}, contact && CONTACT_EMAIL_PATTERN.test(contact) ? {
|
|
84
|
+
contact
|
|
85
|
+
} : {});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=form-unavailable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-unavailable.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\n/**\n * What a VISITOR is told when a site's form is not accepting (AGL-1666).\n *\n * The door answers a flooded site's submissions with a refusal the form\n * element recognises by its code, and the element shows the sentence below.\n * The owner's half of the same moment — the count, the ceiling and when it\n * lifts — is the forms plugin's `visitorDoors` declaration, which the Inbox\n * and the staff pages read without loading this plugin. The two halves are\n * constrained in opposite directions: the visitor is a stranger and must not\n * be told why; the owner needs exactly that.\n */\n\n/**\n * Refusal code carried by the abuse-ceiling 429 (AGL-1655).\n *\n * The Free plan's monthly wall answers 429 too, so the STATUS identifies\n * nothing — this code is the whole discriminator, and one means \"the customer\n * needs a bigger plan\" while the other means \"this site is being flooded\".\n * Shared rather than restated so the producer and the consumer cannot drift.\n */\nexport const FORM_ABUSE_CEILING_CODE = 'form-abuse-ceiling'\n\n/** What the visitor is shown when a site's form is not accepting. */\nexport interface FormUnavailableNotice {\n /** The whole message. Fixed copy — never the server's terse `error`. */\n message: string\n /**\n * An alternative way to reach the site, when the site publishes one\n * (`business.supportEmail`, the token whose own description is \"where\n * visitors should write for help\"). Absent when it does not.\n */\n contact?: string\n}\n\n/**\n * The visitor's sentence.\n *\n * Three things it must not do, all of which the generic error branch did or\n * a friendlier rewrite would:\n *\n * 1. **Not blame the visitor.** They filled in a form correctly. Nothing\n * about their message, their address or their retry is the reason.\n * 2. **Not explain.** \"Unusual volume\", \"temporarily over its limit\", even\n * \"this site is receiving too many messages\" all tell a stranger\n * something about the site owner's account that the owner never chose to\n * publish. The refusal is the owner's problem to see, not the caller's.\n * 3. **Not sound delivered.** \"We'll get back to you\", or anything ending\n * in a thank-you, converts a lost lead into a person waiting for a reply\n * that is never coming. So it says, in as many words, that the message\n * was not sent.\n *\n * What is left is short on purpose. The one genuinely useful thing to add is\n * a door that still opens, which is why `contact` rides along.\n */\nexport const FORM_UNAVAILABLE_MESSAGE =\n 'This form isn’t accepting messages right now, so your message was not ' +\n 'sent. Please try again later.'\n\n/**\n * A site's published support address is author-written, so it is validated\n * before being rendered as a link rather than trusted. Conservative on\n * purpose: a rejected address costs the visitor a convenience, an accepted\n * junk one costs them a bounced email and the site a lead it never learns it\n * lost. The `mailto:` scheme is written by the renderer, not taken from here,\n * so this is a plausibility check and not the injection defence.\n */\nconst CONTACT_EMAIL_PATTERN = /^[^\\s@<>\"']+@[^\\s@<>\"'.]+(\\.[^\\s@<>\"'.]+)+$/\n\n/**\n * Does this refused submit mean \"this site's form is not accepting\", rather\n * than \"something broke\"?\n *\n * Takes the BODY only, deliberately. The obvious signature — `(status, body)`,\n * matching `parseLockdownRefusal` — invites a caller to gate on the status\n * first, and the status is precisely what does not distinguish this refusal\n * from the Free plan's monthly wall. A caller holding only this function\n * cannot make that mistake.\n *\n * Returns `null` for every other body, so a caller's existing generic error\n * branch keeps handling real failures. Dressing a 500 as a deliberate pause\n * would be the same class of lie in the other direction.\n */\nexport function parseFormUnavailableRefusal(\n body: unknown,\n): FormUnavailableNotice | null {\n if (!body || typeof body !== 'object') return null\n const payload = body as { code?: unknown; contact?: unknown }\n if (payload.code !== FORM_ABUSE_CEILING_CODE) return null\n const contact =\n typeof payload.contact === 'string' && payload.contact.trim()\n ? payload.contact.trim()\n : undefined\n return {\n message: FORM_UNAVAILABLE_MESSAGE,\n ...(contact && CONTACT_EMAIL_PATTERN.test(contact) ? { contact } : {}),\n }\n}\n"],"names":["FORM_ABUSE_CEILING_CODE","FORM_UNAVAILABLE_MESSAGE","CONTACT_EMAIL_PATTERN","parseFormUnavailableRefusal","body","payload","code","contact","trim","undefined","message","test"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED;;;;;;;CAOC,GACD,OAAO,MAAMA,0BAA0B,qBAAoB;AAc3D;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMC,2BACX,2EACA,gCAA+B;AAEjC;;;;;;;CAOC,GACD,MAAMC,wBAAwB;AAE9B;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,4BACdC,IAAa;IAEb,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU,OAAO;IAC9C,MAAMC,UAAUD;IAChB,IAAIC,QAAQC,IAAI,KAAKN,yBAAyB,OAAO;IACrD,MAAMO,UACJ,OAAOF,QAAQE,OAAO,KAAK,YAAYF,QAAQE,OAAO,CAACC,IAAI,KACvDH,QAAQE,OAAO,CAACC,IAAI,KACpBC;IACN,OAAO;QACLC,SAAST;OACLM,WAAWL,sBAAsBS,IAAI,CAACJ,WAAW;QAAEA;IAAQ,IAAI,CAAC;AAExE"}
|