@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.
- package/README.md +28 -0
- package/dist/{chunk-GB36YM56.js → chunk-2MEYHM7M.js} +11 -1
- package/dist/chunk-4JRWZXAP.js +62 -0
- package/dist/chunk-MBBPEFDA.js +124 -0
- package/dist/chunk-TTLM7YOH.js +444 -0
- package/dist/chunk-ZXH3O537.js +251 -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 +4 -3
- 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 +212 -0
- package/dist/pets-auth-server.d.cts +17 -0
- package/dist/pets-auth-server.d.ts +17 -0
- package/dist/pets-auth-server.js +155 -0
- package/dist/pets-forms.cjs +762 -0
- package/dist/pets-forms.d.cts +26 -0
- package/dist/pets-forms.d.ts +26 -0
- package/dist/pets-forms.js +712 -0
- package/dist/pets-interactions.cjs +332 -0
- package/dist/pets-interactions.d.cts +20 -0
- package/dist/pets-interactions.d.ts +20 -0
- package/dist/pets-interactions.js +319 -0
- package/dist/pets-publication.cjs +818 -0
- package/dist/pets-publication.d.cts +36 -0
- package/dist/pets-publication.d.ts +36 -0
- package/dist/pets-publication.js +177 -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 +541 -0
- package/dist/pets-view.d.cts +56 -0
- package/dist/pets-view.d.ts +56 -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 +40 -3
- package/pets.css +538 -0
package/dist/meet-forms.d.cts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { P as PublicPlace } from './meet-
|
|
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
|
-
}):
|
|
7
|
-
declare function PhoneCheckForm():
|
|
8
|
-
declare function BirthdayForm():
|
|
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
|
-
}):
|
|
12
|
+
}): React.JSX.Element;
|
|
12
13
|
|
|
13
14
|
export { BirthdayForm, MeetHostForm, MeetupChat, PhoneCheckForm };
|
package/dist/meet-forms.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { P as PublicPlace } from './meet-
|
|
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
|
-
}):
|
|
7
|
-
declare function PhoneCheckForm():
|
|
8
|
-
declare function BirthdayForm():
|
|
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
|
-
}):
|
|
12
|
+
}): React.JSX.Element;
|
|
12
13
|
|
|
13
14
|
export { BirthdayForm, MeetHostForm, MeetupChat, PhoneCheckForm };
|
package/dist/meet-forms.js
CHANGED
package/dist/meet-view.cjs
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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() {
|
package/dist/meet-view.d.cts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { a as PreparedMeetup } from './meet-
|
|
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
|
-
}):
|
|
15
|
+
}): React.JSX.Element;
|
|
14
16
|
declare function MeetupDetail({ meetup, joined }: {
|
|
15
17
|
meetup: MeetupCard;
|
|
16
18
|
joined: boolean;
|
|
17
|
-
}):
|
|
19
|
+
}): React.JSX.Element;
|
|
18
20
|
declare function activityChoices(): {
|
|
19
|
-
value: "
|
|
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.d.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { a as PreparedMeetup } from './meet-
|
|
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
|
-
}):
|
|
15
|
+
}): React.JSX.Element;
|
|
14
16
|
declare function MeetupDetail({ meetup, joined }: {
|
|
15
17
|
meetup: MeetupCard;
|
|
16
18
|
joined: boolean;
|
|
17
|
-
}):
|
|
19
|
+
}): React.JSX.Element;
|
|
18
20
|
declare function activityChoices(): {
|
|
19
|
-
value: "
|
|
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-
|
|
2
|
-
import {
|
|
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
|
-
|
|
52
|
-
|
|
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() {
|
package/dist/network-bar.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
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):
|
|
13
|
+
declare function LocalNetworkBar({ currentSlug, className, style }: LocalNetworkBarProps): React.JSX.Element;
|
|
14
14
|
|
|
15
15
|
export { LocalNetworkBar, type LocalNetworkBarProps };
|
package/dist/network-bar.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
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):
|
|
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
|
|
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):
|
|
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
|
|
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):
|
|
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 };
|