@pushwoosh/websdk-common 0.0.0 → 6.17.0
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/package.json +30 -5
- package/subscription-widget/SubscriptionWidgetReader.d.ts +23 -0
- package/subscription-widget/SubscriptionWidgetReader.js +307 -0
- package/subscription-widget/constants.d.ts +19 -0
- package/subscription-widget/constants.js +61 -0
- package/subscription-widget/helpers.d.ts +16 -0
- package/subscription-widget/helpers.js +110 -0
- package/subscription-widget/index.d.ts +4 -0
- package/subscription-widget/index.js +3 -0
- package/subscription-widget/types.d.ts +90 -0
- package/subscription-widget/types.js +1 -0
- package/web-popups/WebPopupReader.d.ts +4 -0
- package/web-popups/WebPopupReader.js +1394 -0
- package/web-popups/colorScheme.d.ts +2 -0
- package/web-popups/colorScheme.js +22 -0
- package/web-popups/constants.d.ts +71 -0
- package/web-popups/constants.js +216 -0
- package/web-popups/glyphs.d.ts +8 -0
- package/web-popups/glyphs.js +30 -0
- package/web-popups/helpers.d.ts +84 -0
- package/web-popups/helpers.js +687 -0
- package/web-popups/index.d.ts +9 -0
- package/web-popups/index.js +8 -0
- package/web-popups/layout.d.ts +39 -0
- package/web-popups/layout.js +290 -0
- package/web-popups/palette.d.ts +34 -0
- package/web-popups/palette.js +157 -0
- package/web-popups/templates.d.ts +3 -0
- package/web-popups/templates.js +125 -0
- package/web-popups/timer.d.ts +16 -0
- package/web-popups/timer.js +194 -0
- package/web-popups/types.d.ts +660 -0
- package/web-popups/types.js +10 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type WebPopupTimerRemaining } from './types';
|
|
2
|
+
/** Can this engine compute offsets in the zone? Empty = the contract's UTC, so supported. */
|
|
3
|
+
export declare function webPopupTimeZoneSupported(timezone: string): boolean;
|
|
4
|
+
/** The instant (epoch ms) a wall clock names in a zone, or null when this engine can't place it. */
|
|
5
|
+
export declare function webPopupWallClockMs(endTime: string, timezone: string): number | null;
|
|
6
|
+
/** What's left at `nowMs`, by unit; a passed OR uncomputable deadline reads as all zeros. */
|
|
7
|
+
export declare function webPopupTimerRemaining(endTime: string, timezone: string, nowMs: number): WebPopupTimerRemaining;
|
|
8
|
+
/** The four unit labels a timer draws under its digits. */
|
|
9
|
+
export type WebPopupTimerLabels = {
|
|
10
|
+
days: string;
|
|
11
|
+
hours: string;
|
|
12
|
+
minutes: string;
|
|
13
|
+
seconds: string;
|
|
14
|
+
};
|
|
15
|
+
/** The unit labels for a language code; empty/unknown = English. */
|
|
16
|
+
export declare function webPopupTimerLabels(language: string): WebPopupTimerLabels;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Countdown math: `endTime` is a LOCAL WALL CLOCK in `timezone` (empty = UTC),
|
|
2
|
+
// never an instant. Pure, `now` passed in — the renderer ticks, this computes.
|
|
3
|
+
const WALL_CLOCK = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
4
|
+
// One formatter per zone: the reader asks for the remainder once a second per
|
|
5
|
+
// timer slot, and constructing an Intl.DateTimeFormat is the expensive part.
|
|
6
|
+
const zoneFormatters = new Map();
|
|
7
|
+
function zoneFormatter(timeZone) {
|
|
8
|
+
const cached = zoneFormatters.get(timeZone);
|
|
9
|
+
if (cached) return cached;
|
|
10
|
+
try {
|
|
11
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
12
|
+
timeZone,
|
|
13
|
+
hour12: false,
|
|
14
|
+
year: 'numeric',
|
|
15
|
+
month: '2-digit',
|
|
16
|
+
day: '2-digit',
|
|
17
|
+
hour: '2-digit',
|
|
18
|
+
minute: '2-digit',
|
|
19
|
+
second: '2-digit'
|
|
20
|
+
});
|
|
21
|
+
zoneFormatters.set(timeZone, formatter);
|
|
22
|
+
return formatter;
|
|
23
|
+
} catch {
|
|
24
|
+
// An unknown/unsupported IANA name — the engine can't place a wall clock in it.
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Can this engine compute offsets in the zone? Empty = the contract's UTC, so supported. */
|
|
29
|
+
export function webPopupTimeZoneSupported(timezone) {
|
|
30
|
+
return !timezone || zoneFormatter(timezone) !== null;
|
|
31
|
+
}
|
|
32
|
+
// The zone's offset from UTC at an instant, in ms: render local parts, read
|
|
33
|
+
// them back as if they were UTC, take the difference.
|
|
34
|
+
function zoneOffsetMs(instantMs, formatter) {
|
|
35
|
+
const parts = formatter.formatToParts(new Date(instantMs));
|
|
36
|
+
const read = type => Number(parts.find(part => part.type === type)?.value ?? '0');
|
|
37
|
+
// hour12: false still renders midnight as '24' in some engines.
|
|
38
|
+
const asUtc = Date.UTC(read('year'), read('month') - 1, read('day'), read('hour') % 24, read('minute'), read('second'));
|
|
39
|
+
return asUtc - instantMs;
|
|
40
|
+
}
|
|
41
|
+
const DAY_MS = 86400000;
|
|
42
|
+
// Every offset that can apply to a wall clock this close to a DST switch: the
|
|
43
|
+
// day before, the naive instant, the day after.
|
|
44
|
+
function candidateOffsets(wallAsUtc, formatter) {
|
|
45
|
+
const offsets = [];
|
|
46
|
+
for (const probe of [wallAsUtc - DAY_MS, wallAsUtc, wallAsUtc + DAY_MS]) {
|
|
47
|
+
const offset = zoneOffsetMs(probe, formatter);
|
|
48
|
+
if (!offsets.includes(offset)) offsets.push(offset);
|
|
49
|
+
}
|
|
50
|
+
return offsets;
|
|
51
|
+
}
|
|
52
|
+
// The reader asks for the remainder once a second per slot, and the deadline
|
|
53
|
+
// never moves; a handful of entries, cleared wholesale if a document is absurd.
|
|
54
|
+
const deadlines = new Map();
|
|
55
|
+
/** The instant (epoch ms) a wall clock names in a zone, or null when this engine can't place it. */
|
|
56
|
+
export function webPopupWallClockMs(endTime, timezone) {
|
|
57
|
+
const cacheKey = `${endTime}|${timezone}`;
|
|
58
|
+
const cached = deadlines.get(cacheKey);
|
|
59
|
+
if (cached !== undefined) return cached;
|
|
60
|
+
const resolved = resolveWallClockMs(endTime, timezone);
|
|
61
|
+
if (deadlines.size > 100) deadlines.clear();
|
|
62
|
+
deadlines.set(cacheKey, resolved);
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
function resolveWallClockMs(endTime, timezone) {
|
|
66
|
+
const match = WALL_CLOCK.exec((endTime || '').trim());
|
|
67
|
+
if (!match) return null;
|
|
68
|
+
const [, year, month, day, hour, minute, rawSecond] = match.map(Number);
|
|
69
|
+
// The seconds group is optional — an absent one reads as NaN, not 0.
|
|
70
|
+
const second = Number.isNaN(rawSecond) ? 0 : rawSecond;
|
|
71
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
72
|
+
if (hour > 23 || minute > 59 || second > 59) return null;
|
|
73
|
+
const wallAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
74
|
+
// Date.UTC rolls impossible days over (Feb 30 → Mar 2); reject instead.
|
|
75
|
+
const rolled = new Date(wallAsUtc);
|
|
76
|
+
if (rolled.getUTCMonth() !== month - 1 || rolled.getUTCDate() !== day) return null;
|
|
77
|
+
if (!timezone) return wallAsUtc;
|
|
78
|
+
const formatter = zoneFormatter(timezone);
|
|
79
|
+
if (!formatter) return null;
|
|
80
|
+
// One rule for every zone, whatever the sign of its offset: an offset counts
|
|
81
|
+
// when the instant it produces reads back as the wall clock asked for.
|
|
82
|
+
const offsets = candidateOffsets(wallAsUtc, formatter);
|
|
83
|
+
const instants = offsets.filter(offset => zoneOffsetMs(wallAsUtc - offset, formatter) === offset).map(offset => wallAsUtc - offset);
|
|
84
|
+
// An ambiguous wall clock (fall back) takes the FIRST of its two instants.
|
|
85
|
+
if (instants.length > 0) return Math.min(...instants);
|
|
86
|
+
// None reads back: the wall clock is inside a spring-forward gap. The pre-gap
|
|
87
|
+
// offset is the smaller one, so this rolls forward to just past the gap.
|
|
88
|
+
return wallAsUtc - Math.min(...offsets);
|
|
89
|
+
}
|
|
90
|
+
/** What's left at `nowMs`, by unit; a passed OR uncomputable deadline reads as all zeros. */
|
|
91
|
+
export function webPopupTimerRemaining(endTime, timezone, nowMs) {
|
|
92
|
+
const deadline = webPopupWallClockMs(endTime, timezone);
|
|
93
|
+
const total = deadline === null ? 0 : Math.max(0, deadline - nowMs);
|
|
94
|
+
const seconds = Math.floor(total / 1000);
|
|
95
|
+
return {
|
|
96
|
+
total,
|
|
97
|
+
days: Math.floor(seconds / 86400),
|
|
98
|
+
hours: Math.floor(seconds / 3600) % 24,
|
|
99
|
+
minutes: Math.floor(seconds / 60) % 60,
|
|
100
|
+
seconds: seconds % 60
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Unit labels by ISO 639-1 code, the editor's 14 languages; anything else = English.
|
|
104
|
+
// NOTE: not written by native speakers — worth a review before customers see them.
|
|
105
|
+
const TIMER_LABELS = {
|
|
106
|
+
en: {
|
|
107
|
+
days: 'Days',
|
|
108
|
+
hours: 'Hours',
|
|
109
|
+
minutes: 'Minutes',
|
|
110
|
+
seconds: 'Seconds'
|
|
111
|
+
},
|
|
112
|
+
ru: {
|
|
113
|
+
days: 'Дни',
|
|
114
|
+
hours: 'Часы',
|
|
115
|
+
minutes: 'Минуты',
|
|
116
|
+
seconds: 'Секунды'
|
|
117
|
+
},
|
|
118
|
+
de: {
|
|
119
|
+
days: 'Tage',
|
|
120
|
+
hours: 'Stunden',
|
|
121
|
+
minutes: 'Minuten',
|
|
122
|
+
seconds: 'Sekunden'
|
|
123
|
+
},
|
|
124
|
+
es: {
|
|
125
|
+
days: 'Días',
|
|
126
|
+
hours: 'Horas',
|
|
127
|
+
minutes: 'Minutos',
|
|
128
|
+
seconds: 'Segundos'
|
|
129
|
+
},
|
|
130
|
+
fr: {
|
|
131
|
+
days: 'Jours',
|
|
132
|
+
hours: 'Heures',
|
|
133
|
+
minutes: 'Minutes',
|
|
134
|
+
seconds: 'Secondes'
|
|
135
|
+
},
|
|
136
|
+
pt: {
|
|
137
|
+
days: 'Dias',
|
|
138
|
+
hours: 'Horas',
|
|
139
|
+
minutes: 'Minutos',
|
|
140
|
+
seconds: 'Segundos'
|
|
141
|
+
},
|
|
142
|
+
ja: {
|
|
143
|
+
days: '日',
|
|
144
|
+
hours: '時間',
|
|
145
|
+
minutes: '分',
|
|
146
|
+
seconds: '秒'
|
|
147
|
+
},
|
|
148
|
+
ko: {
|
|
149
|
+
days: '일',
|
|
150
|
+
hours: '시간',
|
|
151
|
+
minutes: '분',
|
|
152
|
+
seconds: '초'
|
|
153
|
+
},
|
|
154
|
+
zh: {
|
|
155
|
+
days: '天',
|
|
156
|
+
hours: '小时',
|
|
157
|
+
minutes: '分',
|
|
158
|
+
seconds: '秒'
|
|
159
|
+
},
|
|
160
|
+
hi: {
|
|
161
|
+
days: 'दिन',
|
|
162
|
+
hours: 'घंटे',
|
|
163
|
+
minutes: 'मिनट',
|
|
164
|
+
seconds: 'सेकंड'
|
|
165
|
+
},
|
|
166
|
+
bn: {
|
|
167
|
+
days: 'দিন',
|
|
168
|
+
hours: 'ঘণ্টা',
|
|
169
|
+
minutes: 'মিনিট',
|
|
170
|
+
seconds: 'সেকেন্ড'
|
|
171
|
+
},
|
|
172
|
+
ar: {
|
|
173
|
+
days: 'أيام',
|
|
174
|
+
hours: 'ساعات',
|
|
175
|
+
minutes: 'دقائق',
|
|
176
|
+
seconds: 'ثوان'
|
|
177
|
+
},
|
|
178
|
+
he: {
|
|
179
|
+
days: 'ימים',
|
|
180
|
+
hours: 'שעות',
|
|
181
|
+
minutes: 'דקות',
|
|
182
|
+
seconds: 'שניות'
|
|
183
|
+
},
|
|
184
|
+
th: {
|
|
185
|
+
days: 'วัน',
|
|
186
|
+
hours: 'ชั่วโมง',
|
|
187
|
+
minutes: 'นาที',
|
|
188
|
+
seconds: 'วินาที'
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
/** The unit labels for a language code; empty/unknown = English. */
|
|
192
|
+
export function webPopupTimerLabels(language) {
|
|
193
|
+
return TIMER_LABELS[language] ?? TIMER_LABELS.en;
|
|
194
|
+
}
|