@scalemule/news-publication 0.0.88 → 0.0.89
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 +8 -2
- package/dist/{chunk-XWXPERLK.js → chunk-CKTTYAZY.js} +6 -1
- package/dist/{chunk-XSAVJJVW.js → chunk-EPAFIBBS.js} +1 -1
- package/dist/{chunk-Q77T2S7B.js → chunk-F6HV2HRP.js} +1 -1
- package/dist/{chunk-X7AU7DV6.js → chunk-R6WTBTXO.js} +1 -1
- package/dist/{chunk-FWEYKWKP.js → chunk-RSKB5NNE.js} +1 -1
- package/dist/cookie-consent-banner.js +2 -2
- package/dist/cookie-settings-view.js +2 -2
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/network-bar.js +2 -2
- package/dist/network-copyright.js +2 -2
- package/dist/network.cjs +6 -0
- package/dist/network.d.cts +3 -1
- package/dist/network.d.ts +3 -1
- package/dist/network.js +1 -1
- package/dist/reader-sso.cjs +16 -4
- package/dist/reader-sso.d.cts +10 -2
- package/dist/reader-sso.d.ts +10 -2
- package/dist/reader-sso.js +16 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ The consent banner now offers a choice in every geography where optional measure
|
|
|
249
249
|
## Reader sign-in through Ledvery
|
|
250
250
|
|
|
251
251
|
The `reader-sso` (server) and `reader-sso-client` entry points generalize the
|
|
252
|
-
publications' Google sign-in into one Ledvery consumer flow with
|
|
252
|
+
publications' Google sign-in into one Ledvery consumer flow with click-driven handoff,
|
|
253
253
|
explicit email sign-in, account linking, precedence between two accounts,
|
|
254
254
|
RP-initiated sign-out and server-recorded telemetry. A site supplies its cookie
|
|
255
255
|
prefix, canonical origin, Ledvery client id and a request adapter that performs
|
|
@@ -262,7 +262,13 @@ them with `applyCookies` and answers with private, no-store headers. Merge
|
|
|
262
262
|
`READER_SSO_EVENTS` into the site's analytics registry and allow
|
|
263
263
|
`READER_SSO_PROPERTY_KEYS` in its property sanitizer. `ReaderSsoBoot` is a
|
|
264
264
|
no-op compatibility export: a rendered page never starts Ledvery authorization
|
|
265
|
-
on a timer.
|
|
265
|
+
on a timer. Ordinary explicit sign-in omits `prompt`; `select_account` is reserved
|
|
266
|
+
for an explicit account-switch control and `prompt=none` for a sister-paper
|
|
267
|
+
handoff. Handoff links carry no identity or session token, and
|
|
268
|
+
`networkHandoffReturnPath` discards caller-selected destinations. The two
|
|
269
|
+
initial canaries are listed by `getNetworkReaderHandoffUrl`; other papers
|
|
270
|
+
remain plain links until their route and client registration are verified.
|
|
271
|
+
`ReaderGreeting` is a client
|
|
266
272
|
island that renders only a first name, in the reader's own time zone and locale,
|
|
267
273
|
after loading.
|
|
268
274
|
|
|
@@ -368,6 +368,11 @@ function getNetworkPublicationUrl(slug) {
|
|
|
368
368
|
if (!pub) return null;
|
|
369
369
|
return decorateNetworkUrl(pub.url + "/");
|
|
370
370
|
}
|
|
371
|
+
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
372
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
373
|
+
const publication = findPublicationBySlug(slug);
|
|
374
|
+
return publication && readerHandoffReady.has(slug) ? `${publication.url}/api/news/network-handoff` : null;
|
|
375
|
+
}
|
|
371
376
|
var NETWORK_HOSTNAMES = new Set(
|
|
372
377
|
localPublications.map((p) => {
|
|
373
378
|
try {
|
|
@@ -383,4 +388,4 @@ function isNetworkHostname(hostname) {
|
|
|
383
388
|
return clean === "bayareanewsnetwork.com" || clean.endsWith(".bayareanewsnetwork.com") || NETWORK_HOSTNAMES.has(clean) || clean.endsWith(".bayareachronicle.com") || clean.endsWith(".napsite.com") || clean.endsWith(".site.scalemule.com");
|
|
384
389
|
}
|
|
385
390
|
|
|
386
|
-
export { COOKIE_CATEGORIES, EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, recordConsentToCompliance, setupNetworkConsentDelegation, topicalPublications };
|
|
391
|
+
export { COOKIE_CATEGORIES, EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, recordConsentToCompliance, setupNetworkConsentDelegation, topicalPublications };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, consentModel, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-
|
|
2
|
+
import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, consentModel, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-CKTTYAZY.js';
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-
|
|
2
|
+
import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-CKTTYAZY.js';
|
|
3
3
|
import { useState, useId, useRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { effectivePreferences, getCookieConsent, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED } from './chunk-
|
|
2
|
+
import { effectivePreferences, getCookieConsent, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED } from './chunk-CKTTYAZY.js';
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { CookieConsentBanner } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { CookieConsentBanner } from './chunk-EPAFIBBS.js';
|
|
3
|
+
import './chunk-CKTTYAZY.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { CookieSettingsView } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { CookieSettingsView } from './chunk-R6WTBTXO.js';
|
|
3
|
+
import './chunk-CKTTYAZY.js';
|
package/dist/index.cjs
CHANGED
|
@@ -1477,6 +1477,11 @@ function getNetworkPublicationUrl(slug) {
|
|
|
1477
1477
|
if (!pub) return null;
|
|
1478
1478
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
1479
1479
|
}
|
|
1480
|
+
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
1481
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
1482
|
+
const publication = findPublicationBySlug(slug);
|
|
1483
|
+
return publication && readerHandoffReady.has(slug) ? `${publication.url}/api/news/network-handoff` : null;
|
|
1484
|
+
}
|
|
1480
1485
|
var NETWORK_HOSTNAMES = new Set(
|
|
1481
1486
|
localPublications.map((p) => {
|
|
1482
1487
|
try {
|
|
@@ -4001,6 +4006,7 @@ exports.findPublicationBySlug = findPublicationBySlug;
|
|
|
4001
4006
|
exports.formatArtworkCaption = formatArtworkCaption;
|
|
4002
4007
|
exports.getArticleCards = getArticleCards;
|
|
4003
4008
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
4009
|
+
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
4004
4010
|
exports.getStoredTheme = getStoredTheme;
|
|
4005
4011
|
exports.getSystemTheme = getSystemTheme;
|
|
4006
4012
|
exports.inventoryCacheOptions = inventoryCacheOptions;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,7 +8,7 @@ export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CO
|
|
|
8
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.cjs';
|
|
9
9
|
export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.cjs';
|
|
10
10
|
export { ThemeToggle, ThemeToggleProps } from './theme-toggle.cjs';
|
|
11
|
-
export { EDITORIAL_PRINCIPLES, EditorialPrinciple, NETWORK_PARENT, NetworkParent, PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications, topicalPublications } from './network.cjs';
|
|
11
|
+
export { EDITORIAL_PRINCIPLES, EditorialPrinciple, NETWORK_PARENT, NetworkParent, PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, listedPublications, localPublications, topicalPublications } from './network.cjs';
|
|
12
12
|
export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.cjs';
|
|
13
13
|
export { NetworkCopyrightNotice, NetworkCopyrightNoticeProps } from './network-copyright.cjs';
|
|
14
14
|
import * as React from 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CO
|
|
|
8
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.js';
|
|
9
9
|
export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.js';
|
|
10
10
|
export { ThemeToggle, ThemeToggleProps } from './theme-toggle.js';
|
|
11
|
-
export { EDITORIAL_PRINCIPLES, EditorialPrinciple, NETWORK_PARENT, NetworkParent, PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications, topicalPublications } from './network.js';
|
|
11
|
+
export { EDITORIAL_PRINCIPLES, EditorialPrinciple, NETWORK_PARENT, NetworkParent, PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, listedPublications, localPublications, topicalPublications } from './network.js';
|
|
12
12
|
export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.js';
|
|
13
13
|
export { NetworkCopyrightNotice, NetworkCopyrightNoticeProps } from './network-copyright.js';
|
|
14
14
|
import * as React from 'react';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { LocalNetworkBar } from './chunk-
|
|
2
|
-
export { NetworkCopyrightNotice } from './chunk-
|
|
1
|
+
export { LocalNetworkBar } from './chunk-F6HV2HRP.js';
|
|
2
|
+
export { NetworkCopyrightNotice } from './chunk-RSKB5NNE.js';
|
|
3
3
|
export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-KYUVXEJY.js';
|
|
4
4
|
export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
|
|
5
5
|
export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-AMLVCZDP.js';
|
|
6
|
-
export { CookieConsentBanner } from './chunk-
|
|
7
|
-
export { CookieSettingsView } from './chunk-
|
|
8
|
-
import { localPublications } from './chunk-
|
|
9
|
-
export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-
|
|
6
|
+
export { CookieConsentBanner } from './chunk-EPAFIBBS.js';
|
|
7
|
+
export { CookieSettingsView } from './chunk-R6WTBTXO.js';
|
|
8
|
+
import { localPublications } from './chunk-CKTTYAZY.js';
|
|
9
|
+
export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-CKTTYAZY.js';
|
|
10
10
|
export { THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, ThemeToggle, applyTheme, clearTheme, getStoredTheme, getSystemTheme, resolveEffectiveTheme, setTheme, subscribeTheme, themeInitScript } from './chunk-NQD3M7ZY.js';
|
|
11
11
|
export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
|
|
12
12
|
export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-DN3TBZLK.js';
|
package/dist/network-bar.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { LocalNetworkBar } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { LocalNetworkBar } from './chunk-F6HV2HRP.js';
|
|
2
|
+
import './chunk-CKTTYAZY.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { NetworkCopyrightNotice } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { NetworkCopyrightNotice } from './chunk-RSKB5NNE.js';
|
|
2
|
+
import './chunk-CKTTYAZY.js';
|
package/dist/network.cjs
CHANGED
|
@@ -245,6 +245,11 @@ function getNetworkPublicationUrl(slug) {
|
|
|
245
245
|
if (!pub) return null;
|
|
246
246
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
247
247
|
}
|
|
248
|
+
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
249
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
250
|
+
const publication = findPublicationBySlug(slug);
|
|
251
|
+
return publication && readerHandoffReady.has(slug) ? `${publication.url}/api/news/network-handoff` : null;
|
|
252
|
+
}
|
|
248
253
|
var NETWORK_HOSTNAMES = new Set(
|
|
249
254
|
localPublications.map((p) => {
|
|
250
255
|
try {
|
|
@@ -266,6 +271,7 @@ exports.communityPublications = communityPublications;
|
|
|
266
271
|
exports.findPublicationByHost = findPublicationByHost;
|
|
267
272
|
exports.findPublicationBySlug = findPublicationBySlug;
|
|
268
273
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
274
|
+
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
269
275
|
exports.isNetworkHostname = isNetworkHostname;
|
|
270
276
|
exports.listedPublications = listedPublications;
|
|
271
277
|
exports.localPublications = localPublications;
|
package/dist/network.d.cts
CHANGED
|
@@ -58,7 +58,9 @@ declare function findPublicationBySlug(slug: string): PublicationNetworkItem | u
|
|
|
58
58
|
declare function findPublicationByHost(host: string): PublicationNetworkItem | undefined;
|
|
59
59
|
/** Get decorated URL for cross-domain navigation within the network with consent transfer. */
|
|
60
60
|
declare function getNetworkPublicationUrl(slug: string): string | null;
|
|
61
|
+
/** The fixed destination route; no identity or caller-selected return URL is carried. */
|
|
62
|
+
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
61
63
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
62
64
|
declare function isNetworkHostname(hostname: string): boolean;
|
|
63
65
|
|
|
64
|
-
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
|
66
|
+
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
package/dist/network.d.ts
CHANGED
|
@@ -58,7 +58,9 @@ declare function findPublicationBySlug(slug: string): PublicationNetworkItem | u
|
|
|
58
58
|
declare function findPublicationByHost(host: string): PublicationNetworkItem | undefined;
|
|
59
59
|
/** Get decorated URL for cross-domain navigation within the network with consent transfer. */
|
|
60
60
|
declare function getNetworkPublicationUrl(slug: string): string | null;
|
|
61
|
+
/** The fixed destination route; no identity or caller-selected return URL is carried. */
|
|
62
|
+
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
61
63
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
62
64
|
declare function isNetworkHostname(hostname: string): boolean;
|
|
63
65
|
|
|
64
|
-
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
|
66
|
+
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
package/dist/network.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-
|
|
1
|
+
export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-CKTTYAZY.js';
|
package/dist/reader-sso.cjs
CHANGED
|
@@ -84,6 +84,9 @@ function localReturnPath(value) {
|
|
|
84
84
|
if (/[\\\r\n\u0000-\u001f]/.test(value) || value === "/api" || value.startsWith("/api/")) return "";
|
|
85
85
|
return value;
|
|
86
86
|
}
|
|
87
|
+
function networkHandoffReturnPath(query) {
|
|
88
|
+
return query.getAll("sm_consent").length === 1 && query.get("sm_consent") === "0" ? "/?sm_consent=0" : "/";
|
|
89
|
+
}
|
|
87
90
|
function resolve(config) {
|
|
88
91
|
const origin = new URL(config.origin);
|
|
89
92
|
if (origin.protocol !== "https:" || origin.origin !== config.origin) throw new Error("Reader SSO origin must be a bare https origin");
|
|
@@ -177,9 +180,17 @@ function openPendingSession(cookie, key, now = Date.now()) {
|
|
|
177
180
|
tx: typeof raw.tx === "string" ? raw.tx : void 0
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
|
-
function authorizeUrl(value, state, callback, mode, issuerOrigin = LEDVERY_ISSUER_ORIGIN) {
|
|
183
|
+
function authorizeUrl(value, state, callback, mode, issuerOrigin = LEDVERY_ISSUER_ORIGIN, selectAccount = false, clientId) {
|
|
181
184
|
const url = new URL(value);
|
|
182
|
-
|
|
185
|
+
const prompts = url.searchParams.getAll("prompt");
|
|
186
|
+
const expectedPrompt = mode === "silent" ? "none" : selectAccount ? "select_account" : null;
|
|
187
|
+
const exactly = (key, expected) => {
|
|
188
|
+
const values = url.searchParams.getAll(key);
|
|
189
|
+
return values.length === 1 && values[0] === expected;
|
|
190
|
+
};
|
|
191
|
+
const scope = url.searchParams.getAll("scope");
|
|
192
|
+
const nonce = url.searchParams.getAll("nonce");
|
|
193
|
+
if (url.protocol !== "https:" || url.origin !== new URL(issuerOrigin).origin || url.pathname !== "/authorize" || url.username || url.password || url.hash || !exactly("state", state) || !exactly("redirect_uri", callback) || !exactly("response_type", "code") || clientId !== void 0 && !exactly("client_id", clientId) || scope.length !== 1 || !scope[0].split(/\s+/).includes("openid") || nonce.length !== 1 || !nonce[0] || !exactly("code_challenge_method", "S256") || url.searchParams.getAll("code_challenge").length !== 1 || !url.searchParams.get("code_challenge") || (expectedPrompt === null ? prompts.length !== 0 : prompts.length !== 1 || prompts[0] !== expectedPrompt)) throw new Error("Invalid Ledvery authorization response");
|
|
183
194
|
return url.toString();
|
|
184
195
|
}
|
|
185
196
|
async function startSignIn(config, input) {
|
|
@@ -213,7 +224,7 @@ async function startSignIn(config, input) {
|
|
|
213
224
|
const query = new URLSearchParams({ redirect_uri: callback });
|
|
214
225
|
if (mode === "silent") query.set("prompt", "none");
|
|
215
226
|
else {
|
|
216
|
-
query.set("prompt", "select_account");
|
|
227
|
+
if (input.selectAccount === true) query.set("prompt", "select_account");
|
|
217
228
|
query.set("upstream", provider === "email" ? "scalemule_consumer" : "google");
|
|
218
229
|
}
|
|
219
230
|
try {
|
|
@@ -221,7 +232,7 @@ async function startSignIn(config, input) {
|
|
|
221
232
|
if (failed(response)) throw new Error("authorize");
|
|
222
233
|
const value = payload(response);
|
|
223
234
|
if (typeof value.url !== "string" || typeof value.state !== "string" || !value.state) throw new Error("authorize");
|
|
224
|
-
const url = authorizeUrl(value.url, value.state, callback, mode, c.issuerOrigin);
|
|
235
|
+
const url = authorizeUrl(value.url, value.state, callback, mode, c.issuerOrigin, input.selectAccount === true, c.clientId);
|
|
225
236
|
const cookies = [
|
|
226
237
|
...staleSession,
|
|
227
238
|
{ name: c.names.flow, value: sealReaderFlow({ state: value.state, subscribe: input.subscribe === true && mode === "interactive", returnTo: next, mode, provider, tx }, c.key, c.now()), maxAge: READER_SSO_FLOW_SECONDS }
|
|
@@ -537,6 +548,7 @@ exports.cookieReader = cookieReader;
|
|
|
537
548
|
exports.linkAccount = linkAccount;
|
|
538
549
|
exports.linkFailureMessage = linkFailureMessage;
|
|
539
550
|
exports.localReturnPath = localReturnPath;
|
|
551
|
+
exports.networkHandoffReturnPath = networkHandoffReturnPath;
|
|
540
552
|
exports.openPendingSession = openPendingSession;
|
|
541
553
|
exports.openReaderFlow = openReaderFlow;
|
|
542
554
|
exports.pendingSwitch = pendingSwitch;
|
package/dist/reader-sso.d.cts
CHANGED
|
@@ -132,6 +132,12 @@ type ReaderSsoConfig = {
|
|
|
132
132
|
};
|
|
133
133
|
/** Any same-site path: no protocol-relative URLs, control characters or API routes. */
|
|
134
134
|
declare function localReturnPath(value: unknown): string;
|
|
135
|
+
/**
|
|
136
|
+
* A network-bar handoff always lands on this paper's home page. The only
|
|
137
|
+
* transferable query value is the existing refusal of optional cookies.
|
|
138
|
+
* Identity, callback and return destinations never come from the link.
|
|
139
|
+
*/
|
|
140
|
+
declare function networkHandoffReturnPath(query: URLSearchParams): string;
|
|
135
141
|
declare function stateDigest(state: string): string;
|
|
136
142
|
declare function validState(state: string | null | undefined, digest: string | undefined): boolean;
|
|
137
143
|
type ReaderSsoFlow = {
|
|
@@ -166,10 +172,12 @@ type PendingSession = {
|
|
|
166
172
|
};
|
|
167
173
|
declare function sealPendingSession(pending: PendingSession, key: string, now?: number): string;
|
|
168
174
|
declare function openPendingSession(cookie: string | undefined, key: string, now?: number): PendingSession | null;
|
|
169
|
-
declare function authorizeUrl(value: string, state: string, callback: string, mode: ReaderSsoMode, issuerOrigin?: string): string;
|
|
175
|
+
declare function authorizeUrl(value: string, state: string, callback: string, mode: ReaderSsoMode, issuerOrigin?: string, selectAccount?: boolean, clientId?: string): string;
|
|
170
176
|
type StartSignInInput = {
|
|
171
177
|
mode: ReaderSsoMode;
|
|
172
178
|
provider?: ReaderSsoProvider;
|
|
179
|
+
/** Only an explicit Switch account control may request the account chooser. */
|
|
180
|
+
selectAccount?: boolean;
|
|
173
181
|
subscribe?: boolean;
|
|
174
182
|
next?: string;
|
|
175
183
|
cookies: CookieReader;
|
|
@@ -319,4 +327,4 @@ declare function readerSsoBootFlags(cookies: CookieReader, names: ReaderSsoCooki
|
|
|
319
327
|
suppressed: boolean;
|
|
320
328
|
};
|
|
321
329
|
|
|
322
|
-
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
330
|
+
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/dist/reader-sso.d.ts
CHANGED
|
@@ -132,6 +132,12 @@ type ReaderSsoConfig = {
|
|
|
132
132
|
};
|
|
133
133
|
/** Any same-site path: no protocol-relative URLs, control characters or API routes. */
|
|
134
134
|
declare function localReturnPath(value: unknown): string;
|
|
135
|
+
/**
|
|
136
|
+
* A network-bar handoff always lands on this paper's home page. The only
|
|
137
|
+
* transferable query value is the existing refusal of optional cookies.
|
|
138
|
+
* Identity, callback and return destinations never come from the link.
|
|
139
|
+
*/
|
|
140
|
+
declare function networkHandoffReturnPath(query: URLSearchParams): string;
|
|
135
141
|
declare function stateDigest(state: string): string;
|
|
136
142
|
declare function validState(state: string | null | undefined, digest: string | undefined): boolean;
|
|
137
143
|
type ReaderSsoFlow = {
|
|
@@ -166,10 +172,12 @@ type PendingSession = {
|
|
|
166
172
|
};
|
|
167
173
|
declare function sealPendingSession(pending: PendingSession, key: string, now?: number): string;
|
|
168
174
|
declare function openPendingSession(cookie: string | undefined, key: string, now?: number): PendingSession | null;
|
|
169
|
-
declare function authorizeUrl(value: string, state: string, callback: string, mode: ReaderSsoMode, issuerOrigin?: string): string;
|
|
175
|
+
declare function authorizeUrl(value: string, state: string, callback: string, mode: ReaderSsoMode, issuerOrigin?: string, selectAccount?: boolean, clientId?: string): string;
|
|
170
176
|
type StartSignInInput = {
|
|
171
177
|
mode: ReaderSsoMode;
|
|
172
178
|
provider?: ReaderSsoProvider;
|
|
179
|
+
/** Only an explicit Switch account control may request the account chooser. */
|
|
180
|
+
selectAccount?: boolean;
|
|
173
181
|
subscribe?: boolean;
|
|
174
182
|
next?: string;
|
|
175
183
|
cookies: CookieReader;
|
|
@@ -319,4 +327,4 @@ declare function readerSsoBootFlags(cookies: CookieReader, names: ReaderSsoCooki
|
|
|
319
327
|
suppressed: boolean;
|
|
320
328
|
};
|
|
321
329
|
|
|
322
|
-
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
330
|
+
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/dist/reader-sso.js
CHANGED
|
@@ -69,6 +69,9 @@ function localReturnPath(value) {
|
|
|
69
69
|
if (/[\\\r\n\u0000-\u001f]/.test(value) || value === "/api" || value.startsWith("/api/")) return "";
|
|
70
70
|
return value;
|
|
71
71
|
}
|
|
72
|
+
function networkHandoffReturnPath(query) {
|
|
73
|
+
return query.getAll("sm_consent").length === 1 && query.get("sm_consent") === "0" ? "/?sm_consent=0" : "/";
|
|
74
|
+
}
|
|
72
75
|
function resolve(config) {
|
|
73
76
|
const origin = new URL(config.origin);
|
|
74
77
|
if (origin.protocol !== "https:" || origin.origin !== config.origin) throw new Error("Reader SSO origin must be a bare https origin");
|
|
@@ -162,9 +165,17 @@ function openPendingSession(cookie, key, now = Date.now()) {
|
|
|
162
165
|
tx: typeof raw.tx === "string" ? raw.tx : void 0
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
|
-
function authorizeUrl(value, state, callback, mode, issuerOrigin = LEDVERY_ISSUER_ORIGIN) {
|
|
168
|
+
function authorizeUrl(value, state, callback, mode, issuerOrigin = LEDVERY_ISSUER_ORIGIN, selectAccount = false, clientId) {
|
|
166
169
|
const url = new URL(value);
|
|
167
|
-
|
|
170
|
+
const prompts = url.searchParams.getAll("prompt");
|
|
171
|
+
const expectedPrompt = mode === "silent" ? "none" : selectAccount ? "select_account" : null;
|
|
172
|
+
const exactly = (key, expected) => {
|
|
173
|
+
const values = url.searchParams.getAll(key);
|
|
174
|
+
return values.length === 1 && values[0] === expected;
|
|
175
|
+
};
|
|
176
|
+
const scope = url.searchParams.getAll("scope");
|
|
177
|
+
const nonce = url.searchParams.getAll("nonce");
|
|
178
|
+
if (url.protocol !== "https:" || url.origin !== new URL(issuerOrigin).origin || url.pathname !== "/authorize" || url.username || url.password || url.hash || !exactly("state", state) || !exactly("redirect_uri", callback) || !exactly("response_type", "code") || clientId !== void 0 && !exactly("client_id", clientId) || scope.length !== 1 || !scope[0].split(/\s+/).includes("openid") || nonce.length !== 1 || !nonce[0] || !exactly("code_challenge_method", "S256") || url.searchParams.getAll("code_challenge").length !== 1 || !url.searchParams.get("code_challenge") || (expectedPrompt === null ? prompts.length !== 0 : prompts.length !== 1 || prompts[0] !== expectedPrompt)) throw new Error("Invalid Ledvery authorization response");
|
|
168
179
|
return url.toString();
|
|
169
180
|
}
|
|
170
181
|
async function startSignIn(config, input) {
|
|
@@ -198,7 +209,7 @@ async function startSignIn(config, input) {
|
|
|
198
209
|
const query = new URLSearchParams({ redirect_uri: callback });
|
|
199
210
|
if (mode === "silent") query.set("prompt", "none");
|
|
200
211
|
else {
|
|
201
|
-
query.set("prompt", "select_account");
|
|
212
|
+
if (input.selectAccount === true) query.set("prompt", "select_account");
|
|
202
213
|
query.set("upstream", provider === "email" ? "scalemule_consumer" : "google");
|
|
203
214
|
}
|
|
204
215
|
try {
|
|
@@ -206,7 +217,7 @@ async function startSignIn(config, input) {
|
|
|
206
217
|
if (failed(response)) throw new Error("authorize");
|
|
207
218
|
const value = payload(response);
|
|
208
219
|
if (typeof value.url !== "string" || typeof value.state !== "string" || !value.state) throw new Error("authorize");
|
|
209
|
-
const url = authorizeUrl(value.url, value.state, callback, mode, c.issuerOrigin);
|
|
220
|
+
const url = authorizeUrl(value.url, value.state, callback, mode, c.issuerOrigin, input.selectAccount === true, c.clientId);
|
|
210
221
|
const cookies = [
|
|
211
222
|
...staleSession,
|
|
212
223
|
{ name: c.names.flow, value: sealReaderFlow({ state: value.state, subscribe: input.subscribe === true && mode === "interactive", returnTo: next, mode, provider, tx }, c.key, c.now()), maxAge: READER_SSO_FLOW_SECONDS }
|
|
@@ -507,4 +518,4 @@ function readerSsoBootFlags(cookies, names) {
|
|
|
507
518
|
};
|
|
508
519
|
}
|
|
509
520
|
|
|
510
|
-
export { LEDVERY_ISSUER_ORIGIN, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, SILENT_NEGATIVE_ERRORS, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
521
|
+
export { LEDVERY_ISSUER_ORIGIN, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, SILENT_NEGATIVE_ERRORS, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.89",
|
|
4
4
|
"description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|