@scalemule/news-publication 0.0.88 → 0.0.90
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-Q77T2S7B.js → chunk-2E42LCN3.js} +1 -1
- package/dist/{chunk-XSAVJJVW.js → chunk-D27NMCRK.js} +1 -1
- package/dist/{chunk-FWEYKWKP.js → chunk-FJ672GWO.js} +1 -1
- package/dist/{chunk-XWXPERLK.js → chunk-NNEY6VBX.js} +5 -1
- package/dist/{chunk-X7AU7DV6.js → chunk-PBRDIXQI.js} +1 -1
- package/dist/cookie-consent-banner.js +2 -2
- package/dist/cookie-settings-view.js +2 -2
- package/dist/index.cjs +5 -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 +5 -0
- package/dist/network.d.cts +7 -1
- package/dist/network.d.ts +7 -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
|
|
|
@@ -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-NNEY6VBX.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 { 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-NNEY6VBX.js';
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -368,6 +368,10 @@ function getNetworkPublicationUrl(slug) {
|
|
|
368
368
|
if (!pub) return null;
|
|
369
369
|
return decorateNetworkUrl(pub.url + "/");
|
|
370
370
|
}
|
|
371
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
372
|
+
const publication = findPublicationBySlug(slug);
|
|
373
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
374
|
+
}
|
|
371
375
|
var NETWORK_HOSTNAMES = new Set(
|
|
372
376
|
localPublications.map((p) => {
|
|
373
377
|
try {
|
|
@@ -383,4 +387,4 @@ function isNetworkHostname(hostname) {
|
|
|
383
387
|
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
388
|
}
|
|
385
389
|
|
|
386
|
-
export { COOKIE_CATEGORIES, EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, recordConsentToCompliance, setupNetworkConsentDelegation, topicalPublications };
|
|
390
|
+
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 { 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-NNEY6VBX.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-D27NMCRK.js';
|
|
3
|
+
import './chunk-NNEY6VBX.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { CookieSettingsView } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { CookieSettingsView } from './chunk-PBRDIXQI.js';
|
|
3
|
+
import './chunk-NNEY6VBX.js';
|
package/dist/index.cjs
CHANGED
|
@@ -1477,6 +1477,10 @@ function getNetworkPublicationUrl(slug) {
|
|
|
1477
1477
|
if (!pub) return null;
|
|
1478
1478
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
1479
1479
|
}
|
|
1480
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
1481
|
+
const publication = findPublicationBySlug(slug);
|
|
1482
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
1483
|
+
}
|
|
1480
1484
|
var NETWORK_HOSTNAMES = new Set(
|
|
1481
1485
|
localPublications.map((p) => {
|
|
1482
1486
|
try {
|
|
@@ -4001,6 +4005,7 @@ exports.findPublicationBySlug = findPublicationBySlug;
|
|
|
4001
4005
|
exports.formatArtworkCaption = formatArtworkCaption;
|
|
4002
4006
|
exports.getArticleCards = getArticleCards;
|
|
4003
4007
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
4008
|
+
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
4004
4009
|
exports.getStoredTheme = getStoredTheme;
|
|
4005
4010
|
exports.getSystemTheme = getSystemTheme;
|
|
4006
4011
|
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-2E42LCN3.js';
|
|
2
|
+
export { NetworkCopyrightNotice } from './chunk-FJ672GWO.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-D27NMCRK.js';
|
|
7
|
+
export { CookieSettingsView } from './chunk-PBRDIXQI.js';
|
|
8
|
+
import { localPublications } from './chunk-NNEY6VBX.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-NNEY6VBX.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-2E42LCN3.js';
|
|
2
|
+
import './chunk-NNEY6VBX.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { NetworkCopyrightNotice } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { NetworkCopyrightNotice } from './chunk-FJ672GWO.js';
|
|
2
|
+
import './chunk-NNEY6VBX.js';
|
package/dist/network.cjs
CHANGED
|
@@ -245,6 +245,10 @@ function getNetworkPublicationUrl(slug) {
|
|
|
245
245
|
if (!pub) return null;
|
|
246
246
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
247
247
|
}
|
|
248
|
+
function getNetworkReaderHandoffUrl(slug) {
|
|
249
|
+
const publication = findPublicationBySlug(slug);
|
|
250
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
251
|
+
}
|
|
248
252
|
var NETWORK_HOSTNAMES = new Set(
|
|
249
253
|
localPublications.map((p) => {
|
|
250
254
|
try {
|
|
@@ -266,6 +270,7 @@ exports.communityPublications = communityPublications;
|
|
|
266
270
|
exports.findPublicationByHost = findPublicationByHost;
|
|
267
271
|
exports.findPublicationBySlug = findPublicationBySlug;
|
|
268
272
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
273
|
+
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
269
274
|
exports.isNetworkHostname = isNetworkHostname;
|
|
270
275
|
exports.listedPublications = listedPublications;
|
|
271
276
|
exports.localPublications = localPublications;
|
package/dist/network.d.cts
CHANGED
|
@@ -58,7 +58,13 @@ 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
|
+
/**
|
|
62
|
+
* The fixed destination route for a publication in this reviewed network list.
|
|
63
|
+
* Sites only use it when their handoff switch is enabled after every destination
|
|
64
|
+
* has deployed the route. No identity or caller-selected return URL is carried.
|
|
65
|
+
*/
|
|
66
|
+
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
61
67
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
62
68
|
declare function isNetworkHostname(hostname: string): boolean;
|
|
63
69
|
|
|
64
|
-
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
|
70
|
+
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,13 @@ 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
|
+
/**
|
|
62
|
+
* The fixed destination route for a publication in this reviewed network list.
|
|
63
|
+
* Sites only use it when their handoff switch is enabled after every destination
|
|
64
|
+
* has deployed the route. No identity or caller-selected return URL is carried.
|
|
65
|
+
*/
|
|
66
|
+
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
61
67
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
62
68
|
declare function isNetworkHostname(hostname: string): boolean;
|
|
63
69
|
|
|
64
|
-
export { EDITORIAL_PRINCIPLES, type EditorialPrinciple, NETWORK_PARENT, type NetworkParent, type PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications };
|
|
70
|
+
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-NNEY6VBX.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.90",
|
|
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": {
|