shabbat-gate 0.4.0 → 0.4.2
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/CHANGELOG.md +50 -1
- package/dist/botPattern.d.ts +2 -2
- package/dist/botPattern.js +30 -3
- package/dist/hebcal.d.ts +6 -1
- package/dist/hebcal.js +12 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,56 @@
|
|
|
3
3
|
All notable changes to this package are documented here. Format loosely follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
|
|
6
|
-
## [0.4.
|
|
6
|
+
## [0.4.2] - Unreleased
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **A window-list refresh during Shabbat or a holiday dropped the closure in progress, so the
|
|
11
|
+
site reopened mid-Shabbat.** Found 2026-09-15 on a consumer site. `fetchWindows` asked Hebcal
|
|
12
|
+
for a range starting *today*; on a closed day the candle lighting was yesterday, so the
|
|
13
|
+
response held only the havdalah, `pairWindows` (which opens a window only on candles) ignored
|
|
14
|
+
it, and the list had no window covering `now`. The cache is fresh for 24 hours, so a list
|
|
15
|
+
fetched on Friday went stale on Saturday and the first visitor after that refreshed it into
|
|
16
|
+
an "open" answer for the rest of Shabbat. Same for Yom Kippur and every Yom Tov day after the
|
|
17
|
+
first. Verified against live Hebcal: a range starting 2026-09-19 does not block Saturday noon,
|
|
18
|
+
one starting 2026-09-21 does not block Yom Kippur noon.
|
|
19
|
+
|
|
20
|
+
The range now starts `HEBCAL_LOOKBACK_DAYS` (4) days back, which covers the longest closure
|
|
21
|
+
(a two-day Rosh Hashana running into Shabbat). Past windows in the list are harmless. Applies
|
|
22
|
+
to the visitor-location windows too. Regression test: a fetch on Saturday noon, with Hebcal's
|
|
23
|
+
start-date filtering simulated, must still report blocked.
|
|
24
|
+
|
|
25
|
+
## [0.4.1] - 2026-08-29
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`adsbot-google` in the crawler allowlist was cloaking under Google Ads policy - caused a
|
|
30
|
+
real Ads + Merchant Center account suspension.** Found 2026-08-29 on a consumer site: during
|
|
31
|
+
a gate closure window, AdsBot-Google (the crawler Google Ads uses to verify what a real ad
|
|
32
|
+
landing page shows) saw the full real site, while a human clicking the same ad saw the holding
|
|
33
|
+
page. Google's suspension notice for "עקיפת מערכות: הסוואה" (circumventing systems: cloaking)
|
|
34
|
+
described exactly this - showing Google's verification bot different content than what users
|
|
35
|
+
see - almost verbatim.
|
|
36
|
+
|
|
37
|
+
Removing the explicit `adsbot-google` term from `BOT_PATTERN`'s alternation alone would **not**
|
|
38
|
+
have fixed this: the pattern's generic `bot` alternative already matches the substring "Bot" in
|
|
39
|
+
"AdsBot-Google", so it stayed allowed regardless. `isBot()` now checks a dedicated
|
|
40
|
+
`AD_VERIFICATION_BOT_PATTERN` first and returns `false` before the broad pattern ever runs, so
|
|
41
|
+
these crawlers are gated exactly like a human visitor. Every other crawler in `BOT_PATTERN`
|
|
42
|
+
(Googlebot, Bingbot, GPTBot, ClaudeBot, etc.) is unaffected - organic/AI indexing during a
|
|
43
|
+
closure window is a different, broadly-tolerated risk category from a dedicated ad-verification
|
|
44
|
+
bot, which ad-network policy specifically requires to see what humans see.
|
|
45
|
+
|
|
46
|
+
Two more crawlers in the same risk category are excluded alongside AdsBot-Google, for the same
|
|
47
|
+
reason and hitting the same "bot" substring problem: **`Storebot-Google`** (Google
|
|
48
|
+
Merchant Center/Shopping's product landing-page verifier - directly relevant here, since
|
|
49
|
+
Merchant Center was the linked account also suspended) and **`adidxbot`** (Microsoft
|
|
50
|
+
Advertising/Bing Ads' equivalent landing-page verification crawler).
|
|
51
|
+
|
|
52
|
+
Regression tests assert `isBot()` returns `false` for AdsBot-Google, AdsBot-Google-Mobile,
|
|
53
|
+
Storebot-Google, and adidxbot user agents.
|
|
54
|
+
|
|
55
|
+
## [0.4.0] - 2026-07-28
|
|
7
56
|
|
|
8
57
|
### Fixed
|
|
9
58
|
|
package/dist/botPattern.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Broad, case-insensitive allowlist for search engine and AI crawlers.
|
|
3
3
|
* Matches let a request through unconditionally, before any gate logic runs.
|
|
4
|
-
* Erring toward "let more things through" is the safe direction
|
|
5
|
-
* the whole point of this list is protecting SEO/crawlability.
|
|
4
|
+
* Erring toward "let more things through" is the safe direction for organic/AI
|
|
5
|
+
* crawlers, since the whole point of this list is protecting SEO/crawlability.
|
|
6
6
|
*/
|
|
7
7
|
export declare const BOT_PATTERN: RegExp;
|
|
8
8
|
export declare function isBot(userAgent: string): boolean;
|
package/dist/botPattern.js
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Broad, case-insensitive allowlist for search engine and AI crawlers.
|
|
3
3
|
* Matches let a request through unconditionally, before any gate logic runs.
|
|
4
|
-
* Erring toward "let more things through" is the safe direction
|
|
5
|
-
* the whole point of this list is protecting SEO/crawlability.
|
|
4
|
+
* Erring toward "let more things through" is the safe direction for organic/AI
|
|
5
|
+
* crawlers, since the whole point of this list is protecting SEO/crawlability.
|
|
6
6
|
*/
|
|
7
|
-
export const BOT_PATTERN = /bot|crawl|spider|slurp|googlebot|google-inspectiontool|
|
|
7
|
+
export const BOT_PATTERN = /bot|crawl|spider|slurp|googlebot|google-inspectiontool|bingbot|duckduckbot|baiduspider|yandex|facebookexternalhit|twitterbot|linkedinbot|whatsapp|telegrambot|discordbot|applebot|gptbot|chatgpt-user|oai-searchbot|ccbot|claudebot|claude-web|anthropic-ai|perplexitybot|google-extended|bytespider|semrushbot|ahrefsbot|mj12bot|petalbot/i;
|
|
8
|
+
/**
|
|
9
|
+
* Dedicated ad/shopping-network verification crawlers are deliberately NOT
|
|
10
|
+
* allowed through the gate, even though their user agents also match the
|
|
11
|
+
* generic `bot` term above (e.g. "AdsBot-Google" and "Storebot-Google" both
|
|
12
|
+
* contain the substring "bot", "adidxbot" ends in it) - excluding them from
|
|
13
|
+
* BOT_PATTERN's alternation alone would do nothing, since that bare substring
|
|
14
|
+
* still matches.
|
|
15
|
+
*
|
|
16
|
+
* Unlike organic/AI indexing crawlers, these bots exist specifically to
|
|
17
|
+
* verify that a live ad or shopping listing's destination page matches what a
|
|
18
|
+
* human clicking it sees - Google Ads, Google Merchant Center/Shopping, and
|
|
19
|
+
* Microsoft Advertising all require exactly that. Letting one bypass the gate
|
|
20
|
+
* means it sees the real site during a closure window while a human visitor
|
|
21
|
+
* sees the holding page - a textbook cloaking pattern, and the exact
|
|
22
|
+
* violation Google's policy team cited when it suspended a consumer's Ads +
|
|
23
|
+
* Merchant Center accounts over this (2026-08-29).
|
|
24
|
+
*
|
|
25
|
+
* - `adsbot-google` - AdsBot-Google / AdsBot-Google-Mobile, verifies Google
|
|
26
|
+
* Ads landing pages.
|
|
27
|
+
* - `storebot-google` - verifies Google Shopping / Merchant Center product
|
|
28
|
+
* landing pages against the submitted feed.
|
|
29
|
+
* - `adidxbot` - Microsoft Advertising's (Bing Ads) equivalent landing-page
|
|
30
|
+
* verification crawler.
|
|
31
|
+
*/
|
|
32
|
+
const AD_VERIFICATION_BOT_PATTERN = /adsbot-google|storebot-google|adidxbot/i;
|
|
8
33
|
export function isBot(userAgent) {
|
|
34
|
+
if (AD_VERIFICATION_BOT_PATTERN.test(userAgent))
|
|
35
|
+
return false;
|
|
9
36
|
return BOT_PATTERN.test(userAgent);
|
|
10
37
|
}
|
package/dist/hebcal.d.ts
CHANGED
|
@@ -50,6 +50,10 @@ export declare function pairWindows(items: HebcalItem[], defaults?: {
|
|
|
50
50
|
label: string;
|
|
51
51
|
closingLabel: string;
|
|
52
52
|
}): Window[];
|
|
53
|
+
/** How far back the Hebcal range starts. The longest closure is a two-day Rosh
|
|
54
|
+
* Hashana running into Shabbat, whose first candles are lit three days before
|
|
55
|
+
* it ends; four days covers it with a margin for the UTC date boundary. */
|
|
56
|
+
export declare const HEBCAL_LOOKBACK_DAYS = 4;
|
|
53
57
|
export interface FetchWindowsOptions {
|
|
54
58
|
/** `true` (default) = Israel single-day Yom Tov reckoning (`i=on`). `false` =
|
|
55
59
|
* diaspora two-day Yom Tov reckoning (`i=off`), correct for a visitor
|
|
@@ -92,7 +96,8 @@ export declare class HebcalTimeoutError extends Error {
|
|
|
92
96
|
constructor(timeoutMs: number);
|
|
93
97
|
}
|
|
94
98
|
/**
|
|
95
|
-
* Fetches and merges Shabbat + major-holiday windows
|
|
99
|
+
* Fetches and merges Shabbat + major-holiday windows from a few days back (so a
|
|
100
|
+
* closure already in progress is included) to ~45 days ahead, from
|
|
96
101
|
* Hebcal's free public JSON API. Defaults to Israel single-day Yom Tov mode at
|
|
97
102
|
* Jerusalem's timezone; pass `options` to compute windows for a visitor's own
|
|
98
103
|
* location/reckoning instead (see {@link FetchWindowsOptions}).
|
package/dist/hebcal.js
CHANGED
|
@@ -60,6 +60,10 @@ export function pairWindows(items, defaults) {
|
|
|
60
60
|
function toISODate(date) {
|
|
61
61
|
return date.toISOString().slice(0, 10);
|
|
62
62
|
}
|
|
63
|
+
/** How far back the Hebcal range starts. The longest closure is a two-day Rosh
|
|
64
|
+
* Hashana running into Shabbat, whose first candles are lit three days before
|
|
65
|
+
* it ends; four days covers it with a margin for the UTC date boundary. */
|
|
66
|
+
export const HEBCAL_LOOKBACK_DAYS = 4;
|
|
63
67
|
/** Conservative default: Hebcal normally answers in well under a second, and
|
|
64
68
|
* the answer only has to be waited for on a cold cache anyway. */
|
|
65
69
|
export const DEFAULT_HEBCAL_TIMEOUT_MS = 3000;
|
|
@@ -73,7 +77,8 @@ export class HebcalTimeoutError extends Error {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
76
|
-
* Fetches and merges Shabbat + major-holiday windows
|
|
80
|
+
* Fetches and merges Shabbat + major-holiday windows from a few days back (so a
|
|
81
|
+
* closure already in progress is included) to ~45 days ahead, from
|
|
77
82
|
* Hebcal's free public JSON API. Defaults to Israel single-day Yom Tov mode at
|
|
78
83
|
* Jerusalem's timezone; pass `options` to compute windows for a visitor's own
|
|
79
84
|
* location/reckoning instead (see {@link FetchWindowsOptions}).
|
|
@@ -93,8 +98,12 @@ export class HebcalTimeoutError extends Error {
|
|
|
93
98
|
export async function fetchWindows(latitude, longitude, options = {}) {
|
|
94
99
|
const israelMode = options.israelMode ?? true;
|
|
95
100
|
const tzid = options.tzid ?? 'Asia/Jerusalem';
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
// Start a few days back, not today: on a closed day the candle lighting that
|
|
102
|
+
// opened the closure was yesterday, and without it pairWindows drops the
|
|
103
|
+
// whole window - a refresh during Shabbat would reopen the site (2026-09-15).
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
const start = new Date(now - HEBCAL_LOOKBACK_DAYS * 24 * 60 * 60 * 1000);
|
|
106
|
+
const end = new Date(now + 45 * 24 * 60 * 60 * 1000);
|
|
98
107
|
const startParam = toISODate(start);
|
|
99
108
|
const endParam = toISODate(end);
|
|
100
109
|
// A specific city (geonameid) yields Hebcal's official times for that city;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shabbat-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Cloudflare Pages/Workers middleware that closes a site to human visitors during Shabbat and major Jewish holidays (Israel-observance rules), while always letting search engines and AI crawlers through.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|