@aglyn/plugins-marketplace 1.15.0 → 1.15.2

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.
Files changed (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
@@ -0,0 +1,78 @@
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 { openSecret, sealSecret } from "@aglyn/shared-util-tools/secret-box";
17
+ import { PLUGIN_CONNECTION_CREDENTIALS_COLLECTION, pluginConnectionDocId } from "../../model/plugin-connections.js";
18
+ export function credentialsRef(firestore, listingId, connectionId) {
19
+ return firestore.collection(PLUGIN_CONNECTION_CREDENTIALS_COLLECTION).doc(pluginConnectionDocId(listingId, connectionId));
20
+ }
21
+ /** The seal's context: this document and this value's purpose. */ export const credentialsContext = (listingId, connectionId, purpose)=>`plugin-connections:credentials:${listingId}:${connectionId}:${purpose}`;
22
+ /** Builds the stored record for a pair the publisher typed. */ export function sealCredentials(keyring, input) {
23
+ return {
24
+ orgId: input.orgId,
25
+ listingId: input.listingId,
26
+ connectionId: input.connectionId,
27
+ sealedClientId: sealSecret(input.clientId, keyring.current, {
28
+ context: credentialsContext(input.listingId, input.connectionId, 'client-id')
29
+ }),
30
+ sealedClientSecret: sealSecret(input.clientSecret, keyring.current, {
31
+ context: credentialsContext(input.listingId, input.connectionId, 'client-secret')
32
+ }),
33
+ updatedAtMs: input.nowMs,
34
+ updatedBy: input.uid
35
+ };
36
+ }
37
+ export async function saveCredentials(firestore, record) {
38
+ await credentialsRef(firestore, record.listingId, record.connectionId).set(record);
39
+ }
40
+ export async function clearCredentials(firestore, listingId, connectionId) {
41
+ await credentialsRef(firestore, listingId, connectionId).delete();
42
+ }
43
+ /** Whether each named connection's credentials are set, and when: never the values. */ export async function credentialsStatus(firestore, listingId, connectionIds) {
44
+ const snapshots = await Promise.all(connectionIds.map((id)=>credentialsRef(firestore, listingId, id).get()));
45
+ const out = {};
46
+ connectionIds.forEach((id, index)=>{
47
+ const data = snapshots[index].exists ? snapshots[index].data() : undefined;
48
+ const set = typeof (data == null ? void 0 : data['sealedClientId']) === 'string' && typeof (data == null ? void 0 : data['sealedClientSecret']) === 'string';
49
+ out[id] = {
50
+ set,
51
+ updatedAtMs: set ? Number(data == null ? void 0 : data['updatedAtMs']) || null : null
52
+ };
53
+ });
54
+ return out;
55
+ }
56
+ /**
57
+ * The publisher's OAuth app for a listing's connection, opened, or `null`
58
+ * when it is not set. Throws `SecretBoxError` for a value that does not open
59
+ * (a key that was dropped from the ring, or a tampered document).
60
+ */ export async function openCredentials(firestore, keyring, listingId, connectionId) {
61
+ const snapshot = await credentialsRef(firestore, listingId, connectionId).get();
62
+ const data = snapshot.exists ? snapshot.data() : undefined;
63
+ if (typeof (data == null ? void 0 : data['sealedClientId']) !== 'string' || typeof (data == null ? void 0 : data['sealedClientSecret']) !== 'string') return null;
64
+ return {
65
+ clientId: openSecret(data['sealedClientId'], keyring, {
66
+ context: credentialsContext(listingId, connectionId, 'client-id')
67
+ }).plaintext,
68
+ clientSecret: openSecret(data['sealedClientSecret'], keyring, {
69
+ context: credentialsContext(listingId, connectionId, 'client-secret')
70
+ }).plaintext
71
+ };
72
+ }
73
+ /** Every credentials document a workspace's listings hold, for its erasure. */ export async function credentialsForOrg(firestore, orgId) {
74
+ const snapshot = await firestore.collection(PLUGIN_CONNECTION_CREDENTIALS_COLLECTION).where('orgId', '==', orgId).get();
75
+ return snapshot.docs;
76
+ }
77
+
78
+ //# sourceMappingURL=credentials-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/marketplace/src/lib/server/connections/credentials-store.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 { openSecret, sealSecret, type SecretBoxKeyring } from '@aglyn/shared-util-tools/secret-box'\nimport { PLUGIN_CONNECTION_CREDENTIALS_COLLECTION, pluginConnectionDocId } from '../../model/plugin-connections'\nimport type { PluginConnectionClient } from './provider-http'\n\n/**\n * A LISTING'S OAUTH APP, SEALED (AGL-4193).\n *\n * `pluginConnectionCredentials/{listingId}__{connectionId}`: the client id\n * and secret of the PUBLISHER's own OAuth app, which every site's connect for\n * that listing authenticates with. Both are sealed with\n * `PLUGIN_CONNECTIONS_TOKEN_KEY`, each bound to this document and its purpose\n * as the seal's context, so a value copied into another listing's document\n * refuses to open there.\n *\n * Write-only by design: no route reads either value back. A publisher sees\n * \"set\" or \"not set\", and replaces the pair whole. `orgId` names the\n * publishing workspace, so that workspace's erasure sweeps the document by\n * field (`orgKeyedCollections`), and the Firestore rules refuse every client.\n */\n\nexport interface PluginConnectionCredentialsRecord {\n /** The publishing workspace. */\n orgId: string\n listingId: string\n connectionId: string\n sealedClientId: string\n sealedClientSecret: string\n updatedAtMs: number\n updatedBy: string\n}\n\nexport function credentialsRef(\n firestore: FirebaseFirestore.Firestore,\n listingId: string,\n connectionId: string,\n): FirebaseFirestore.DocumentReference {\n return firestore.collection(PLUGIN_CONNECTION_CREDENTIALS_COLLECTION).doc(pluginConnectionDocId(listingId, connectionId))\n}\n\n/** The seal's context: this document and this value's purpose. */\nexport const credentialsContext = (listingId: string, connectionId: string, purpose: 'client-id' | 'client-secret') =>\n `plugin-connections:credentials:${listingId}:${connectionId}:${purpose}`\n\n/** Builds the stored record for a pair the publisher typed. */\nexport function sealCredentials(\n keyring: SecretBoxKeyring,\n input: { orgId: string; listingId: string; connectionId: string; clientId: string; clientSecret: string; uid: string; nowMs: number },\n): PluginConnectionCredentialsRecord {\n return {\n orgId: input.orgId,\n listingId: input.listingId,\n connectionId: input.connectionId,\n sealedClientId: sealSecret(input.clientId, keyring.current, {\n context: credentialsContext(input.listingId, input.connectionId, 'client-id'),\n }),\n sealedClientSecret: sealSecret(input.clientSecret, keyring.current, {\n context: credentialsContext(input.listingId, input.connectionId, 'client-secret'),\n }),\n updatedAtMs: input.nowMs,\n updatedBy: input.uid,\n }\n}\n\nexport async function saveCredentials(\n firestore: FirebaseFirestore.Firestore,\n record: PluginConnectionCredentialsRecord,\n): Promise<void> {\n await credentialsRef(firestore, record.listingId, record.connectionId).set(record)\n}\n\nexport async function clearCredentials(\n firestore: FirebaseFirestore.Firestore,\n listingId: string,\n connectionId: string,\n): Promise<void> {\n await credentialsRef(firestore, listingId, connectionId).delete()\n}\n\n/** Whether each named connection's credentials are set, and when: never the values. */\nexport async function credentialsStatus(\n firestore: FirebaseFirestore.Firestore,\n listingId: string,\n connectionIds: readonly string[],\n): Promise<Record<string, { set: boolean; updatedAtMs: number | null }>> {\n const snapshots = await Promise.all(connectionIds.map((id) => credentialsRef(firestore, listingId, id).get()))\n const out: Record<string, { set: boolean; updatedAtMs: number | null }> = {}\n connectionIds.forEach((id, index) => {\n const data = snapshots[index].exists ? snapshots[index].data() : undefined\n const set = typeof data?.['sealedClientId'] === 'string' && typeof data?.['sealedClientSecret'] === 'string'\n out[id] = { set, updatedAtMs: set ? Number(data?.['updatedAtMs']) || null : null }\n })\n return out\n}\n\n/**\n * The publisher's OAuth app for a listing's connection, opened, or `null`\n * when it is not set. Throws `SecretBoxError` for a value that does not open\n * (a key that was dropped from the ring, or a tampered document).\n */\nexport async function openCredentials(\n firestore: FirebaseFirestore.Firestore,\n keyring: SecretBoxKeyring,\n listingId: string,\n connectionId: string,\n): Promise<PluginConnectionClient | null> {\n const snapshot = await credentialsRef(firestore, listingId, connectionId).get()\n const data = snapshot.exists ? snapshot.data() : undefined\n if (typeof data?.['sealedClientId'] !== 'string' || typeof data?.['sealedClientSecret'] !== 'string') return null\n return {\n clientId: openSecret(data['sealedClientId'], keyring, {\n context: credentialsContext(listingId, connectionId, 'client-id'),\n }).plaintext,\n clientSecret: openSecret(data['sealedClientSecret'], keyring, {\n context: credentialsContext(listingId, connectionId, 'client-secret'),\n }).plaintext,\n }\n}\n\n/** Every credentials document a workspace's listings hold, for its erasure. */\nexport async function credentialsForOrg(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n): Promise<FirebaseFirestore.QueryDocumentSnapshot[]> {\n const snapshot = await firestore.collection(PLUGIN_CONNECTION_CREDENTIALS_COLLECTION).where('orgId', '==', orgId).get()\n return snapshot.docs\n}\n"],"names":["openSecret","sealSecret","PLUGIN_CONNECTION_CREDENTIALS_COLLECTION","pluginConnectionDocId","credentialsRef","firestore","listingId","connectionId","collection","doc","credentialsContext","purpose","sealCredentials","keyring","input","orgId","sealedClientId","clientId","current","context","sealedClientSecret","clientSecret","updatedAtMs","nowMs","updatedBy","uid","saveCredentials","record","set","clearCredentials","delete","credentialsStatus","connectionIds","snapshots","Promise","all","map","id","get","out","forEach","index","data","exists","undefined","Number","openCredentials","snapshot","plaintext","credentialsForOrg","where","docs"],"mappings":"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"}
@@ -0,0 +1,25 @@
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 { PluginOrgErasureReport, PluginOrgErasureRequest } from '@aglyn/aglyn/plugin-manager/plugin-org-erasure';
18
+ import type { PluginSiteResourcePreview, PluginSiteResourceRequest } from '@aglyn/aglyn/plugin-manager/plugin-site-resources';
19
+ /** The site-resource eraser: nothing to preview, every grant revoked and deleted on erase. */
20
+ export declare const pluginConnectionsSiteEraser: {
21
+ preview(): Promise<PluginSiteResourcePreview>;
22
+ erase(request: PluginSiteResourceRequest): Promise<number>;
23
+ };
24
+ /** The workspace's share: its listings' OAuth app credentials, counted and (unless a dry run) deleted. */
25
+ export declare function erasePluginConnectionCredentials(request: PluginOrgErasureRequest): Promise<PluginOrgErasureReport>;
@@ -0,0 +1,72 @@
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 { firebaseAdmin } from "@aglyn/tenant-data-admin";
17
+ import { connectionsCollection, listHostConnections } from "./connection-store.js";
18
+ import { credentialsForOrg } from "./credentials-store.js";
19
+ import { revokeRecord } from "./revoke.js";
20
+ /**
21
+ * DECLARED CONNECTIONS GO WITH WHAT HELD THEM (AGL-4193).
22
+ *
23
+ * A SITE deleted takes its connections: `hosts/{hostId}/pluginConnections`
24
+ * is under the site's tree, so `eraseHost`'s recursive delete removes the
25
+ * documents. What it cannot reach is the grant at the provider, which would
26
+ * otherwise go on opening the business's account after the site that held
27
+ * it is gone. So the site-resource eraser, which runs before that delete,
28
+ * revokes each grant first (best effort: a revoke that fails is logged, and
29
+ * the sealed token is deleted either way), then deletes the documents itself
30
+ * so nothing depends on the order.
31
+ *
32
+ * A WORKSPACE erased takes the OAuth app credentials its listings hold
33
+ * (`pluginConnectionCredentials`, keyed by `orgId`): the erasure sweeps that
34
+ * collection by field (`orgKeyedCollections`), and the marketplace's org
35
+ * eraser deletes them too and counts them, so the audit record says how many
36
+ * went. Its sites' connections go with each site, through the eraser above.
37
+ *
38
+ * Nothing is listed on the Delete site card: a connection holds no content of
39
+ * the owner's, only a grant the owner can make again.
40
+ */ const firestore = ()=>firebaseAdmin.app().firestore();
41
+ /** The site-resource eraser: nothing to preview, every grant revoked and deleted on erase. */ export const pluginConnectionsSiteEraser = {
42
+ async preview () {
43
+ return {
44
+ noun: [
45
+ 'plugin connection',
46
+ 'plugin connections'
47
+ ],
48
+ itemNoun: [
49
+ 'item',
50
+ 'items'
51
+ ],
52
+ going: [],
53
+ staying: 0
54
+ };
55
+ },
56
+ async erase (request) {
57
+ const records = await listHostConnections(firestore(), request.hostId);
58
+ for (const record of records)await revokeRecord(record, request.orgId);
59
+ const snapshot = await connectionsCollection(firestore(), request.hostId).get();
60
+ await Promise.all(snapshot.docs.map((doc)=>doc.ref.delete()));
61
+ return records.length;
62
+ }
63
+ };
64
+ /** The workspace's share: its listings' OAuth app credentials, counted and (unless a dry run) deleted. */ export async function erasePluginConnectionCredentials(request) {
65
+ const docs = await credentialsForOrg(firestore(), request.orgId);
66
+ if (!request.dryRun) await Promise.all(docs.map((doc)=>doc.ref.delete()));
67
+ return {
68
+ connectionCredentials: docs.length
69
+ };
70
+ }
71
+
72
+ //# sourceMappingURL=erasure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/marketplace/src/lib/server/connections/erasure.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 { PluginOrgErasureReport, PluginOrgErasureRequest } from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport type {\n PluginSiteResourcePreview,\n PluginSiteResourceRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-resources'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { connectionsCollection, listHostConnections } from './connection-store'\nimport { credentialsForOrg } from './credentials-store'\nimport { revokeRecord } from './revoke'\n\n/**\n * DECLARED CONNECTIONS GO WITH WHAT HELD THEM (AGL-4193).\n *\n * A SITE deleted takes its connections: `hosts/{hostId}/pluginConnections`\n * is under the site's tree, so `eraseHost`'s recursive delete removes the\n * documents. What it cannot reach is the grant at the provider, which would\n * otherwise go on opening the business's account after the site that held\n * it is gone. So the site-resource eraser, which runs before that delete,\n * revokes each grant first (best effort: a revoke that fails is logged, and\n * the sealed token is deleted either way), then deletes the documents itself\n * so nothing depends on the order.\n *\n * A WORKSPACE erased takes the OAuth app credentials its listings hold\n * (`pluginConnectionCredentials`, keyed by `orgId`): the erasure sweeps that\n * collection by field (`orgKeyedCollections`), and the marketplace's org\n * eraser deletes them too and counts them, so the audit record says how many\n * went. Its sites' connections go with each site, through the eraser above.\n *\n * Nothing is listed on the Delete site card: a connection holds no content of\n * the owner's, only a grant the owner can make again.\n */\n\nconst firestore = (): FirebaseFirestore.Firestore => firebaseAdmin.app().firestore()\n\n/** The site-resource eraser: nothing to preview, every grant revoked and deleted on erase. */\nexport const pluginConnectionsSiteEraser = {\n async preview(): Promise<PluginSiteResourcePreview> {\n return { noun: ['plugin connection', 'plugin connections'], itemNoun: ['item', 'items'], going: [], staying: 0 }\n },\n async erase(request: PluginSiteResourceRequest): Promise<number> {\n const records = await listHostConnections(firestore(), request.hostId)\n for (const record of records) await revokeRecord(record, request.orgId)\n const snapshot = await connectionsCollection(firestore(), request.hostId).get()\n await Promise.all(snapshot.docs.map((doc) => doc.ref.delete()))\n return records.length\n },\n}\n\n/** The workspace's share: its listings' OAuth app credentials, counted and (unless a dry run) deleted. */\nexport async function erasePluginConnectionCredentials(request: PluginOrgErasureRequest): Promise<PluginOrgErasureReport> {\n const docs = await credentialsForOrg(firestore(), request.orgId)\n if (!request.dryRun) await Promise.all(docs.map((doc) => doc.ref.delete()))\n return { connectionCredentials: docs.length }\n}\n"],"names":["firebaseAdmin","connectionsCollection","listHostConnections","credentialsForOrg","revokeRecord","firestore","app","pluginConnectionsSiteEraser","preview","noun","itemNoun","going","staying","erase","request","records","hostId","record","orgId","snapshot","get","Promise","all","docs","map","doc","ref","delete","length","erasePluginConnectionCredentials","dryRun","connectionCredentials"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SAASA,aAAa,QAAQ,2BAA0B;AACxD,SAASC,qBAAqB,EAAEC,mBAAmB,QAAQ,wBAAoB;AAC/E,SAASC,iBAAiB,QAAQ,yBAAqB;AACvD,SAASC,YAAY,QAAQ,cAAU;AAEvC;;;;;;;;;;;;;;;;;;;;CAoBC,GAED,MAAMC,YAAY,IAAmCL,cAAcM,GAAG,GAAGD,SAAS;AAElF,4FAA4F,GAC5F,OAAO,MAAME,8BAA8B;IACzC,MAAMC;QACJ,OAAO;YAAEC,MAAM;gBAAC;gBAAqB;aAAqB;YAAEC,UAAU;gBAAC;gBAAQ;aAAQ;YAAEC,OAAO,EAAE;YAAEC,SAAS;QAAE;IACjH;IACA,MAAMC,OAAMC,OAAkC;QAC5C,MAAMC,UAAU,MAAMb,oBAAoBG,aAAaS,QAAQE,MAAM;QACrE,KAAK,MAAMC,UAAUF,QAAS,MAAMX,aAAaa,QAAQH,QAAQI,KAAK;QACtE,MAAMC,WAAW,MAAMlB,sBAAsBI,aAAaS,QAAQE,MAAM,EAAEI,GAAG;QAC7E,MAAMC,QAAQC,GAAG,CAACH,SAASI,IAAI,CAACC,GAAG,CAAC,CAACC,MAAQA,IAAIC,GAAG,CAACC,MAAM;QAC3D,OAAOZ,QAAQa,MAAM;IACvB;AACF,EAAC;AAED,wGAAwG,GACxG,OAAO,eAAeC,iCAAiCf,OAAgC;IACrF,MAAMS,OAAO,MAAMpB,kBAAkBE,aAAaS,QAAQI,KAAK;IAC/D,IAAI,CAACJ,QAAQgB,MAAM,EAAE,MAAMT,QAAQC,GAAG,CAACC,KAAKC,GAAG,CAAC,CAACC,MAAQA,IAAIC,GAAG,CAACC,MAAM;IACvE,OAAO;QAAEI,uBAAuBR,KAAKK,MAAM;IAAC;AAC9C"}
@@ -0,0 +1,64 @@
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 PluginConnectionDeclaration } from '@aglyn/aglyn/app-utils/plugin-connections';
18
+ /**
19
+ * WHICH CONNECTIONS A SITE'S INSTALLED PLUGINS DECLARE (AGL-4193).
20
+ *
21
+ * A connection is run under the declaration of the version the site has
22
+ * PINNED, never the listing's latest: a publisher shipping a new version must
23
+ * not change where an existing site's token is sent until that site moves to
24
+ * it. The pin carries its manifest (`hosts/{hostId}/installs/{listingId}`, or
25
+ * the workspace-wide `orgs/{orgId}/installs/{listingId}`), and every read here
26
+ * re-validates it, because a denormalized copy is data, not a verdict.
27
+ *
28
+ * A version the kill switch revoked declares nothing: its connections stop
29
+ * connecting with the code that declared them.
30
+ */
31
+ export interface InstalledPluginConnections {
32
+ listingId: string;
33
+ version: string;
34
+ /** The pinned plugin's name, for the activity feed. */
35
+ displayName: string;
36
+ connections: PluginConnectionDeclaration[];
37
+ }
38
+ /** The site's pin of one listing (the site's own first, then the workspace's), with its connections. */
39
+ export declare function installedPluginConnections(firestore: FirebaseFirestore.Firestore, input: {
40
+ hostId: string;
41
+ orgId: string | null;
42
+ listingId: string;
43
+ }): Promise<InstalledPluginConnections | null>;
44
+ /** One declared connection of a listing the site runs, or `null`. */
45
+ export declare function installedConnection(firestore: FirebaseFirestore.Firestore, input: {
46
+ hostId: string;
47
+ orgId: string | null;
48
+ listingId: string;
49
+ connectionId: string;
50
+ }): Promise<{
51
+ installed: InstalledPluginConnections;
52
+ connection: PluginConnectionDeclaration;
53
+ } | null>;
54
+ /** Every plugin the site runs that declares a connection: its own pins over the workspace's. */
55
+ export declare function installedConnectionsForHost(firestore: FirebaseFirestore.Firestore, input: {
56
+ hostId: string;
57
+ orgId: string | null;
58
+ }): Promise<InstalledPluginConnections[]>;
59
+ /**
60
+ * The connections a listing's LATEST version declares, for its publisher's
61
+ * credentials form: credentials belong to the listing, and the form offers
62
+ * what the newest version asks for.
63
+ */
64
+ export declare function latestListingConnections(firestore: FirebaseFirestore.Firestore, listingId: string, latestVersion: string): Promise<PluginConnectionDeclaration[]>;
@@ -0,0 +1,92 @@
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 { findPluginConnection, validatePluginConnections } from "@aglyn/aglyn/app-utils/plugin-connections";
17
+ import { isPluginRevoked } from "@aglyn/aglyn/app-utils/plugin-manifest";
18
+ async function revocationFor(firestore, listingId) {
19
+ return (await firestore.collection('revocations').doc(listingId).get()).data();
20
+ }
21
+ function readPin(listingId, data, revocation) {
22
+ var _data_version, _data_manifest, _ref, _data_displayName;
23
+ if (!data) return null;
24
+ const version = String((_data_version = data['version']) != null ? _data_version : '');
25
+ if (!version || isPluginRevoked(revocation, version)) return null;
26
+ const manifest = (_data_manifest = data['manifest']) != null ? _data_manifest : null;
27
+ const verdict = validatePluginConnections(manifest == null ? void 0 : manifest.connections);
28
+ if (verdict.ok === false || !verdict.value.length) return null;
29
+ return {
30
+ listingId,
31
+ version,
32
+ displayName: String((_ref = (_data_displayName = data['displayName']) != null ? _data_displayName : manifest == null ? void 0 : manifest.name) != null ? _ref : listingId).slice(0, 120),
33
+ connections: verdict.value
34
+ };
35
+ }
36
+ /** The site's pin of one listing (the site's own first, then the workspace's), with its connections. */ export async function installedPluginConnections(firestore, input) {
37
+ const [hostPin, orgPin, revocation] = await Promise.all([
38
+ firestore.collection('hosts').doc(input.hostId).collection('installs').doc(input.listingId).get(),
39
+ input.orgId ? firestore.collection('orgs').doc(input.orgId).collection('installs').doc(input.listingId).get() : Promise.resolve(null),
40
+ revocationFor(firestore, input.listingId)
41
+ ]);
42
+ const pin = hostPin.exists ? hostPin.data() : (orgPin == null ? void 0 : orgPin.exists) ? orgPin.data() : undefined;
43
+ return readPin(input.listingId, pin, revocation);
44
+ }
45
+ /** One declared connection of a listing the site runs, or `null`. */ export async function installedConnection(firestore, input) {
46
+ const installed = await installedPluginConnections(firestore, input);
47
+ if (!installed) return null;
48
+ const connection = findPluginConnection({
49
+ connections: installed.connections
50
+ }, input.connectionId);
51
+ return connection ? {
52
+ installed,
53
+ connection
54
+ } : null;
55
+ }
56
+ /** Every plugin the site runs that declares a connection: its own pins over the workspace's. */ export async function installedConnectionsForHost(firestore, input) {
57
+ var _ref;
58
+ const [hostPins, orgPins] = await Promise.all([
59
+ firestore.collection('hosts').doc(input.hostId).collection('installs').get(),
60
+ input.orgId ? firestore.collection('orgs').doc(input.orgId).collection('installs').get() : Promise.resolve(null)
61
+ ]);
62
+ const pins = new Map();
63
+ for (const doc of (_ref = orgPins == null ? void 0 : orgPins.docs) != null ? _ref : [])pins.set(doc.id, doc.data());
64
+ // The site's own pin wins, as it does for the loaders.
65
+ for (const doc of hostPins.docs)pins.set(doc.id, doc.data());
66
+ const declaring = [
67
+ ...pins.entries()
68
+ ].filter(([, data])=>{
69
+ var _data_manifest;
70
+ const connections = (_data_manifest = data['manifest']) == null ? void 0 : _data_manifest.connections;
71
+ return Array.isArray(connections) && connections.length > 0;
72
+ });
73
+ const out = [];
74
+ for (const [listingId, data] of declaring){
75
+ const read = readPin(listingId, data, await revocationFor(firestore, listingId));
76
+ if (read) out.push(read);
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * The connections a listing's LATEST version declares, for its publisher's
82
+ * credentials form: credentials belong to the listing, and the form offers
83
+ * what the newest version asks for.
84
+ */ export async function latestListingConnections(firestore, listingId, latestVersion) {
85
+ var _snapshot_get;
86
+ if (!latestVersion) return [];
87
+ const snapshot = await firestore.collection('marketplaceListings').doc(listingId).collection('pluginVersions').doc(latestVersion).get();
88
+ const verdict = validatePluginConnections((_snapshot_get = snapshot.get('manifest')) == null ? void 0 : _snapshot_get.connections);
89
+ return verdict.ok === false ? [] : verdict.value;
90
+ }
91
+
92
+ //# sourceMappingURL=installed-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/marketplace/src/lib/server/connections/installed-connection.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 findPluginConnection,\n validatePluginConnections,\n type PluginConnectionDeclaration,\n} from '@aglyn/aglyn/app-utils/plugin-connections'\nimport { isPluginRevoked, type PluginRevocation } from '@aglyn/aglyn/app-utils/plugin-manifest'\n\n/**\n * WHICH CONNECTIONS A SITE'S INSTALLED PLUGINS DECLARE (AGL-4193).\n *\n * A connection is run under the declaration of the version the site has\n * PINNED, never the listing's latest: a publisher shipping a new version must\n * not change where an existing site's token is sent until that site moves to\n * it. The pin carries its manifest (`hosts/{hostId}/installs/{listingId}`, or\n * the workspace-wide `orgs/{orgId}/installs/{listingId}`), and every read here\n * re-validates it, because a denormalized copy is data, not a verdict.\n *\n * A version the kill switch revoked declares nothing: its connections stop\n * connecting with the code that declared them.\n */\n\nexport interface InstalledPluginConnections {\n listingId: string\n version: string\n /** The pinned plugin's name, for the activity feed. */\n displayName: string\n connections: PluginConnectionDeclaration[]\n}\n\nasync function revocationFor(\n firestore: FirebaseFirestore.Firestore,\n listingId: string,\n): Promise<PluginRevocation | undefined> {\n return (await firestore.collection('revocations').doc(listingId).get()).data() as PluginRevocation | undefined\n}\n\nfunction readPin(\n listingId: string,\n data: Record<string, unknown> | undefined,\n revocation: PluginRevocation | undefined,\n): InstalledPluginConnections | null {\n if (!data) return null\n const version = String(data['version'] ?? '')\n if (!version || isPluginRevoked(revocation, version)) return null\n const manifest = (data['manifest'] ?? null) as { connections?: unknown; name?: unknown } | null\n const verdict = validatePluginConnections(manifest?.connections)\n if (verdict.ok === false || !verdict.value.length) return null\n return {\n listingId,\n version,\n displayName: String(data['displayName'] ?? manifest?.name ?? listingId).slice(0, 120),\n connections: verdict.value,\n }\n}\n\n/** The site's pin of one listing (the site's own first, then the workspace's), with its connections. */\nexport async function installedPluginConnections(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; orgId: string | null; listingId: string },\n): Promise<InstalledPluginConnections | null> {\n const [hostPin, orgPin, revocation] = await Promise.all([\n firestore.collection('hosts').doc(input.hostId).collection('installs').doc(input.listingId).get(),\n input.orgId\n ? firestore.collection('orgs').doc(input.orgId).collection('installs').doc(input.listingId).get()\n : Promise.resolve(null),\n revocationFor(firestore, input.listingId),\n ])\n const pin = hostPin.exists ? hostPin.data() : orgPin?.exists ? orgPin.data() : undefined\n return readPin(input.listingId, pin, revocation)\n}\n\n/** One declared connection of a listing the site runs, or `null`. */\nexport async function installedConnection(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; orgId: string | null; listingId: string; connectionId: string },\n): Promise<{ installed: InstalledPluginConnections; connection: PluginConnectionDeclaration } | null> {\n const installed = await installedPluginConnections(firestore, input)\n if (!installed) return null\n const connection = findPluginConnection({ connections: installed.connections }, input.connectionId)\n return connection ? { installed, connection } : null\n}\n\n/** Every plugin the site runs that declares a connection: its own pins over the workspace's. */\nexport async function installedConnectionsForHost(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; orgId: string | null },\n): Promise<InstalledPluginConnections[]> {\n const [hostPins, orgPins] = await Promise.all([\n firestore.collection('hosts').doc(input.hostId).collection('installs').get(),\n input.orgId ? firestore.collection('orgs').doc(input.orgId).collection('installs').get() : Promise.resolve(null),\n ])\n const pins = new Map<string, Record<string, unknown>>()\n for (const doc of orgPins?.docs ?? []) pins.set(doc.id, doc.data())\n // The site's own pin wins, as it does for the loaders.\n for (const doc of hostPins.docs) pins.set(doc.id, doc.data())\n const declaring = [...pins.entries()].filter(([, data]) => {\n const connections = (data['manifest'] as { connections?: unknown } | undefined)?.connections\n return Array.isArray(connections) && connections.length > 0\n })\n const out: InstalledPluginConnections[] = []\n for (const [listingId, data] of declaring) {\n const read = readPin(listingId, data, await revocationFor(firestore, listingId))\n if (read) out.push(read)\n }\n return out\n}\n\n/**\n * The connections a listing's LATEST version declares, for its publisher's\n * credentials form: credentials belong to the listing, and the form offers\n * what the newest version asks for.\n */\nexport async function latestListingConnections(\n firestore: FirebaseFirestore.Firestore,\n listingId: string,\n latestVersion: string,\n): Promise<PluginConnectionDeclaration[]> {\n if (!latestVersion) return []\n const snapshot = await firestore\n .collection('marketplaceListings')\n .doc(listingId)\n .collection('pluginVersions')\n .doc(latestVersion)\n .get()\n const verdict = validatePluginConnections((snapshot.get('manifest') as { connections?: unknown } | undefined)?.connections)\n return verdict.ok === false ? [] : verdict.value\n}\n"],"names":["findPluginConnection","validatePluginConnections","isPluginRevoked","revocationFor","firestore","listingId","collection","doc","get","data","readPin","revocation","version","String","manifest","verdict","connections","ok","value","length","displayName","name","slice","installedPluginConnections","input","hostPin","orgPin","Promise","all","hostId","orgId","resolve","pin","exists","undefined","installedConnection","installed","connection","connectionId","installedConnectionsForHost","hostPins","orgPins","pins","Map","docs","set","id","declaring","entries","filter","Array","isArray","out","read","push","latestListingConnections","latestVersion","snapshot"],"mappings":"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"}
@@ -0,0 +1,129 @@
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
+ * THE OAUTH `state` FOR A DECLARED CONNECTION (AGL-4193): signed,
19
+ * short-lived, single-use, and bound to one member, one site, one listing and
20
+ * one of its connections. Copied in shape from sales channels' connect state
21
+ * (`libs/plugins/sales-channels/.../oauth-state.ts`); one plugin never
22
+ * imports another.
23
+ *
24
+ * ## Signed
25
+ *
26
+ * `pc1.<payload>.<signature>`: the payload names the site, its workspace, the
27
+ * member, the listing, the connection, a nonce, the expiry and the console
28
+ * page to come back to; the signature is an HMAC under the shared,
29
+ * fail-closed `TOKEN_SIGNING_SECRET` with its own context, so no other signed
30
+ * token here verifies as one. The provider hands the state back verbatim, so
31
+ * a state that verifies says whose connect it was — which is how the
32
+ * callback, a redirect with no bearer token, knows its site, and the release
33
+ * gate knows its workspace.
34
+ *
35
+ * ## Single-use
36
+ *
37
+ * Starting a connect also writes one pending record per member, site, listing
38
+ * and connection on `hosts/{hostId}/pluginConnections/oauth-…`, holding the
39
+ * SHA-256 of the nonce, the expiry and the redirect address the code is
40
+ * issued for. The callback consumes it in a transaction, so a replayed state
41
+ * finds nothing, and a later start replaces it, retiring every earlier state.
42
+ *
43
+ * ## Coming back
44
+ *
45
+ * The page to return to is checked against the console's own origin when the
46
+ * connect starts, signed into the state, and checked again on the way back;
47
+ * the redirect carries only its path, so it can only ever land on the console
48
+ * that served the callback.
49
+ */
50
+ /** How long a member has to finish the provider's consent screen. */
51
+ export declare const CONNECT_STATE_TTL_MS: number;
52
+ /** The callback's path. Registered as a redirect URI at providers: never change it. */
53
+ export declare const CONNECT_CALLBACK_PATH: string;
54
+ export interface ConnectStateClaims {
55
+ hostId: string;
56
+ orgId: string;
57
+ uid: string;
58
+ listingId: string;
59
+ connectionId: string;
60
+ nonce: string;
61
+ /** Expiry, epoch ms. */
62
+ exp: number;
63
+ /** The console path (with its query) to return to. */
64
+ returnTo: string;
65
+ }
66
+ /** Mints a state. Throws when `TOKEN_SIGNING_SECRET` is not configured. */
67
+ export declare function mintConnectState(input: Omit<ConnectStateClaims, 'nonce' | 'exp'> & {
68
+ nowMs: number;
69
+ nonce?: string;
70
+ }): {
71
+ state: string;
72
+ claims: ConnectStateClaims;
73
+ };
74
+ export type ConnectStateRead = {
75
+ ok: true;
76
+ claims: ConnectStateClaims;
77
+ }
78
+ /** Authentic and past its expiry: the claims still say whose it was. */
79
+ | {
80
+ ok: false;
81
+ refusal: 'state-expired';
82
+ claims: ConnectStateClaims;
83
+ }
84
+ /** Tampered, truncated, another version, or no secret to check it with. */
85
+ | {
86
+ ok: false;
87
+ refusal: 'state-invalid';
88
+ };
89
+ /** Verifies a state's signature and expiry. Never throws. */
90
+ export declare function readConnectState(state: unknown, nowMs: number): ConnectStateRead;
91
+ /** The pending record's id: the member, listing and connection hashed, so a uid never names a document. */
92
+ export declare function connectStateDocId(uid: string, listingId: string, connectionId: string): string;
93
+ /** Writes the pending record for a freshly minted state, replacing any earlier one. */
94
+ export declare function recordConnectState(firestore: FirebaseFirestore.Firestore, input: {
95
+ claims: ConnectStateClaims;
96
+ redirectUri: string;
97
+ nowMs: number;
98
+ }): Promise<void>;
99
+ export type ConnectStateConsumed = {
100
+ ok: true;
101
+ redirectUri: string;
102
+ } | {
103
+ ok: false;
104
+ refusal: 'state-replayed' | 'state-superseded' | 'state-expired';
105
+ };
106
+ /** Consumes the pending record a verified state names — once. */
107
+ export declare function consumeConnectState(firestore: FirebaseFirestore.Firestore, input: {
108
+ claims: ConnectStateClaims;
109
+ nowMs: number;
110
+ }): Promise<ConnectStateConsumed>;
111
+ /** The canonical console origin, or `null` when it is set to something that is not http(s). */
112
+ export declare function canonicalConsoleOrigin(): string | null;
113
+ /**
114
+ * The console origin a connect started by `requestUrl` comes back to: the
115
+ * canonical one — the address registered on each publisher's OAuth app —
116
+ * except in development, where a request from `http://localhost` or
117
+ * `127.0.0.1` comes back to itself. Nothing else a request claims about its
118
+ * origin is believed.
119
+ */
120
+ export declare function consoleOriginFor(requestUrl: string): string | null;
121
+ /** Where the provider sends the member back to, or `null` when no origin is usable. */
122
+ export declare function connectRedirectUri(requestUrl: string): string | null;
123
+ /**
124
+ * The page to return to as a console path with its query, or `null` when
125
+ * `returnTo` is not on `origin`. Accepts an absolute URL on the origin or a
126
+ * single-slash path; anything else — another host, a protocol-relative
127
+ * `//host`, a `javascript:` URL, the callback itself — is refused.
128
+ */
129
+ export declare function consolePath(returnTo: unknown, origin: string): string | null;