@scalemule/news-publication 0.0.48 → 0.0.49

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 (72) hide show
  1. package/README.md +28 -0
  2. package/dist/{chunk-GB36YM56.js → chunk-2MEYHM7M.js} +11 -1
  3. package/dist/chunk-4JRWZXAP.js +62 -0
  4. package/dist/chunk-MBBPEFDA.js +124 -0
  5. package/dist/chunk-TTLM7YOH.js +444 -0
  6. package/dist/chunk-ZXH3O537.js +251 -0
  7. package/dist/community-maps-view.d.cts +3 -3
  8. package/dist/community-maps-view.d.ts +3 -3
  9. package/dist/cookie-consent-banner.d.cts +2 -2
  10. package/dist/cookie-consent-banner.d.ts +2 -2
  11. package/dist/cookie-settings-view.d.cts +2 -2
  12. package/dist/cookie-settings-view.d.ts +2 -2
  13. package/dist/index.cjs +29 -1
  14. package/dist/index.d.cts +4 -3
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +4 -3
  17. package/dist/marketplace-forms.d.cts +3 -3
  18. package/dist/marketplace-forms.d.ts +3 -3
  19. package/dist/marketplace-view.d.cts +5 -5
  20. package/dist/marketplace-view.d.ts +5 -5
  21. package/dist/{meet-RWmfj3I3.d.cts → meet-BWpxY20X.d.ts} +5 -0
  22. package/dist/{meet-RWmfj3I3.d.ts → meet-CO9Unz5M.d.cts} +5 -0
  23. package/dist/meet-forms.d.cts +7 -6
  24. package/dist/meet-forms.d.ts +7 -6
  25. package/dist/meet-forms.js +2 -1
  26. package/dist/meet-view.cjs +22 -2
  27. package/dist/meet-view.d.cts +7 -5
  28. package/dist/meet-view.d.ts +7 -5
  29. package/dist/meet-view.js +21 -4
  30. package/dist/network-bar.d.cts +2 -2
  31. package/dist/network-bar.d.ts +2 -2
  32. package/dist/network-copyright.d.cts +2 -2
  33. package/dist/network-copyright.d.ts +2 -2
  34. package/dist/pets-auth-server.cjs +212 -0
  35. package/dist/pets-auth-server.d.cts +17 -0
  36. package/dist/pets-auth-server.d.ts +17 -0
  37. package/dist/pets-auth-server.js +155 -0
  38. package/dist/pets-forms.cjs +762 -0
  39. package/dist/pets-forms.d.cts +26 -0
  40. package/dist/pets-forms.d.ts +26 -0
  41. package/dist/pets-forms.js +712 -0
  42. package/dist/pets-interactions.cjs +332 -0
  43. package/dist/pets-interactions.d.cts +20 -0
  44. package/dist/pets-interactions.d.ts +20 -0
  45. package/dist/pets-interactions.js +319 -0
  46. package/dist/pets-publication.cjs +818 -0
  47. package/dist/pets-publication.d.cts +36 -0
  48. package/dist/pets-publication.d.ts +36 -0
  49. package/dist/pets-publication.js +177 -0
  50. package/dist/pets-server.cjs +289 -0
  51. package/dist/pets-server.d.cts +42 -0
  52. package/dist/pets-server.d.ts +42 -0
  53. package/dist/pets-server.js +2 -0
  54. package/dist/pets-view.cjs +541 -0
  55. package/dist/pets-view.d.cts +56 -0
  56. package/dist/pets-view.d.ts +56 -0
  57. package/dist/pets-view.js +4 -0
  58. package/dist/pets.cjs +138 -0
  59. package/dist/pets.d.cts +127 -0
  60. package/dist/pets.d.ts +127 -0
  61. package/dist/pets.js +1 -0
  62. package/dist/sponsorship-view.d.cts +2 -2
  63. package/dist/sponsorship-view.d.ts +2 -2
  64. package/dist/sponsorship-view.js +2 -62
  65. package/dist/story-review.d.cts +6 -6
  66. package/dist/story-review.d.ts +6 -6
  67. package/dist/theme-toggle.d.cts +2 -2
  68. package/dist/theme-toggle.d.ts +2 -2
  69. package/dist/verified-card.d.cts +3 -3
  70. package/dist/verified-card.d.ts +3 -3
  71. package/package.json +40 -3
  72. package/pets.css +538 -0
@@ -1,13 +1,14 @@
1
- import * as react from 'react';
2
- import { P as PublicPlace } from './meet-RWmfj3I3.cjs';
1
+ import * as React from 'react';
2
+ import { P as PublicPlace } from './meet-CO9Unz5M.cjs';
3
+ import './pets.cjs';
3
4
 
4
5
  declare function MeetHostForm({ places }: {
5
6
  places: PublicPlace[];
6
- }): react.JSX.Element;
7
- declare function PhoneCheckForm(): react.JSX.Element;
8
- declare function BirthdayForm(): react.JSX.Element;
7
+ }): React.JSX.Element;
8
+ declare function PhoneCheckForm(): React.JSX.Element;
9
+ declare function BirthdayForm(): React.JSX.Element;
9
10
  declare function MeetupChat({ meetupId }: {
10
11
  meetupId: string;
11
- }): react.JSX.Element;
12
+ }): React.JSX.Element;
12
13
 
13
14
  export { BirthdayForm, MeetHostForm, MeetupChat, PhoneCheckForm };
@@ -1,13 +1,14 @@
1
- import * as react from 'react';
2
- import { P as PublicPlace } from './meet-RWmfj3I3.js';
1
+ import * as React from 'react';
2
+ import { P as PublicPlace } from './meet-BWpxY20X.js';
3
+ import './pets.js';
3
4
 
4
5
  declare function MeetHostForm({ places }: {
5
6
  places: PublicPlace[];
6
- }): react.JSX.Element;
7
- declare function PhoneCheckForm(): react.JSX.Element;
8
- declare function BirthdayForm(): react.JSX.Element;
7
+ }): React.JSX.Element;
8
+ declare function PhoneCheckForm(): React.JSX.Element;
9
+ declare function BirthdayForm(): React.JSX.Element;
9
10
  declare function MeetupChat({ meetupId }: {
10
11
  meetupId: string;
11
- }): react.JSX.Element;
12
+ }): React.JSX.Element;
12
13
 
13
14
  export { BirthdayForm, MeetHostForm, MeetupChat, PhoneCheckForm };
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { MEET_ACTIVITIES } from './chunk-GB36YM56.js';
2
+ import { MEET_ACTIVITIES } from './chunk-2MEYHM7M.js';
3
+ import './chunk-MBBPEFDA.js';
3
4
  import { useState } from 'react';
4
5
  import { jsxs, jsx } from 'react/jsx-runtime';
5
6
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
 
5
+ // src/pets.ts
6
+ var PET_SAFETY = {
7
+ 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."};
8
+
5
9
  // src/meet.ts
6
10
  var MEET_ACTIVITIES = [
7
11
  { value: "coffee", label: "Coffee" },
@@ -69,8 +73,24 @@ function MeetupDetail({ meetup, joined }) {
69
73
  meetup.capacity
70
74
  ] }),
71
75
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "body", children: meetup.notes }),
72
- !joined && meetup.going < meetup.capacity && /* @__PURE__ */ jsxRuntime.jsx("form", { className: "actions", method: "post", action: `/api/meet/${encodeURIComponent(meetup.id)}/join`, children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "primary", type: "submit", children: "I\u2019ll be there" }) }),
73
- joined && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "meta", children: "You\u2019re going. Use the chat below. Keep phone numbers for when you meet." })
76
+ meetup.activity === "dogs" && /* @__PURE__ */ jsxRuntime.jsxs("aside", { children: [
77
+ meetup.walk && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
78
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
79
+ meetup.walk.duration_minutes,
80
+ " minutes",
81
+ meetup.walk.distance_km !== void 0 ? ` \xB7 ${meetup.walk.distance_km} km` : "",
82
+ " \xB7 ",
83
+ meetup.walk.leash === "required" ? "Leashes required" : "Follow venue leash rules"
84
+ ] }),
85
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: meetup.walk.size_guidance }),
86
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: meetup.walk.accessibility })
87
+ ] }),
88
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: PET_SAFETY.walks })
89
+ ] }),
90
+ meetup.status === "cancelled" && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "This meetup has been cancelled." }),
91
+ meetup.status !== "cancelled" && !joined && meetup.going < meetup.capacity && /* @__PURE__ */ jsxRuntime.jsx("form", { className: "actions", method: "post", action: `/api/meet/${encodeURIComponent(meetup.id)}/join`, children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "primary", type: "submit", children: "I\u2019ll be there" }) }),
92
+ meetup.status !== "cancelled" && joined && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "meta", children: "You\u2019re going. Use the chat below. Keep phone numbers for when you meet." }),
93
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/corrections?resource=" + encodeURIComponent(`/meet/${meetup.id}`), children: "Report this meetup to the publication" }) })
74
94
  ] });
75
95
  }
76
96
  function activityChoices() {
@@ -1,22 +1,24 @@
1
- import * as react from 'react';
2
- import { a as PreparedMeetup } from './meet-RWmfj3I3.cjs';
1
+ import * as React from 'react';
2
+ import { a as PreparedMeetup } from './meet-CO9Unz5M.cjs';
3
+ import './pets.cjs';
3
4
 
4
5
  type MeetupCard = PreparedMeetup & {
5
6
  id: string;
6
7
  hostName: string;
7
8
  going: number;
8
9
  conversationId?: string | null;
10
+ status?: "open" | "cancelled";
9
11
  };
10
12
  declare function MeetBoard({ region, meetups }: {
11
13
  region: string;
12
14
  meetups: MeetupCard[];
13
- }): react.JSX.Element;
15
+ }): React.JSX.Element;
14
16
  declare function MeetupDetail({ meetup, joined }: {
15
17
  meetup: MeetupCard;
16
18
  joined: boolean;
17
- }): react.JSX.Element;
19
+ }): React.JSX.Element;
18
20
  declare function activityChoices(): {
19
- value: "coffee" | "walk" | "hike" | "pickleball" | "tennis" | "soccer" | "bike" | "dogs" | "books" | "games" | "kids" | "volunteer" | "other";
21
+ value: "other" | "walk" | "coffee" | "hike" | "pickleball" | "tennis" | "soccer" | "bike" | "dogs" | "books" | "games" | "kids" | "volunteer";
20
22
  label: string;
21
23
  }[];
22
24
 
@@ -1,22 +1,24 @@
1
- import * as react from 'react';
2
- import { a as PreparedMeetup } from './meet-RWmfj3I3.js';
1
+ import * as React from 'react';
2
+ import { a as PreparedMeetup } from './meet-BWpxY20X.js';
3
+ import './pets.js';
3
4
 
4
5
  type MeetupCard = PreparedMeetup & {
5
6
  id: string;
6
7
  hostName: string;
7
8
  going: number;
8
9
  conversationId?: string | null;
10
+ status?: "open" | "cancelled";
9
11
  };
10
12
  declare function MeetBoard({ region, meetups }: {
11
13
  region: string;
12
14
  meetups: MeetupCard[];
13
- }): react.JSX.Element;
15
+ }): React.JSX.Element;
14
16
  declare function MeetupDetail({ meetup, joined }: {
15
17
  meetup: MeetupCard;
16
18
  joined: boolean;
17
- }): react.JSX.Element;
19
+ }): React.JSX.Element;
18
20
  declare function activityChoices(): {
19
- value: "coffee" | "walk" | "hike" | "pickleball" | "tennis" | "soccer" | "bike" | "dogs" | "books" | "games" | "kids" | "volunteer" | "other";
21
+ value: "other" | "walk" | "coffee" | "hike" | "pickleball" | "tennis" | "soccer" | "bike" | "dogs" | "books" | "games" | "kids" | "volunteer";
20
22
  label: string;
21
23
  }[];
22
24
 
package/dist/meet-view.js CHANGED
@@ -1,5 +1,6 @@
1
- import { MEET_ACTIVITIES, activityLabel } from './chunk-GB36YM56.js';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { MEET_ACTIVITIES, activityLabel } from './chunk-2MEYHM7M.js';
2
+ import { PET_SAFETY } from './chunk-MBBPEFDA.js';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
4
 
4
5
  function MeetBoard({ region, meetups }) {
5
6
  return /* @__PURE__ */ jsxs("div", { className: "np-market", children: [
@@ -48,8 +49,24 @@ function MeetupDetail({ meetup, joined }) {
48
49
  meetup.capacity
49
50
  ] }),
50
51
  /* @__PURE__ */ jsx("div", { className: "body", children: meetup.notes }),
51
- !joined && meetup.going < meetup.capacity && /* @__PURE__ */ jsx("form", { className: "actions", method: "post", action: `/api/meet/${encodeURIComponent(meetup.id)}/join`, children: /* @__PURE__ */ jsx("button", { className: "primary", type: "submit", children: "I\u2019ll be there" }) }),
52
- joined && /* @__PURE__ */ jsx("p", { className: "meta", children: "You\u2019re going. Use the chat below. Keep phone numbers for when you meet." })
52
+ meetup.activity === "dogs" && /* @__PURE__ */ jsxs("aside", { children: [
53
+ meetup.walk && /* @__PURE__ */ jsxs(Fragment, { children: [
54
+ /* @__PURE__ */ jsxs("p", { children: [
55
+ meetup.walk.duration_minutes,
56
+ " minutes",
57
+ meetup.walk.distance_km !== void 0 ? ` \xB7 ${meetup.walk.distance_km} km` : "",
58
+ " \xB7 ",
59
+ meetup.walk.leash === "required" ? "Leashes required" : "Follow venue leash rules"
60
+ ] }),
61
+ /* @__PURE__ */ jsx("p", { children: meetup.walk.size_guidance }),
62
+ /* @__PURE__ */ jsx("p", { children: meetup.walk.accessibility })
63
+ ] }),
64
+ /* @__PURE__ */ jsx("p", { children: PET_SAFETY.walks })
65
+ ] }),
66
+ meetup.status === "cancelled" && /* @__PURE__ */ jsx("p", { role: "status", children: "This meetup has been cancelled." }),
67
+ meetup.status !== "cancelled" && !joined && meetup.going < meetup.capacity && /* @__PURE__ */ jsx("form", { className: "actions", method: "post", action: `/api/meet/${encodeURIComponent(meetup.id)}/join`, children: /* @__PURE__ */ jsx("button", { className: "primary", type: "submit", children: "I\u2019ll be there" }) }),
68
+ meetup.status !== "cancelled" && joined && /* @__PURE__ */ jsx("p", { className: "meta", children: "You\u2019re going. Use the chat below. Keep phone numbers for when you meet." }),
69
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", { href: "/corrections?resource=" + encodeURIComponent(`/meet/${meetup.id}`), children: "Report this meetup to the publication" }) })
53
70
  ] });
54
71
  }
55
72
  function activityChoices() {
@@ -1,4 +1,4 @@
1
- import * as react from 'react';
1
+ import * as React from 'react';
2
2
  import { CSSProperties } from 'react';
3
3
 
4
4
  interface LocalNetworkBarProps {
@@ -10,6 +10,6 @@ interface LocalNetworkBarProps {
10
10
  * Global navigation bar for the Bay Area Local News network.
11
11
  * Links across independent publication origins, transferring consent tokens automatically.
12
12
  */
13
- declare function LocalNetworkBar({ currentSlug, className, style }: LocalNetworkBarProps): react.JSX.Element;
13
+ declare function LocalNetworkBar({ currentSlug, className, style }: LocalNetworkBarProps): React.JSX.Element;
14
14
 
15
15
  export { LocalNetworkBar, type LocalNetworkBarProps };
@@ -1,4 +1,4 @@
1
- import * as react from 'react';
1
+ import * as React from 'react';
2
2
  import { CSSProperties } from 'react';
3
3
 
4
4
  interface LocalNetworkBarProps {
@@ -10,6 +10,6 @@ interface LocalNetworkBarProps {
10
10
  * Global navigation bar for the Bay Area Local News network.
11
11
  * Links across independent publication origins, transferring consent tokens automatically.
12
12
  */
13
- declare function LocalNetworkBar({ currentSlug, className, style }: LocalNetworkBarProps): react.JSX.Element;
13
+ declare function LocalNetworkBar({ currentSlug, className, style }: LocalNetworkBarProps): React.JSX.Element;
14
14
 
15
15
  export { LocalNetworkBar, type LocalNetworkBarProps };
@@ -1,4 +1,4 @@
1
- import react__default, { CSSProperties } from 'react';
1
+ import React__default, { CSSProperties } from 'react';
2
2
 
3
3
  interface NetworkCopyrightNoticeProps {
4
4
  className?: string;
@@ -13,6 +13,6 @@ interface NetworkCopyrightNoticeProps {
13
13
  * Reusable copyright and institutional network parent attribution notice for all ScaleMule news publications.
14
14
  * Links to the official Bay Area News Network institutional website.
15
15
  */
16
- declare function NetworkCopyrightNotice({ className, style, publicationName, showPublicationNotice, targetBlank, prefix, showRightsReserved, }: NetworkCopyrightNoticeProps): react__default.JSX.Element;
16
+ declare function NetworkCopyrightNotice({ className, style, publicationName, showPublicationNotice, targetBlank, prefix, showRightsReserved, }: NetworkCopyrightNoticeProps): React__default.JSX.Element;
17
17
 
18
18
  export { NetworkCopyrightNotice, type NetworkCopyrightNoticeProps };
@@ -1,4 +1,4 @@
1
- import react__default, { CSSProperties } from 'react';
1
+ import React__default, { CSSProperties } from 'react';
2
2
 
3
3
  interface NetworkCopyrightNoticeProps {
4
4
  className?: string;
@@ -13,6 +13,6 @@ interface NetworkCopyrightNoticeProps {
13
13
  * Reusable copyright and institutional network parent attribution notice for all ScaleMule news publications.
14
14
  * Links to the official Bay Area News Network institutional website.
15
15
  */
16
- declare function NetworkCopyrightNotice({ className, style, publicationName, showPublicationNotice, targetBlank, prefix, showRightsReserved, }: NetworkCopyrightNoticeProps): react__default.JSX.Element;
16
+ declare function NetworkCopyrightNotice({ className, style, publicationName, showPublicationNotice, targetBlank, prefix, showRightsReserved, }: NetworkCopyrightNoticeProps): React__default.JSX.Element;
17
17
 
18
18
  export { NetworkCopyrightNotice, type NetworkCopyrightNoticeProps };
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var sdk = require('@scalemule/sdk');
5
+
6
+ // src/pets-auth-server.ts
7
+
8
+ // src/pets.ts
9
+ var PET_SLUG = /^[a-z0-9][a-z0-9-]{0,190}$/;
10
+
11
+ // src/pets-server.ts
12
+ var PetRequestError = class extends Error {
13
+ constructor(status, message) {
14
+ super(message);
15
+ this.status = status;
16
+ }
17
+ status;
18
+ };
19
+ var petPrivateHeaders = {
20
+ "cache-control": "private, no-store",
21
+ "x-robots-tag": "noindex, nofollow",
22
+ "referrer-policy": "no-referrer",
23
+ "x-content-type-options": "nosniff"
24
+ };
25
+ function config(scope) {
26
+ const url = new URL(scope.gatewayUrl);
27
+ 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)))
28
+ throw new PetRequestError(503, "The pet community is not available yet.");
29
+ return url.origin;
30
+ }
31
+ async function petRequest(scope, path, init = {}, fetcher = fetch) {
32
+ const gateway = config(scope);
33
+ if (!path.startsWith("/v1/") || path.includes("..") || /[\\\r\n]/.test(path))
34
+ throw new Error("Invalid platform path");
35
+ const response = await fetcher(gateway + path, {
36
+ method: init.method || "GET",
37
+ headers: {
38
+ ...sdk.buildClientContextHeaders(scope.clientContext),
39
+ "x-api-key": scope.apiKey,
40
+ "content-type": "application/json",
41
+ ...scope.accessToken ? { authorization: `Bearer ${scope.accessToken}` } : {}
42
+ },
43
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
44
+ cache: "no-store",
45
+ redirect: "error",
46
+ signal: AbortSignal.timeout(15e3)
47
+ });
48
+ if (!response.ok)
49
+ throw new PetRequestError(
50
+ response.status,
51
+ 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."
52
+ );
53
+ const json = await response.json();
54
+ return json?.data ?? json;
55
+ }
56
+
57
+ // src/pets-auth-server.ts
58
+ var PAW_SESSION_COOKIE = "__Host-paw-reader";
59
+ var FLOW = "__Host-paw-signin";
60
+ var attrs = "Path=/; HttpOnly; Secure; SameSite=Lax";
61
+ function pawCookie(request, name = PAW_SESSION_COOKIE) {
62
+ return request.headers.get("cookie")?.split(";").map((v) => v.trim()).find((v) => v.startsWith(name + "="))?.slice(name.length + 1);
63
+ }
64
+ function digest(value) {
65
+ return crypto.createHash("sha256").update(value).digest("hex");
66
+ }
67
+ function returnPath(value) {
68
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//") && value.length <= 500 && !/[\\\r\n]/.test(value) ? value : "/account";
69
+ }
70
+ function sealPawFlow(state, next, key, now = Date.now()) {
71
+ const payload = Buffer.from(
72
+ JSON.stringify({
73
+ state: digest(state),
74
+ next: returnPath(next),
75
+ expires: now + 6e5
76
+ })
77
+ ).toString("base64url");
78
+ return payload + "." + crypto.createHmac("sha256", key).update("paw-signin:v1:" + payload).digest("hex");
79
+ }
80
+ function openPawFlow(raw, state, key, now = Date.now()) {
81
+ if (!key || !raw || raw.length > 2048 || !state || state.length > 2048)
82
+ return null;
83
+ const [payload, signature, extra] = raw.split(".");
84
+ if (!payload || extra || !/^[a-f0-9]{64}$/.test(signature || "")) return null;
85
+ if (!crypto.timingSafeEqual(
86
+ Buffer.from(signature, "hex"),
87
+ crypto.createHmac("sha256", key).update("paw-signin:v1:" + payload).digest()
88
+ ))
89
+ return null;
90
+ try {
91
+ const flow = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
92
+ if (!Number.isFinite(flow.expires) || flow.expires <= now || !/^[a-f0-9]{64}$/.test(flow.state) || !crypto.timingSafeEqual(
93
+ Buffer.from(flow.state, "hex"),
94
+ Buffer.from(digest(state), "hex")
95
+ ))
96
+ return null;
97
+ return returnPath(flow.next);
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function createPawAuthHandler(options) {
103
+ return async (request, action) => {
104
+ const json = (body, status = 200) => Response.json(body, { status, headers: petPrivateHeaders });
105
+ try {
106
+ if (!options.enabled)
107
+ return json({ error: "Community sign-in is not available yet." }, 503);
108
+ const scope = await options.resolve(request), origin = new URL(scope.origin).origin;
109
+ if (!origin.startsWith("https://") || !scope.apiKey)
110
+ return json({ error: "Sign-in is not configured." }, 503);
111
+ const redirect = (path) => new Response(null, {
112
+ status: 303,
113
+ headers: {
114
+ ...petPrivateHeaders,
115
+ location: origin + returnPath(path)
116
+ }
117
+ });
118
+ if (action === "start" && request.method === "POST") {
119
+ if (request.headers.get("origin") !== origin)
120
+ return json({ error: "Sign in from this website." }, 403);
121
+ const callback = origin + "/api/paw-auth/callback";
122
+ const result = await petRequest(
123
+ scope,
124
+ "/v1/auth/oauth/ledvery/authorize?" + new URLSearchParams({ redirect_uri: callback }),
125
+ {},
126
+ options.fetch
127
+ );
128
+ const url = new URL(result.url);
129
+ if (url.origin !== new URL(options.issuerOrigin).origin || url.pathname !== "/authorize" || url.username || url.password || url.searchParams.get("state") !== result.state || url.searchParams.get("redirect_uri") !== callback || url.searchParams.get("code_challenge_method") !== "S256" || !url.searchParams.get("code_challenge"))
130
+ throw new Error("Invalid authorization response");
131
+ const response = json({ url: url.href });
132
+ response.headers.append(
133
+ "set-cookie",
134
+ `${FLOW}=${sealPawFlow(result.state, returnPath(new URL(request.url).searchParams.get("next")), scope.apiKey)}; ${attrs}; Max-Age=600`
135
+ );
136
+ return response;
137
+ }
138
+ if (action === "callback" && request.method === "GET") {
139
+ const query = new URL(request.url).searchParams, code = query.get("code"), state = query.get("state");
140
+ const next = openPawFlow(pawCookie(request, FLOW), state, scope.apiKey);
141
+ const response = redirect(next || "/account?signin=retry");
142
+ response.headers.append("set-cookie", `${FLOW}=; ${attrs}; Max-Age=0`);
143
+ if (!next || !code || code.length > 4096 || query.has("error"))
144
+ return response;
145
+ try {
146
+ const session = await petRequest(
147
+ scope,
148
+ "/v1/auth/oauth/ledvery/callback?" + new URLSearchParams({ code, state }),
149
+ {},
150
+ options.fetch
151
+ );
152
+ const expiry = Date.parse(session.expires_at);
153
+ if (!session.session_token || !/^[A-Za-z0-9._~-]{1,2048}$/.test(session.session_token) || !Number.isFinite(expiry) || expiry <= Date.now())
154
+ throw new Error("Invalid session");
155
+ const me = await petRequest(
156
+ { ...scope, accessToken: session.session_token },
157
+ "/v1/auth/me",
158
+ {},
159
+ options.fetch
160
+ );
161
+ if (!me.id || !me.email_verified)
162
+ throw new Error("Verified reader required");
163
+ response.headers.append(
164
+ "set-cookie",
165
+ `${PAW_SESSION_COOKIE}=${session.session_token}; ${attrs}; Expires=${new Date(expiry).toUTCString()}`
166
+ );
167
+ return response;
168
+ } catch {
169
+ response.headers.set("location", origin + "/account?signin=retry");
170
+ return response;
171
+ }
172
+ }
173
+ if (action === "session" && request.method === "GET") {
174
+ if (!scope.accessToken) return json({ authenticated: false });
175
+ const me = await petRequest(scope, "/v1/auth/me", {}, options.fetch);
176
+ return json({
177
+ authenticated: Boolean(me.id && me.email_verified),
178
+ name: me.full_name || "Neighbor"
179
+ });
180
+ }
181
+ if (action === "logout" && request.method === "POST") {
182
+ if (request.headers.get("origin") !== origin)
183
+ return json({ error: "Continue from this website." }, 403);
184
+ if (scope.accessToken)
185
+ await petRequest(
186
+ scope,
187
+ "/v1/auth/logout",
188
+ { method: "POST", body: { session_token: scope.accessToken } },
189
+ options.fetch
190
+ );
191
+ const response = json({ authenticated: false });
192
+ response.headers.append(
193
+ "set-cookie",
194
+ `${PAW_SESSION_COOKIE}=; ${attrs}; Max-Age=0`
195
+ );
196
+ return response;
197
+ }
198
+ return json({ error: "Route not found" }, 404);
199
+ } catch {
200
+ return json(
201
+ { error: "Sign-in is temporarily unavailable. Please try again." },
202
+ 503
203
+ );
204
+ }
205
+ };
206
+ }
207
+
208
+ exports.PAW_SESSION_COOKIE = PAW_SESSION_COOKIE;
209
+ exports.createPawAuthHandler = createPawAuthHandler;
210
+ exports.openPawFlow = openPawFlow;
211
+ exports.pawCookie = pawCookie;
212
+ exports.sealPawFlow = sealPawFlow;
@@ -0,0 +1,17 @@
1
+ import { PetScope } from './pets-server.cjs';
2
+ import '@scalemule/sdk';
3
+ import './sponsorships-CtnBe3S8.cjs';
4
+ import './pets.cjs';
5
+
6
+ declare const PAW_SESSION_COOKIE = "__Host-paw-reader";
7
+ declare function pawCookie(request: Request, name?: string): string | undefined;
8
+ declare function sealPawFlow(state: string, next: string, key: string, now?: number): string;
9
+ declare function openPawFlow(raw: string | undefined, state: string | null, key: string, now?: number): string | null;
10
+ declare function createPawAuthHandler(options: {
11
+ resolve(request: Request): Promise<PetScope>;
12
+ enabled: boolean;
13
+ issuerOrigin: string;
14
+ fetch?: typeof fetch;
15
+ }): (request: Request, action: string) => Promise<Response>;
16
+
17
+ export { PAW_SESSION_COOKIE, createPawAuthHandler, openPawFlow, pawCookie, sealPawFlow };
@@ -0,0 +1,17 @@
1
+ import { PetScope } from './pets-server.js';
2
+ import '@scalemule/sdk';
3
+ import './sponsorships-CtnBe3S8.js';
4
+ import './pets.js';
5
+
6
+ declare const PAW_SESSION_COOKIE = "__Host-paw-reader";
7
+ declare function pawCookie(request: Request, name?: string): string | undefined;
8
+ declare function sealPawFlow(state: string, next: string, key: string, now?: number): string;
9
+ declare function openPawFlow(raw: string | undefined, state: string | null, key: string, now?: number): string | null;
10
+ declare function createPawAuthHandler(options: {
11
+ resolve(request: Request): Promise<PetScope>;
12
+ enabled: boolean;
13
+ issuerOrigin: string;
14
+ fetch?: typeof fetch;
15
+ }): (request: Request, action: string) => Promise<Response>;
16
+
17
+ export { PAW_SESSION_COOKIE, createPawAuthHandler, openPawFlow, pawCookie, sealPawFlow };
@@ -0,0 +1,155 @@
1
+ import { petPrivateHeaders, petRequest } from './chunk-ZXH3O537.js';
2
+ import './chunk-MBBPEFDA.js';
3
+ import { createHmac, timingSafeEqual, createHash } from 'crypto';
4
+
5
+ var PAW_SESSION_COOKIE = "__Host-paw-reader";
6
+ var FLOW = "__Host-paw-signin";
7
+ var attrs = "Path=/; HttpOnly; Secure; SameSite=Lax";
8
+ function pawCookie(request, name = PAW_SESSION_COOKIE) {
9
+ return request.headers.get("cookie")?.split(";").map((v) => v.trim()).find((v) => v.startsWith(name + "="))?.slice(name.length + 1);
10
+ }
11
+ function digest(value) {
12
+ return createHash("sha256").update(value).digest("hex");
13
+ }
14
+ function returnPath(value) {
15
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//") && value.length <= 500 && !/[\\\r\n]/.test(value) ? value : "/account";
16
+ }
17
+ function sealPawFlow(state, next, key, now = Date.now()) {
18
+ const payload = Buffer.from(
19
+ JSON.stringify({
20
+ state: digest(state),
21
+ next: returnPath(next),
22
+ expires: now + 6e5
23
+ })
24
+ ).toString("base64url");
25
+ return payload + "." + createHmac("sha256", key).update("paw-signin:v1:" + payload).digest("hex");
26
+ }
27
+ function openPawFlow(raw, state, key, now = Date.now()) {
28
+ if (!key || !raw || raw.length > 2048 || !state || state.length > 2048)
29
+ return null;
30
+ const [payload, signature, extra] = raw.split(".");
31
+ if (!payload || extra || !/^[a-f0-9]{64}$/.test(signature || "")) return null;
32
+ if (!timingSafeEqual(
33
+ Buffer.from(signature, "hex"),
34
+ createHmac("sha256", key).update("paw-signin:v1:" + payload).digest()
35
+ ))
36
+ return null;
37
+ try {
38
+ const flow = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
39
+ if (!Number.isFinite(flow.expires) || flow.expires <= now || !/^[a-f0-9]{64}$/.test(flow.state) || !timingSafeEqual(
40
+ Buffer.from(flow.state, "hex"),
41
+ Buffer.from(digest(state), "hex")
42
+ ))
43
+ return null;
44
+ return returnPath(flow.next);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function createPawAuthHandler(options) {
50
+ return async (request, action) => {
51
+ const json = (body, status = 200) => Response.json(body, { status, headers: petPrivateHeaders });
52
+ try {
53
+ if (!options.enabled)
54
+ return json({ error: "Community sign-in is not available yet." }, 503);
55
+ const scope = await options.resolve(request), origin = new URL(scope.origin).origin;
56
+ if (!origin.startsWith("https://") || !scope.apiKey)
57
+ return json({ error: "Sign-in is not configured." }, 503);
58
+ const redirect = (path) => new Response(null, {
59
+ status: 303,
60
+ headers: {
61
+ ...petPrivateHeaders,
62
+ location: origin + returnPath(path)
63
+ }
64
+ });
65
+ if (action === "start" && request.method === "POST") {
66
+ if (request.headers.get("origin") !== origin)
67
+ return json({ error: "Sign in from this website." }, 403);
68
+ const callback = origin + "/api/paw-auth/callback";
69
+ const result = await petRequest(
70
+ scope,
71
+ "/v1/auth/oauth/ledvery/authorize?" + new URLSearchParams({ redirect_uri: callback }),
72
+ {},
73
+ options.fetch
74
+ );
75
+ const url = new URL(result.url);
76
+ if (url.origin !== new URL(options.issuerOrigin).origin || url.pathname !== "/authorize" || url.username || url.password || url.searchParams.get("state") !== result.state || url.searchParams.get("redirect_uri") !== callback || url.searchParams.get("code_challenge_method") !== "S256" || !url.searchParams.get("code_challenge"))
77
+ throw new Error("Invalid authorization response");
78
+ const response = json({ url: url.href });
79
+ response.headers.append(
80
+ "set-cookie",
81
+ `${FLOW}=${sealPawFlow(result.state, returnPath(new URL(request.url).searchParams.get("next")), scope.apiKey)}; ${attrs}; Max-Age=600`
82
+ );
83
+ return response;
84
+ }
85
+ if (action === "callback" && request.method === "GET") {
86
+ const query = new URL(request.url).searchParams, code = query.get("code"), state = query.get("state");
87
+ const next = openPawFlow(pawCookie(request, FLOW), state, scope.apiKey);
88
+ const response = redirect(next || "/account?signin=retry");
89
+ response.headers.append("set-cookie", `${FLOW}=; ${attrs}; Max-Age=0`);
90
+ if (!next || !code || code.length > 4096 || query.has("error"))
91
+ return response;
92
+ try {
93
+ const session = await petRequest(
94
+ scope,
95
+ "/v1/auth/oauth/ledvery/callback?" + new URLSearchParams({ code, state }),
96
+ {},
97
+ options.fetch
98
+ );
99
+ const expiry = Date.parse(session.expires_at);
100
+ if (!session.session_token || !/^[A-Za-z0-9._~-]{1,2048}$/.test(session.session_token) || !Number.isFinite(expiry) || expiry <= Date.now())
101
+ throw new Error("Invalid session");
102
+ const me = await petRequest(
103
+ { ...scope, accessToken: session.session_token },
104
+ "/v1/auth/me",
105
+ {},
106
+ options.fetch
107
+ );
108
+ if (!me.id || !me.email_verified)
109
+ throw new Error("Verified reader required");
110
+ response.headers.append(
111
+ "set-cookie",
112
+ `${PAW_SESSION_COOKIE}=${session.session_token}; ${attrs}; Expires=${new Date(expiry).toUTCString()}`
113
+ );
114
+ return response;
115
+ } catch {
116
+ response.headers.set("location", origin + "/account?signin=retry");
117
+ return response;
118
+ }
119
+ }
120
+ if (action === "session" && request.method === "GET") {
121
+ if (!scope.accessToken) return json({ authenticated: false });
122
+ const me = await petRequest(scope, "/v1/auth/me", {}, options.fetch);
123
+ return json({
124
+ authenticated: Boolean(me.id && me.email_verified),
125
+ name: me.full_name || "Neighbor"
126
+ });
127
+ }
128
+ if (action === "logout" && request.method === "POST") {
129
+ if (request.headers.get("origin") !== origin)
130
+ return json({ error: "Continue from this website." }, 403);
131
+ if (scope.accessToken)
132
+ await petRequest(
133
+ scope,
134
+ "/v1/auth/logout",
135
+ { method: "POST", body: { session_token: scope.accessToken } },
136
+ options.fetch
137
+ );
138
+ const response = json({ authenticated: false });
139
+ response.headers.append(
140
+ "set-cookie",
141
+ `${PAW_SESSION_COOKIE}=; ${attrs}; Max-Age=0`
142
+ );
143
+ return response;
144
+ }
145
+ return json({ error: "Route not found" }, 404);
146
+ } catch {
147
+ return json(
148
+ { error: "Sign-in is temporarily unavailable. Please try again." },
149
+ 503
150
+ );
151
+ }
152
+ };
153
+ }
154
+
155
+ export { PAW_SESSION_COOKIE, createPawAuthHandler, openPawFlow, pawCookie, sealPawFlow };