@scalemule/news-publication 0.0.87 → 0.0.88
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 +5 -5
- package/dist/reader-sso-client.cjs +1 -106
- package/dist/reader-sso-client.d.cts +7 -5
- package/dist/reader-sso-client.d.ts +7 -5
- package/dist/reader-sso-client.js +1 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -260,11 +260,11 @@ other than that key and the browser only ever sees host-only HttpOnly cookies.
|
|
|
260
260
|
`sessionSummary` return cookie instructions and destinations; the site applies
|
|
261
261
|
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
|
-
`READER_SSO_PROPERTY_KEYS` in its property sanitizer. `ReaderSsoBoot`
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
`READER_SSO_PROPERTY_KEYS` in its property sanitizer. `ReaderSsoBoot` is a
|
|
264
|
+
no-op compatibility export: a rendered page never starts Ledvery authorization
|
|
265
|
+
on a timer. The explicit sign-in routes still work. `ReaderGreeting` is a client
|
|
266
|
+
island that renders only a first name, in the reader's own time zone and locale,
|
|
267
|
+
after loading.
|
|
268
268
|
|
|
269
269
|
## Community pet sections
|
|
270
270
|
|
|
@@ -4,46 +4,6 @@
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
7
|
-
// src/reader-sso-scheduler.ts
|
|
8
|
-
function mountReaderSsoScheduler({ document: doc, timers, eligible, attempt, idleDelayMs }) {
|
|
9
|
-
let active = true;
|
|
10
|
-
let attempted = false;
|
|
11
|
-
let idle;
|
|
12
|
-
let delay;
|
|
13
|
-
const cancelPending = () => {
|
|
14
|
-
if (idle !== void 0) timers.cancelIdleCallback?.(idle);
|
|
15
|
-
if (delay !== void 0) timers.clearTimeout(delay);
|
|
16
|
-
idle = delay = void 0;
|
|
17
|
-
};
|
|
18
|
-
const run = () => {
|
|
19
|
-
delay = void 0;
|
|
20
|
-
if (!active || attempted || !eligible()) return;
|
|
21
|
-
attempted = true;
|
|
22
|
-
attempt();
|
|
23
|
-
};
|
|
24
|
-
const defer = () => {
|
|
25
|
-
idle = void 0;
|
|
26
|
-
if (active && !attempted) delay = timers.setTimeout(run, idleDelayMs);
|
|
27
|
-
};
|
|
28
|
-
const schedule = () => {
|
|
29
|
-
if (!active || attempted) return;
|
|
30
|
-
cancelPending();
|
|
31
|
-
if (doc.readyState === "loading" || !eligible()) return;
|
|
32
|
-
if (timers.requestIdleCallback) idle = timers.requestIdleCallback(defer, { timeout: 4e3 });
|
|
33
|
-
else defer();
|
|
34
|
-
};
|
|
35
|
-
doc.addEventListener("DOMContentLoaded", schedule);
|
|
36
|
-
doc.addEventListener("visibilitychange", schedule);
|
|
37
|
-
doc.addEventListener("prerenderingchange", schedule);
|
|
38
|
-
schedule();
|
|
39
|
-
return () => {
|
|
40
|
-
active = false;
|
|
41
|
-
cancelPending();
|
|
42
|
-
doc.removeEventListener("DOMContentLoaded", schedule);
|
|
43
|
-
doc.removeEventListener("visibilitychange", schedule);
|
|
44
|
-
doc.removeEventListener("prerenderingchange", schedule);
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
7
|
var READER_SSO_BOT_PATTERN = /bot|crawl|spider|headless|lighthouse|pagespeed|preview|slurp|facebookexternalhit|embedly|python|curl|wget|phantom|puppeteer|playwright|selenium|prerender/i;
|
|
48
8
|
var READER_SSO_PRIVATE_PATHS = [
|
|
49
9
|
/^\/account(\/|$)/,
|
|
@@ -86,72 +46,7 @@ function isFormField(element) {
|
|
|
86
46
|
const tag = focused.tagName;
|
|
87
47
|
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || focused.isContentEditable === true;
|
|
88
48
|
}
|
|
89
|
-
|
|
90
|
-
function readAttempt() {
|
|
91
|
-
try {
|
|
92
|
-
const raw = window.sessionStorage.getItem(ATTEMPT_KEY);
|
|
93
|
-
const value = raw ? Number(raw) : NaN;
|
|
94
|
-
return Number.isFinite(value) ? value : null;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function writeAttempt(now) {
|
|
100
|
-
try {
|
|
101
|
-
window.sessionStorage.setItem(ATTEMPT_KEY, String(now));
|
|
102
|
-
} catch {
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function browserEnvironment(flags) {
|
|
106
|
-
const doc = document;
|
|
107
|
-
let framed = false;
|
|
108
|
-
try {
|
|
109
|
-
framed = window.top !== window.self;
|
|
110
|
-
} catch {
|
|
111
|
-
framed = true;
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
...flags,
|
|
115
|
-
webdriver: navigator.webdriver === true,
|
|
116
|
-
userAgent: navigator.userAgent,
|
|
117
|
-
pathname: location.pathname,
|
|
118
|
-
search: location.search,
|
|
119
|
-
fieldFocused: isFormField(document.activeElement),
|
|
120
|
-
prerendering: doc.prerendering === true,
|
|
121
|
-
visibilityState: document.visibilityState,
|
|
122
|
-
framed,
|
|
123
|
-
lastAttemptAt: readAttempt(),
|
|
124
|
-
now: Date.now()
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function ReaderSsoBoot({ enabled = true, hasSession: hasSessionCookie, suppressed, startPath = "/api/news/ledvery-start", sessionPath = "/api/news/session", privatePaths, idleDelayMs = 1500 }) {
|
|
128
|
-
const [staleCookie, setStaleCookie] = react.useState(false);
|
|
129
|
-
react.useEffect(() => {
|
|
130
|
-
if (!enabled || !hasSessionCookie || staleCookie) return;
|
|
131
|
-
let cancelled = false;
|
|
132
|
-
fetch(sessionPath, { credentials: "same-origin", cache: "no-store" }).then((response) => response.json()).then((body) => {
|
|
133
|
-
if (!cancelled && (body?.authenticated !== true || body.session_upgrade_required === true)) setStaleCookie(true);
|
|
134
|
-
}).catch(() => {
|
|
135
|
-
});
|
|
136
|
-
return () => {
|
|
137
|
-
cancelled = true;
|
|
138
|
-
};
|
|
139
|
-
}, [enabled, hasSessionCookie, staleCookie, sessionPath]);
|
|
140
|
-
const hasSession = hasSessionCookie && !staleCookie;
|
|
141
|
-
react.useEffect(() => {
|
|
142
|
-
const flags = { enabled, hasSession, suppressed, privatePaths };
|
|
143
|
-
return mountReaderSsoScheduler({
|
|
144
|
-
document,
|
|
145
|
-
timers: window,
|
|
146
|
-
idleDelayMs,
|
|
147
|
-
eligible: () => silentSignInDecision(browserEnvironment(flags)).attempt === true,
|
|
148
|
-
attempt: () => {
|
|
149
|
-
const env = browserEnvironment(flags);
|
|
150
|
-
writeAttempt(env.now);
|
|
151
|
-
window.location.assign(silentSignInUrl(startPath, env.pathname, env.search));
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
}, [enabled, hasSession, suppressed, startPath, privatePaths, idleDelayMs]);
|
|
49
|
+
function ReaderSsoBoot(_props) {
|
|
155
50
|
return null;
|
|
156
51
|
}
|
|
157
52
|
function firstName(fullName) {
|
|
@@ -28,16 +28,18 @@ type SilentSignInDecision = {
|
|
|
28
28
|
attempt: false;
|
|
29
29
|
reason: string;
|
|
30
30
|
};
|
|
31
|
+
/** @deprecated No automatic reader sign-in should use this decision. */
|
|
31
32
|
declare function silentSignInDecision(env: SilentSignInEnvironment): SilentSignInDecision;
|
|
33
|
+
/** @deprecated Use an explicit, user-initiated handoff flow instead. */
|
|
32
34
|
declare function silentSignInUrl(startPath: string, pathname: string, search: string): string;
|
|
33
35
|
declare function isFormField(element: Element | null): boolean;
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Compatibility export for publications that still mount the old boot island.
|
|
38
|
+
* Reader identity is checked by the same-origin session UI; authentication
|
|
39
|
+
* must begin with an intentional click, never a timer or page-load navigation.
|
|
40
|
+
* @deprecated Remove this component from publication layouts.
|
|
39
41
|
*/
|
|
40
|
-
declare function ReaderSsoBoot(
|
|
42
|
+
declare function ReaderSsoBoot(_props: {
|
|
41
43
|
enabled?: boolean;
|
|
42
44
|
/** A reader session cookie is present (it may have been revoked elsewhere). */
|
|
43
45
|
hasSession: boolean;
|
|
@@ -28,16 +28,18 @@ type SilentSignInDecision = {
|
|
|
28
28
|
attempt: false;
|
|
29
29
|
reason: string;
|
|
30
30
|
};
|
|
31
|
+
/** @deprecated No automatic reader sign-in should use this decision. */
|
|
31
32
|
declare function silentSignInDecision(env: SilentSignInEnvironment): SilentSignInDecision;
|
|
33
|
+
/** @deprecated Use an explicit, user-initiated handoff flow instead. */
|
|
32
34
|
declare function silentSignInUrl(startPath: string, pathname: string, search: string): string;
|
|
33
35
|
declare function isFormField(element: Element | null): boolean;
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Compatibility export for publications that still mount the old boot island.
|
|
38
|
+
* Reader identity is checked by the same-origin session UI; authentication
|
|
39
|
+
* must begin with an intentional click, never a timer or page-load navigation.
|
|
40
|
+
* @deprecated Remove this component from publication layouts.
|
|
39
41
|
*/
|
|
40
|
-
declare function ReaderSsoBoot(
|
|
42
|
+
declare function ReaderSsoBoot(_props: {
|
|
41
43
|
enabled?: boolean;
|
|
42
44
|
/** A reader session cookie is present (it may have been revoked elsewhere). */
|
|
43
45
|
hasSession: boolean;
|
|
@@ -2,46 +2,6 @@
|
|
|
2
2
|
import { useState, useEffect, useId } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
|
-
// src/reader-sso-scheduler.ts
|
|
6
|
-
function mountReaderSsoScheduler({ document: doc, timers, eligible, attempt, idleDelayMs }) {
|
|
7
|
-
let active = true;
|
|
8
|
-
let attempted = false;
|
|
9
|
-
let idle;
|
|
10
|
-
let delay;
|
|
11
|
-
const cancelPending = () => {
|
|
12
|
-
if (idle !== void 0) timers.cancelIdleCallback?.(idle);
|
|
13
|
-
if (delay !== void 0) timers.clearTimeout(delay);
|
|
14
|
-
idle = delay = void 0;
|
|
15
|
-
};
|
|
16
|
-
const run = () => {
|
|
17
|
-
delay = void 0;
|
|
18
|
-
if (!active || attempted || !eligible()) return;
|
|
19
|
-
attempted = true;
|
|
20
|
-
attempt();
|
|
21
|
-
};
|
|
22
|
-
const defer = () => {
|
|
23
|
-
idle = void 0;
|
|
24
|
-
if (active && !attempted) delay = timers.setTimeout(run, idleDelayMs);
|
|
25
|
-
};
|
|
26
|
-
const schedule = () => {
|
|
27
|
-
if (!active || attempted) return;
|
|
28
|
-
cancelPending();
|
|
29
|
-
if (doc.readyState === "loading" || !eligible()) return;
|
|
30
|
-
if (timers.requestIdleCallback) idle = timers.requestIdleCallback(defer, { timeout: 4e3 });
|
|
31
|
-
else defer();
|
|
32
|
-
};
|
|
33
|
-
doc.addEventListener("DOMContentLoaded", schedule);
|
|
34
|
-
doc.addEventListener("visibilitychange", schedule);
|
|
35
|
-
doc.addEventListener("prerenderingchange", schedule);
|
|
36
|
-
schedule();
|
|
37
|
-
return () => {
|
|
38
|
-
active = false;
|
|
39
|
-
cancelPending();
|
|
40
|
-
doc.removeEventListener("DOMContentLoaded", schedule);
|
|
41
|
-
doc.removeEventListener("visibilitychange", schedule);
|
|
42
|
-
doc.removeEventListener("prerenderingchange", schedule);
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
5
|
var READER_SSO_BOT_PATTERN = /bot|crawl|spider|headless|lighthouse|pagespeed|preview|slurp|facebookexternalhit|embedly|python|curl|wget|phantom|puppeteer|playwright|selenium|prerender/i;
|
|
46
6
|
var READER_SSO_PRIVATE_PATHS = [
|
|
47
7
|
/^\/account(\/|$)/,
|
|
@@ -84,72 +44,7 @@ function isFormField(element) {
|
|
|
84
44
|
const tag = focused.tagName;
|
|
85
45
|
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || focused.isContentEditable === true;
|
|
86
46
|
}
|
|
87
|
-
|
|
88
|
-
function readAttempt() {
|
|
89
|
-
try {
|
|
90
|
-
const raw = window.sessionStorage.getItem(ATTEMPT_KEY);
|
|
91
|
-
const value = raw ? Number(raw) : NaN;
|
|
92
|
-
return Number.isFinite(value) ? value : null;
|
|
93
|
-
} catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function writeAttempt(now) {
|
|
98
|
-
try {
|
|
99
|
-
window.sessionStorage.setItem(ATTEMPT_KEY, String(now));
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function browserEnvironment(flags) {
|
|
104
|
-
const doc = document;
|
|
105
|
-
let framed = false;
|
|
106
|
-
try {
|
|
107
|
-
framed = window.top !== window.self;
|
|
108
|
-
} catch {
|
|
109
|
-
framed = true;
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
...flags,
|
|
113
|
-
webdriver: navigator.webdriver === true,
|
|
114
|
-
userAgent: navigator.userAgent,
|
|
115
|
-
pathname: location.pathname,
|
|
116
|
-
search: location.search,
|
|
117
|
-
fieldFocused: isFormField(document.activeElement),
|
|
118
|
-
prerendering: doc.prerendering === true,
|
|
119
|
-
visibilityState: document.visibilityState,
|
|
120
|
-
framed,
|
|
121
|
-
lastAttemptAt: readAttempt(),
|
|
122
|
-
now: Date.now()
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function ReaderSsoBoot({ enabled = true, hasSession: hasSessionCookie, suppressed, startPath = "/api/news/ledvery-start", sessionPath = "/api/news/session", privatePaths, idleDelayMs = 1500 }) {
|
|
126
|
-
const [staleCookie, setStaleCookie] = useState(false);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (!enabled || !hasSessionCookie || staleCookie) return;
|
|
129
|
-
let cancelled = false;
|
|
130
|
-
fetch(sessionPath, { credentials: "same-origin", cache: "no-store" }).then((response) => response.json()).then((body) => {
|
|
131
|
-
if (!cancelled && (body?.authenticated !== true || body.session_upgrade_required === true)) setStaleCookie(true);
|
|
132
|
-
}).catch(() => {
|
|
133
|
-
});
|
|
134
|
-
return () => {
|
|
135
|
-
cancelled = true;
|
|
136
|
-
};
|
|
137
|
-
}, [enabled, hasSessionCookie, staleCookie, sessionPath]);
|
|
138
|
-
const hasSession = hasSessionCookie && !staleCookie;
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
const flags = { enabled, hasSession, suppressed, privatePaths };
|
|
141
|
-
return mountReaderSsoScheduler({
|
|
142
|
-
document,
|
|
143
|
-
timers: window,
|
|
144
|
-
idleDelayMs,
|
|
145
|
-
eligible: () => silentSignInDecision(browserEnvironment(flags)).attempt === true,
|
|
146
|
-
attempt: () => {
|
|
147
|
-
const env = browserEnvironment(flags);
|
|
148
|
-
writeAttempt(env.now);
|
|
149
|
-
window.location.assign(silentSignInUrl(startPath, env.pathname, env.search));
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}, [enabled, hasSession, suppressed, startPath, privatePaths, idleDelayMs]);
|
|
47
|
+
function ReaderSsoBoot(_props) {
|
|
153
48
|
return null;
|
|
154
49
|
}
|
|
155
50
|
function firstName(fullName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.88",
|
|
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": {
|