@scalemule/news-publication 0.0.48 → 0.0.50
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 +54 -0
- package/dist/chunk-4JRWZXAP.js +62 -0
- package/dist/chunk-ABPJMDUG.js +251 -0
- package/dist/chunk-AFJ6S5WJ.js +124 -0
- package/dist/{chunk-GB36YM56.js → chunk-AMLVCZDP.js} +11 -1
- package/dist/chunk-QW3OZB22.js +616 -0
- package/dist/community-maps-view.d.cts +3 -3
- package/dist/community-maps-view.d.ts +3 -3
- package/dist/cookie-consent-banner.d.cts +2 -2
- package/dist/cookie-consent-banner.d.ts +2 -2
- package/dist/cookie-settings-view.d.cts +2 -2
- package/dist/cookie-settings-view.d.ts +2 -2
- package/dist/index.cjs +29 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/marketplace-forms.d.cts +3 -3
- package/dist/marketplace-forms.d.ts +3 -3
- package/dist/marketplace-view.d.cts +5 -5
- package/dist/marketplace-view.d.ts +5 -5
- package/dist/{meet-RWmfj3I3.d.cts → meet-BWpxY20X.d.ts} +5 -0
- package/dist/{meet-RWmfj3I3.d.ts → meet-CO9Unz5M.d.cts} +5 -0
- package/dist/meet-forms.d.cts +7 -6
- package/dist/meet-forms.d.ts +7 -6
- package/dist/meet-forms.js +2 -1
- package/dist/meet-view.cjs +22 -2
- package/dist/meet-view.d.cts +7 -5
- package/dist/meet-view.d.ts +7 -5
- package/dist/meet-view.js +21 -4
- package/dist/network-bar.d.cts +2 -2
- package/dist/network-bar.d.ts +2 -2
- package/dist/network-copyright.d.cts +2 -2
- package/dist/network-copyright.d.ts +2 -2
- package/dist/pets-auth-server.cjs +257 -0
- package/dist/pets-auth-server.d.cts +23 -0
- package/dist/pets-auth-server.d.ts +23 -0
- package/dist/pets-auth-server.js +198 -0
- package/dist/pets-forms.cjs +764 -0
- package/dist/pets-forms.d.cts +28 -0
- package/dist/pets-forms.d.ts +28 -0
- package/dist/pets-forms.js +714 -0
- package/dist/pets-interactions.cjs +333 -0
- package/dist/pets-interactions.d.cts +23 -0
- package/dist/pets-interactions.d.ts +23 -0
- package/dist/pets-interactions.js +320 -0
- package/dist/pets-plugin.cjs +546 -0
- package/dist/pets-plugin.d.cts +50 -0
- package/dist/pets-plugin.d.ts +50 -0
- package/dist/pets-plugin.js +312 -0
- package/dist/pets-publication.cjs +1014 -0
- package/dist/pets-publication.d.cts +40 -0
- package/dist/pets-publication.d.ts +40 -0
- package/dist/pets-publication.js +201 -0
- package/dist/pets-server.cjs +289 -0
- package/dist/pets-server.d.cts +42 -0
- package/dist/pets-server.d.ts +42 -0
- package/dist/pets-server.js +2 -0
- package/dist/pets-view.cjs +714 -0
- package/dist/pets-view.d.cts +58 -0
- package/dist/pets-view.d.ts +58 -0
- package/dist/pets-view.js +4 -0
- package/dist/pets.cjs +138 -0
- package/dist/pets.d.cts +127 -0
- package/dist/pets.d.ts +127 -0
- package/dist/pets.js +1 -0
- package/dist/sponsorship-view.d.cts +2 -2
- package/dist/sponsorship-view.d.ts +2 -2
- package/dist/sponsorship-view.js +2 -62
- package/dist/story-review.d.cts +6 -6
- package/dist/story-review.d.ts +6 -6
- package/dist/theme-toggle.d.cts +2 -2
- package/dist/theme-toggle.d.ts +2 -2
- package/dist/verified-card.d.cts +3 -3
- package/dist/verified-card.d.ts +3 -3
- package/package.json +45 -3
- package/pets.css +746 -0
package/README.md
CHANGED
|
@@ -245,3 +245,57 @@ publication must validate that return path and preserve it through sign-in.
|
|
|
245
245
|
Inventory is unavailable until a verified operational placement is active. `placementAvailable` enforces verification freshness and keeps paid briefings disabled. Campaign approval, authorization, allocation capacity and manual commercial references remain server responsibilities. Report HTML distinguishes observed counts from unavailable data and can be printed or rendered to PDF. No invoice or payment is initiated by this SDK.
|
|
246
246
|
|
|
247
247
|
The consent banner now offers a choice in every geography where optional measurement requires one. `measurementConsent` is the server gate for current, explicitly allowed categories and GPC/DNT. Positive permission is local: unsigned URL parameters and a binary network hub cannot grant all categories. Rendering sponsorship recognition does not require tracking consent; measuring it requires both performance and advertising permission.
|
|
248
|
+
|
|
249
|
+
## Community pet sections
|
|
250
|
+
|
|
251
|
+
The `pets`, `pets-view`, `pets-forms`, `pets-interactions`, `pets-server`,
|
|
252
|
+
`pets-auth-server`, and `pets-publication` entry points add typed, moderated pet
|
|
253
|
+
resources to existing Community Maps collections. Import `pets.css` for the
|
|
254
|
+
scoped presentation. Server adapters require each application's own API key;
|
|
255
|
+
never pass credentials or a `PetScope` to a client component.
|
|
256
|
+
|
|
257
|
+
`PublicationPetsSection` resolves an accepted collection view and its coverage.
|
|
258
|
+
`PublicationPetReferences` resolves `metadata.pet_resources` at render time:
|
|
259
|
+
each reference has `collection_slug`, the API-returned `id`, and `kind`.
|
|
260
|
+
Removed/private resources are omitted; article records need no copied pet data.
|
|
261
|
+
`PublicationPetEvents` extends the existing publication calendar with reviewed
|
|
262
|
+
pet event references and official registration links. Meet's optional `walk`
|
|
263
|
+
field extends its existing dog-walk activity without creating another join system.
|
|
264
|
+
|
|
265
|
+
`createPawAuthHandler` delegates authentication to the platform's supported
|
|
266
|
+
Ledvery OIDC flow. Enable it only after configuring and testing the application
|
|
267
|
+
client and callback. `createPublicationPetsHandler` restricts routes, requires
|
|
268
|
+
same-origin authenticated writes, and proxies visibility-checked photographs.
|
|
269
|
+
Approved sponsorships come from existing Blog campaign delivery and reporting.
|
|
270
|
+
There is no fictional sponsor fallback and no public demonstration inventory.
|
|
271
|
+
|
|
272
|
+
Private recovery contact and broad community alert delivery are not implemented
|
|
273
|
+
by these components. Notification controls default to disabled. The canonical
|
|
274
|
+
application, collections, publication views and legitimate source content must
|
|
275
|
+
be provisioned separately before launch.
|
|
276
|
+
|
|
277
|
+
### Native pet plugin and shared sign-in
|
|
278
|
+
|
|
279
|
+
`pets-plugin` exports `PetAccountPage`, `PetSubmitPage`, `PetStoryPage` and
|
|
280
|
+
`PetBrowsePage`, with `publicationPetPaths` or `standalonePetPaths`. These are
|
|
281
|
+
server components: give them the publication’s `PetScope` resolved on the server
|
|
282
|
+
and its current reader token. Mount publication pages at `/pets/account`,
|
|
283
|
+
`/pets/submit`, `/pets/:id` and `/pets/browse/:kind`. `native` on the publication
|
|
284
|
+
section, references and calendar keeps resource links inside the publication.
|
|
285
|
+
|
|
286
|
+
Configure `createPawAuthHandler` with the host’s existing `sessionCookie`,
|
|
287
|
+
`callbackPath` and `accountPath`. An existing callback should dispatch to this
|
|
288
|
+
handler only when `openPawFlow(pawCookie(request, PAW_FLOW_COOKIE), state, key)`
|
|
289
|
+
validates. Otherwise preserve the publication’s normal authentication flow.
|
|
290
|
+
`GET resume?next=...` makes a top-level `prompt=none` attempt; protected pages
|
|
291
|
+
should call it only when the session is absent/invalid, `signin` is absent and
|
|
292
|
+
`PAW_SIGNED_OUT_COOKIE` is absent. Failed continuity returns `signin=required`
|
|
293
|
+
or `signin=retry`, preventing redirect loops. Explicit `POST start` opens the
|
|
294
|
+
normal sign-in. Honor and clear the sign-out marker in existing host login/logout
|
|
295
|
+
flows. All cookies remain host-only; do not embed the sign-in in an iframe.
|
|
296
|
+
|
|
297
|
+
The collection must explicitly set `shared_identity_provider` to its configured
|
|
298
|
+
OIDC provider. Auth resolves the verified provider subject; Knowledge authorizes
|
|
299
|
+
each accepted view and keeps ownership collection-scoped. Never correlate local
|
|
300
|
+
users by email or forward one application’s reader token/API key to another.
|
|
301
|
+
Provision and verify each RP before enabling sign-in or contribution flags.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { safeSponsorUrl } from './chunk-66GI2IX6.js';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
function SponsorAcknowledgment({
|
|
5
|
+
creative,
|
|
6
|
+
label = "Community Partner",
|
|
7
|
+
preview = false,
|
|
8
|
+
acknowledgment
|
|
9
|
+
}) {
|
|
10
|
+
const logo = safeSponsorUrl(creative.logo_url);
|
|
11
|
+
const destination = safeSponsorUrl(creative.destination_url);
|
|
12
|
+
return /* @__PURE__ */ jsxs(
|
|
13
|
+
"aside",
|
|
14
|
+
{
|
|
15
|
+
className: "sm-sponsorship",
|
|
16
|
+
"aria-label": `${label}: ${creative.name}`,
|
|
17
|
+
"data-sponsorship": preview ? void 0 : acknowledgment?.campaign_id,
|
|
18
|
+
"data-placement-id": preview ? void 0 : acknowledgment?.placement_id,
|
|
19
|
+
"data-publication-id": preview ? void 0 : acknowledgment?.publication_id,
|
|
20
|
+
"data-sponsor-id": preview ? void 0 : acknowledgment?.sponsor_id,
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ jsxs("div", { className: "sm-sponsorship-copy", children: [
|
|
23
|
+
/* @__PURE__ */ jsxs("p", { className: "sm-sponsorship-label", children: [
|
|
24
|
+
label,
|
|
25
|
+
preview ? " \xB7 Illustrative preview" : ""
|
|
26
|
+
] }),
|
|
27
|
+
/* @__PURE__ */ jsx("p", { className: "sm-sponsorship-name", children: creative.name }),
|
|
28
|
+
creative.description && /* @__PURE__ */ jsx("p", { className: "sm-sponsorship-description", children: creative.description }),
|
|
29
|
+
destination && /* @__PURE__ */ jsxs(
|
|
30
|
+
"a",
|
|
31
|
+
{
|
|
32
|
+
href: destination,
|
|
33
|
+
rel: "sponsored noopener noreferrer",
|
|
34
|
+
referrerPolicy: "no-referrer",
|
|
35
|
+
"data-analytics-skip": "true",
|
|
36
|
+
children: [
|
|
37
|
+
creative.link_label || "Learn more",
|
|
38
|
+
/* @__PURE__ */ jsxs("span", { className: "sm-sponsorship-sr", children: [
|
|
39
|
+
" about ",
|
|
40
|
+
creative.name
|
|
41
|
+
] })
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
] }),
|
|
46
|
+
logo && /* @__PURE__ */ jsx(
|
|
47
|
+
"img",
|
|
48
|
+
{
|
|
49
|
+
src: logo,
|
|
50
|
+
alt: `${creative.name} logo`,
|
|
51
|
+
loading: "lazy",
|
|
52
|
+
decoding: "async",
|
|
53
|
+
referrerPolicy: "no-referrer",
|
|
54
|
+
className: "sm-sponsorship-logo"
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { SponsorAcknowledgment };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { PET_UUID, petReferences, PET_SLUG } from './chunk-AFJ6S5WJ.js';
|
|
2
|
+
import { buildClientContextHeaders } from '@scalemule/sdk';
|
|
3
|
+
|
|
4
|
+
var PetRequestError = class extends Error {
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
status;
|
|
10
|
+
};
|
|
11
|
+
var petPrivateHeaders = {
|
|
12
|
+
"cache-control": "private, no-store",
|
|
13
|
+
"x-robots-tag": "noindex, nofollow",
|
|
14
|
+
"referrer-policy": "no-referrer",
|
|
15
|
+
"x-content-type-options": "nosniff"
|
|
16
|
+
};
|
|
17
|
+
async function bounded(request, max) {
|
|
18
|
+
if (Number(request.headers.get("content-length")) > max)
|
|
19
|
+
throw new PetRequestError(413, "The submission is too large.");
|
|
20
|
+
const reader = request.body?.getReader();
|
|
21
|
+
if (!reader) return new Uint8Array();
|
|
22
|
+
const parts = [];
|
|
23
|
+
let size = 0;
|
|
24
|
+
try {
|
|
25
|
+
for (; ; ) {
|
|
26
|
+
const { done, value } = await reader.read();
|
|
27
|
+
if (done) break;
|
|
28
|
+
size += value.length;
|
|
29
|
+
if (size > max) {
|
|
30
|
+
await reader.cancel();
|
|
31
|
+
throw new PetRequestError(413, "The submission is too large.");
|
|
32
|
+
}
|
|
33
|
+
parts.push(value);
|
|
34
|
+
}
|
|
35
|
+
} finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
return Buffer.concat(parts);
|
|
39
|
+
}
|
|
40
|
+
function config(scope) {
|
|
41
|
+
const url = new URL(scope.gatewayUrl);
|
|
42
|
+
if (!scope.apiKey || !PET_SLUG.test(scope.collectionSlug) || url.username || url.password || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname)))
|
|
43
|
+
throw new PetRequestError(503, "The pet community is not available yet.");
|
|
44
|
+
return url.origin;
|
|
45
|
+
}
|
|
46
|
+
async function petRequest(scope, path, init = {}, fetcher = fetch) {
|
|
47
|
+
const gateway = config(scope);
|
|
48
|
+
if (!path.startsWith("/v1/") || path.includes("..") || /[\\\r\n]/.test(path))
|
|
49
|
+
throw new Error("Invalid platform path");
|
|
50
|
+
const response = await fetcher(gateway + path, {
|
|
51
|
+
method: init.method || "GET",
|
|
52
|
+
headers: {
|
|
53
|
+
...buildClientContextHeaders(scope.clientContext),
|
|
54
|
+
"x-api-key": scope.apiKey,
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
...scope.accessToken ? { authorization: `Bearer ${scope.accessToken}` } : {}
|
|
57
|
+
},
|
|
58
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
|
|
59
|
+
cache: "no-store",
|
|
60
|
+
redirect: "error",
|
|
61
|
+
signal: AbortSignal.timeout(15e3)
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok)
|
|
64
|
+
throw new PetRequestError(
|
|
65
|
+
response.status,
|
|
66
|
+
response.status === 401 ? "Please sign in to continue." : response.status === 404 ? "This pet resource is not available." : response.status === 409 ? "This contribution changed. Reload before saving." : response.status === 429 ? "Please wait before trying again." : "We couldn\u2019t complete that request. Please try again."
|
|
67
|
+
);
|
|
68
|
+
const json = await response.json();
|
|
69
|
+
return json?.data ?? json;
|
|
70
|
+
}
|
|
71
|
+
function createPublicationPetsHandler(options) {
|
|
72
|
+
const fetcher = options.fetch || fetch;
|
|
73
|
+
return async function handler(request, path) {
|
|
74
|
+
const json = (data, status = 200) => Response.json(data, { status, headers: petPrivateHeaders });
|
|
75
|
+
try {
|
|
76
|
+
const scope = await options.resolve(request), gateway = config(scope), method = request.method;
|
|
77
|
+
const root = `/v1/knowledge/community-maps/collections/${scope.collectionSlug}`;
|
|
78
|
+
const id = path[0] && PET_UUID.test(path[0]);
|
|
79
|
+
const isMedia = id && path.length === 3 && path[1] === "media" && PET_UUID.test(path[2]);
|
|
80
|
+
const upload = path.length === 3 && path[0] === "submissions" && PET_UUID.test(path[1]) && path[2] === "media";
|
|
81
|
+
const ownMedia = path.length === 4 && path[0] === "submissions" && PET_UUID.test(path[1]) && path[2] === "media" && PET_UUID.test(path[3]);
|
|
82
|
+
const own = path[0] === "mine" && (path.length === 1 || path.length === 2 && PET_UUID.test(path[1]));
|
|
83
|
+
const preferences = path.length === 1 && path[0] === "preferences";
|
|
84
|
+
const reaction = id && path.length === 2 && path[1] === "reaction";
|
|
85
|
+
const report = id && path.length === 2 && path[1] === "reports";
|
|
86
|
+
const publicRead = method === "GET" && (!path.length || id && path.length === 1 || isMedia || path.length === 1 && path[0] === "community");
|
|
87
|
+
const read = publicRead || method === "GET" && (own || ownMedia || preferences || reaction);
|
|
88
|
+
const write = method === "POST" && (!path.length || upload || report || id && path.length === 2 && path[1] === "reunited") || method === "PUT" && (preferences || reaction) || ["PUT", "DELETE"].includes(method) && id && path.length === 1;
|
|
89
|
+
if (!read && !write) return json({ error: "Route not found" }, 404);
|
|
90
|
+
if (write && (!scope.writesEnabled || request.headers.get("origin") !== new URL(scope.origin).origin))
|
|
91
|
+
return json(
|
|
92
|
+
{ error: "Continue from the pet community\u2019s official website." },
|
|
93
|
+
403
|
|
94
|
+
);
|
|
95
|
+
if ((write || own || ownMedia || preferences || reaction) && !scope.accessToken)
|
|
96
|
+
return json({ error: "Sign in to continue." }, 401);
|
|
97
|
+
let endpoint = root + (path[0] === "community" ? "" : upload || ownMedia ? "/" + path.join("/") : "/pets" + (path.length ? "/" + path.join("/") : ""));
|
|
98
|
+
const query = new URLSearchParams();
|
|
99
|
+
if (method === "GET" && !path.length)
|
|
100
|
+
for (const key of [
|
|
101
|
+
"area_id",
|
|
102
|
+
"kind",
|
|
103
|
+
"after",
|
|
104
|
+
"week",
|
|
105
|
+
"limit",
|
|
106
|
+
"category",
|
|
107
|
+
"species",
|
|
108
|
+
"event_type",
|
|
109
|
+
"cost",
|
|
110
|
+
"starts_after",
|
|
111
|
+
"starts_before"
|
|
112
|
+
]) {
|
|
113
|
+
const value = new URL(request.url).searchParams.get(key);
|
|
114
|
+
if (value) query.set(key, value);
|
|
115
|
+
}
|
|
116
|
+
if (own) {
|
|
117
|
+
const after = new URL(request.url).searchParams.get("after");
|
|
118
|
+
if (after && PET_UUID.test(after)) query.set("after", after);
|
|
119
|
+
}
|
|
120
|
+
if (query.size) endpoint += "?" + query;
|
|
121
|
+
if (isMedia) {
|
|
122
|
+
const imageQuery = new URLSearchParams();
|
|
123
|
+
const sourceQuery = new URL(request.url).searchParams;
|
|
124
|
+
if (["640", "1280"].includes(sourceQuery.get("width") || ""))
|
|
125
|
+
imageQuery.set("width", sourceQuery.get("width"));
|
|
126
|
+
if (["jpeg", "webp"].includes(sourceQuery.get("format") || ""))
|
|
127
|
+
imageQuery.set("format", sourceQuery.get("format"));
|
|
128
|
+
if (imageQuery.size) endpoint += "?" + imageQuery;
|
|
129
|
+
}
|
|
130
|
+
if (isMedia || upload || ownMedia) {
|
|
131
|
+
let body2;
|
|
132
|
+
const type = request.headers.get("content-type") || "";
|
|
133
|
+
if (upload) {
|
|
134
|
+
if (!type.startsWith("multipart/form-data;"))
|
|
135
|
+
throw new PetRequestError(415, "Choose a photograph.");
|
|
136
|
+
body2 = await bounded(request, 9 * 1024 * 1024);
|
|
137
|
+
}
|
|
138
|
+
const response = await fetcher(gateway + endpoint, {
|
|
139
|
+
method,
|
|
140
|
+
headers: {
|
|
141
|
+
...buildClientContextHeaders(scope.clientContext),
|
|
142
|
+
"x-api-key": scope.apiKey,
|
|
143
|
+
...scope.accessToken ? { authorization: `Bearer ${scope.accessToken}` } : {},
|
|
144
|
+
...upload ? { "content-type": type } : {}
|
|
145
|
+
},
|
|
146
|
+
body: body2,
|
|
147
|
+
cache: "no-store",
|
|
148
|
+
redirect: "error",
|
|
149
|
+
signal: AbortSignal.timeout(45e3)
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
await response.body?.cancel();
|
|
153
|
+
return json(
|
|
154
|
+
{
|
|
155
|
+
error: "The photograph is unavailable. Check its size and try again."
|
|
156
|
+
},
|
|
157
|
+
response.status >= 500 ? 503 : response.status
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (upload) return json(await response.json());
|
|
161
|
+
const imageType = response.headers.get("content-type")?.split(";")[0] || "";
|
|
162
|
+
if (!["image/png", "image/jpeg", "image/webp"].includes(imageType)) {
|
|
163
|
+
await response.body?.cancel();
|
|
164
|
+
return json({ error: "Photograph unavailable" }, 404);
|
|
165
|
+
}
|
|
166
|
+
return new Response(response.body, {
|
|
167
|
+
headers: { ...petPrivateHeaders, "content-type": imageType }
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
let body;
|
|
171
|
+
if (write) {
|
|
172
|
+
if (!request.headers.get("content-type")?.startsWith("application/json"))
|
|
173
|
+
throw new PetRequestError(415, "Use the pet contribution form.");
|
|
174
|
+
try {
|
|
175
|
+
body = JSON.parse(
|
|
176
|
+
new TextDecoder().decode(await bounded(request, 32768))
|
|
177
|
+
);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof PetRequestError) throw error;
|
|
180
|
+
throw new PetRequestError(400, "Check the contribution fields.");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return json({
|
|
184
|
+
data: await petRequest(scope, endpoint, { method, body }, fetcher)
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return json(
|
|
188
|
+
{
|
|
189
|
+
error: error instanceof PetRequestError ? error.message : "The pet community is temporarily unavailable."
|
|
190
|
+
},
|
|
191
|
+
error instanceof PetRequestError ? error.status : 503
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function resolvePetReferences(scope, metadata, fetcher = fetch) {
|
|
197
|
+
const references = petReferences(metadata).filter(
|
|
198
|
+
(ref) => ref.collection_slug === scope.collectionSlug
|
|
199
|
+
);
|
|
200
|
+
const results = await Promise.all(
|
|
201
|
+
references.map(async (ref) => {
|
|
202
|
+
try {
|
|
203
|
+
const resource = await petRequest(
|
|
204
|
+
{ ...scope, accessToken: void 0 },
|
|
205
|
+
`/v1/knowledge/community-maps/collections/${ref.collection_slug}/pets/${ref.id}`,
|
|
206
|
+
{},
|
|
207
|
+
fetcher
|
|
208
|
+
);
|
|
209
|
+
return resource.kind === ref.kind ? resource : null;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof PetRequestError && [403, 404].includes(error.status))
|
|
212
|
+
return null;
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
return results.filter(
|
|
218
|
+
(resource) => resource !== null
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async function petAcknowledgments(scope, community, productPath = "/pets", fetcher = fetch) {
|
|
222
|
+
if (!scope.publicationId || !PET_UUID.test(scope.publicationId)) return [];
|
|
223
|
+
try {
|
|
224
|
+
const groups = await Promise.all(
|
|
225
|
+
["community_partner", "community_guide"].map(
|
|
226
|
+
async (placement) => {
|
|
227
|
+
const query = new URLSearchParams({
|
|
228
|
+
placement_type: placement,
|
|
229
|
+
product_path: productPath
|
|
230
|
+
});
|
|
231
|
+
if (community) query.set("community", community);
|
|
232
|
+
const response = await petRequest(
|
|
233
|
+
{ ...scope, accessToken: void 0 },
|
|
234
|
+
`/v1/blog/publications/${scope.publicationId}/sponsorships/delivery?${query}`,
|
|
235
|
+
{},
|
|
236
|
+
fetcher
|
|
237
|
+
);
|
|
238
|
+
return response.items.filter(
|
|
239
|
+
(item) => item.publication_id === scope.publicationId && item.placement_type === placement
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
return groups.flat().slice(0, 2);
|
|
245
|
+
} catch {
|
|
246
|
+
console.warn("paw.sponsorship_delivery_unavailable");
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { PetRequestError, createPublicationPetsHandler, petAcknowledgments, petPrivateHeaders, petRequest, resolvePetReferences };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/pets.ts
|
|
2
|
+
var PET_KINDS = [
|
|
3
|
+
"profile",
|
|
4
|
+
"spotlight",
|
|
5
|
+
"photo",
|
|
6
|
+
"celebration",
|
|
7
|
+
"lost",
|
|
8
|
+
"found",
|
|
9
|
+
"adoption",
|
|
10
|
+
"event"
|
|
11
|
+
];
|
|
12
|
+
var PET_NOTIFICATION_CATEGORIES = [
|
|
13
|
+
["lost_found", "Lost & found"],
|
|
14
|
+
["walks", "Paw Walks"],
|
|
15
|
+
["adoption_events", "Adoption events"],
|
|
16
|
+
["celebrations", "Pet celebrations"],
|
|
17
|
+
["spotlight", "Paw of the Week"],
|
|
18
|
+
["community", "Community information"]
|
|
19
|
+
];
|
|
20
|
+
var PET_PHOTO_CATEGORIES = [
|
|
21
|
+
["cute", "Cute pets"],
|
|
22
|
+
["funny", "Funny moments"],
|
|
23
|
+
["adventures", "Adventures"],
|
|
24
|
+
["senior", "Senior pets"],
|
|
25
|
+
["rescue", "Rescue stories"],
|
|
26
|
+
["young", "Puppies & kittens"],
|
|
27
|
+
["unusual", "Unusual pets"],
|
|
28
|
+
["friends", "Best friends"],
|
|
29
|
+
["holiday", "Holiday pets"]
|
|
30
|
+
];
|
|
31
|
+
var PET_CELEBRATIONS = [
|
|
32
|
+
["birthday", "Pet birthday"],
|
|
33
|
+
["adoption_anniversary", "Adoption anniversary"],
|
|
34
|
+
["welcome_home", "Welcome home"],
|
|
35
|
+
["rescue_story", "Rescue story"],
|
|
36
|
+
["happy_reunion", "Happy reunion"],
|
|
37
|
+
["memorial", "Memorial tribute"]
|
|
38
|
+
];
|
|
39
|
+
var PET_SAFETY = {
|
|
40
|
+
recovery: "Bay Area Paw does not independently verify pet ownership. A report is a contributor\u2019s account. Never send money or verification codes to someone claiming to have found a pet. Keep microchip numbers and ownership evidence private.",
|
|
41
|
+
walks: "Walks are independently organized. Bay Area Paw does not supervise meetups or guarantee participants\u2019 safety. Meet in a public place, follow leash rules and respect other people and animals.",
|
|
42
|
+
adoption: "Contact the named shelter or rescue for current availability, eligibility and adoption arrangements. Bay Area Paw is not the adoption agency and does not collect adoption payments.",
|
|
43
|
+
wellness: "Wellness information is educational. Contact a veterinarian for advice about your pet."
|
|
44
|
+
};
|
|
45
|
+
var PET_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
46
|
+
var PET_SLUG = /^[a-z0-9][a-z0-9-]{0,190}$/;
|
|
47
|
+
function petReferences(metadata) {
|
|
48
|
+
const entries = metadata && typeof metadata === "object" ? metadata.pet_resources : null;
|
|
49
|
+
if (!Array.isArray(entries)) return [];
|
|
50
|
+
return entries.slice(0, 12).flatMap((v) => {
|
|
51
|
+
if (!v || typeof v !== "object") return [];
|
|
52
|
+
const r = v;
|
|
53
|
+
return typeof r.collection_slug === "string" && PET_SLUG.test(r.collection_slug) && typeof r.id === "string" && PET_UUID.test(r.id) && PET_KINDS.includes(r.kind) ? [
|
|
54
|
+
{
|
|
55
|
+
collection_slug: r.collection_slug,
|
|
56
|
+
id: r.id,
|
|
57
|
+
kind: r.kind
|
|
58
|
+
}
|
|
59
|
+
] : [];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolvePetCommunity(areas, selected, preference, publicationDefault) {
|
|
63
|
+
if (selected === "" || selected === void 0 && preference === "all") return void 0;
|
|
64
|
+
for (const candidate of [selected, preference, publicationDefault]) {
|
|
65
|
+
const found = areas.find(
|
|
66
|
+
(area) => area.id === candidate || area.slug === candidate
|
|
67
|
+
);
|
|
68
|
+
if (found) return found;
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
function safePetLink(raw) {
|
|
73
|
+
if (raw.startsWith("/") && !raw.startsWith("//") && !/[\\\r\n]/.test(raw))
|
|
74
|
+
return raw;
|
|
75
|
+
try {
|
|
76
|
+
const url = new URL(raw);
|
|
77
|
+
return url.protocol === "https:" && !url.username && !url.password ? url.href : void 0;
|
|
78
|
+
} catch {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function adoptionAvailability(source, now = Date.now()) {
|
|
83
|
+
const at = source?.verified_at ? Date.parse(source.verified_at) : NaN;
|
|
84
|
+
return source?.availability_confirmed && Number.isFinite(at) && at <= now && now - at <= 864e5 ? "confirmed" : "check_with_organization";
|
|
85
|
+
}
|
|
86
|
+
function petProfileMetadata(resource, canonicalUrl, photoUrl) {
|
|
87
|
+
const canonical = safePetLink(canonicalUrl);
|
|
88
|
+
if (!resource || !canonical?.startsWith("https://"))
|
|
89
|
+
return {
|
|
90
|
+
title: "Pet profile unavailable",
|
|
91
|
+
robots: { index: false, follow: false }
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
title: `Meet ${resource.content.name}`,
|
|
95
|
+
description: Array.from(resource.content.story).slice(0, 180).join(""),
|
|
96
|
+
alternates: { canonical },
|
|
97
|
+
openGraph: {
|
|
98
|
+
title: `Meet ${resource.content.name}`,
|
|
99
|
+
description: resource.content.story,
|
|
100
|
+
url: canonical,
|
|
101
|
+
...photoUrl && safePetLink(photoUrl) ? { images: [{ url: photoUrl, alt: resource.content.name }] } : {}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function preparePawWalk(value) {
|
|
106
|
+
if (!Number.isInteger(value.duration_minutes) || value.duration_minutes < 10 || value.duration_minutes > 240)
|
|
107
|
+
throw new Error("Choose a walk duration from 10 to 240 minutes.");
|
|
108
|
+
if (value.distance_km !== void 0 && (!Number.isFinite(value.distance_km) || value.distance_km < 0 || value.distance_km > 30))
|
|
109
|
+
throw new Error("Choose a walking distance from 0 to 30 km.");
|
|
110
|
+
if (value.leash !== "required" && value.leash !== "venue_rules")
|
|
111
|
+
throw new Error("Choose the leash requirements.");
|
|
112
|
+
for (const field of [value.size_guidance, value.accessibility])
|
|
113
|
+
if (typeof field !== "string" || Array.from(field).length > 500)
|
|
114
|
+
throw new Error("Keep walk guidance within 500 characters.");
|
|
115
|
+
return {
|
|
116
|
+
duration_minutes: value.duration_minutes,
|
|
117
|
+
distance_km: value.distance_km,
|
|
118
|
+
leash: value.leash,
|
|
119
|
+
size_guidance: value.size_guidance,
|
|
120
|
+
accessibility: value.accessibility
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { PET_CELEBRATIONS, PET_KINDS, PET_NOTIFICATION_CATEGORIES, PET_PHOTO_CATEGORIES, PET_SAFETY, PET_SLUG, PET_UUID, adoptionAvailability, petProfileMetadata, petReferences, preparePawWalk, resolvePetCommunity, safePetLink };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { preparePawWalk } from './chunk-AFJ6S5WJ.js';
|
|
2
|
+
|
|
1
3
|
// src/meet.ts
|
|
2
4
|
var MEET_ACTIVITIES = [
|
|
3
5
|
{ value: "coffee", label: "Coffee" },
|
|
@@ -57,6 +59,13 @@ function prepareMeetup(input, places, now = /* @__PURE__ */ new Date()) {
|
|
|
57
59
|
if (!notes.ok) throw new Error(notes.reason);
|
|
58
60
|
const capacity = Number(input.capacity ?? 6);
|
|
59
61
|
if (!Number.isInteger(capacity) || capacity < 2 || capacity > 12) throw new Error("Choose a group size from 2 to 12.");
|
|
62
|
+
const walk = activity === "dogs" && input.walk ? preparePawWalk(input.walk) : void 0;
|
|
63
|
+
if (walk) for (const guidance of [walk.size_guidance, walk.accessibility]) {
|
|
64
|
+
if (guidance.trim()) {
|
|
65
|
+
const screened = screenMessage(guidance);
|
|
66
|
+
if (!screened.ok) throw new Error(screened.reason);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
60
69
|
return {
|
|
61
70
|
activity,
|
|
62
71
|
activityLabel: label,
|
|
@@ -65,7 +74,8 @@ function prepareMeetup(input, places, now = /* @__PURE__ */ new Date()) {
|
|
|
65
74
|
placeDetail: place.detail,
|
|
66
75
|
startsAt: new Date(starts).toISOString(),
|
|
67
76
|
notes: notes.text,
|
|
68
|
-
capacity
|
|
77
|
+
capacity,
|
|
78
|
+
...walk ? { walk } : {}
|
|
69
79
|
};
|
|
70
80
|
}
|
|
71
81
|
|