@session-services/web-elements 0.1.30 → 0.1.31

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.
Files changed (22) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/session-services-elements.cjs.js +1 -1
  3. package/dist/cjs/session-services-ticketing.cjs.entry.js +1645 -22994
  4. package/dist/collection/components/session-services-ticketing/bot-detection.js +83 -0
  5. package/dist/collection/components/session-services-ticketing/bot-detection.test.js +74 -0
  6. package/dist/collection/components/session-services-ticketing/session-services-ticketing.css +164 -36
  7. package/dist/collection/components/session-services-ticketing/session-services-ticketing.js +697 -219
  8. package/dist/collection/components/session-services-ticketing/session-services-ticketing.logic.js +25 -4
  9. package/dist/collection/components/session-services-ticketing/session-services-ticketing.logic.test.js +19 -2
  10. package/dist/collection/components/session-services-ticketing/session-services-ticketing.test.js +540 -19
  11. package/dist/components/session-services-ticketing.js +1 -1
  12. package/dist/esm/loader.js +1 -1
  13. package/dist/esm/session-services-elements.js +1 -1
  14. package/dist/esm/session-services-ticketing.entry.js +1645 -22994
  15. package/dist/session-services-elements/p-d7264f6b.entry.js +1 -0
  16. package/dist/session-services-elements/session-services-elements.esm.js +1 -1
  17. package/dist/types/components/session-services-ticketing/bot-detection.d.ts +13 -0
  18. package/dist/types/components/session-services-ticketing/bot-detection.test.d.ts +1 -0
  19. package/dist/types/components/session-services-ticketing/session-services-ticketing.d.ts +52 -5
  20. package/dist/types/components/session-services-ticketing/session-services-ticketing.logic.d.ts +8 -1
  21. package/package.json +4 -4
  22. package/dist/session-services-elements/p-4dd3f723.entry.js +0 -1
@@ -0,0 +1,83 @@
1
+ const PRODUCTION_API_ORIGIN = "https://api.session.services";
2
+ const PUPPETEER_GLOBALS = ["__puppeteer_evaluation_script__", "__puppeteer"];
3
+ const PLAYWRIGHT_GLOBALS = [
4
+ "__playwright",
5
+ "__playwright__binding__",
6
+ "__pw_manual",
7
+ "__pwInitScripts",
8
+ ];
9
+ const SELENIUM_GLOBALS = [
10
+ "_selenium",
11
+ "callSelenium",
12
+ "_Selenium_IDE_Recorder",
13
+ "__selenium_evaluate",
14
+ "__webdriver_evaluate",
15
+ "__webdriver_script_function",
16
+ "__fxdriver_evaluate",
17
+ "domAutomation",
18
+ "domAutomationController",
19
+ ];
20
+ const PHANTOM_GLOBALS = [
21
+ "callPhantom",
22
+ "_phantom",
23
+ "__phantomas",
24
+ "__casper",
25
+ "slimer",
26
+ "__nightmare",
27
+ ];
28
+ const CHROMEDRIVER_PREFIXES = ["$cdc_", "$wdc_", "$chrome_asyncScriptInfo"];
29
+ function environment() {
30
+ if (typeof window === "undefined" ||
31
+ typeof document === "undefined" ||
32
+ typeof navigator === "undefined")
33
+ return undefined;
34
+ return {
35
+ globals: window,
36
+ root: document.documentElement,
37
+ userAgent: navigator.userAgent,
38
+ webdriver: navigator.webdriver,
39
+ };
40
+ }
41
+ function hasGlobal(globals, names) {
42
+ return names.some((name) => name in globals);
43
+ }
44
+ function inspect(env) {
45
+ const keys = Object.keys(env.globals);
46
+ const signals = [
47
+ env.webdriver === true ? "webdriver" : undefined,
48
+ /HeadlessChrome|Playwright|PhantomJS/i.test(env.userAgent)
49
+ ? "automation-user-agent"
50
+ : undefined,
51
+ hasGlobal(env.globals, PUPPETEER_GLOBALS) ? "puppeteer" : undefined,
52
+ hasGlobal(env.globals, PLAYWRIGHT_GLOBALS) ? "playwright" : undefined,
53
+ hasGlobal(env.globals, SELENIUM_GLOBALS) ||
54
+ env.root.hasAttribute("webdriver") ||
55
+ env.root.hasAttribute("selenium")
56
+ ? "selenium"
57
+ : undefined,
58
+ keys.some((key) => CHROMEDRIVER_PREFIXES.some((prefix) => key.startsWith(prefix)))
59
+ ? "chromedriver"
60
+ : undefined,
61
+ hasGlobal(env.globals, PHANTOM_GLOBALS) ? "phantomjs" : undefined,
62
+ ].filter((signal) => signal !== undefined);
63
+ return { bot: signals.length > 0, signals };
64
+ }
65
+ export function detectBot(env) {
66
+ try {
67
+ const current = env ?? environment();
68
+ if (!current)
69
+ return { bot: false, signals: [] };
70
+ return inspect(current);
71
+ }
72
+ catch {
73
+ return { bot: false, signals: [] };
74
+ }
75
+ }
76
+ export function isProductionApi(apiUrl) {
77
+ try {
78
+ return new URL(apiUrl).origin === PRODUCTION_API_ORIGIN;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { detectBot, isProductionApi, } from "./bot-detection";
3
+ function environment(data = {}) {
4
+ return {
5
+ globals: {},
6
+ root: { hasAttribute: () => false },
7
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/140 Safari/537.36",
8
+ webdriver: false,
9
+ ...data,
10
+ };
11
+ }
12
+ describe("bot detection", () => {
13
+ it("allows a normal browser", () => {
14
+ expect(detectBot(environment())).toEqual({ bot: false, signals: [] });
15
+ });
16
+ it.each([
17
+ ["webdriver", environment({ webdriver: true })],
18
+ [
19
+ "automation-user-agent",
20
+ environment({ userAgent: "Mozilla/5.0 HeadlessChrome/140" }),
21
+ ],
22
+ ["puppeteer", environment({ globals: { __puppeteer: {} } })],
23
+ ["playwright", environment({ globals: { __playwright: {} } })],
24
+ ["selenium", environment({ globals: { __selenium_evaluate: {} } })],
25
+ ["chromedriver", environment({ globals: { $cdc_driver: {} } })],
26
+ ["phantomjs", environment({ globals: { callPhantom: {} } })],
27
+ ])("detects the %s signal", (signal, env) => {
28
+ expect(detectBot(env)).toEqual({ bot: true, signals: [signal] });
29
+ });
30
+ it("detects explicit automation document attributes", () => {
31
+ expect(detectBot(environment({
32
+ root: { hasAttribute: (name) => name === "webdriver" },
33
+ }))).toEqual({ bot: true, signals: ["selenium"] });
34
+ });
35
+ it("does not treat weak browser characteristics as bot signals", () => {
36
+ expect(detectBot(environment({
37
+ globals: {
38
+ innerHeight: 600,
39
+ innerWidth: 800,
40
+ },
41
+ }))).toEqual({ bot: false, signals: [] });
42
+ });
43
+ it("allows the Phantom wallet provider global", () => {
44
+ expect(detectBot(environment({ globals: { phantom: { solana: {} } } }))).toEqual({ bot: false, signals: [] });
45
+ });
46
+ it("fails open when browser inspection throws", () => {
47
+ expect(detectBot(environment({
48
+ globals: new Proxy({}, {
49
+ ownKeys() {
50
+ throw new Error("Inspection failed");
51
+ },
52
+ }),
53
+ }))).toEqual({ bot: false, signals: [] });
54
+ });
55
+ });
56
+ describe("production API detection", () => {
57
+ it.each([
58
+ "https://api.session.services",
59
+ "https://api.session.services/",
60
+ "https://api.session.services/events",
61
+ "https://api.session.services:443",
62
+ ])("enables detection for %s", (apiUrl) => {
63
+ expect(isProductionApi(apiUrl)).toBe(true);
64
+ });
65
+ it.each([
66
+ "https://api.sandbox.session.services",
67
+ "https://j-api.dev.session.services",
68
+ "http://localhost:3000",
69
+ "https://api.session.services.example.com",
70
+ "not a URL",
71
+ ])("bypasses detection for %s", (apiUrl) => {
72
+ expect(isProductionApi(apiUrl)).toBe(false);
73
+ });
74
+ });
@@ -176,6 +176,7 @@
176
176
  font-family: var(--internal-font-family);
177
177
  color: var(--internal-foreground);
178
178
  background: var(--internal-background);
179
+ color-scheme: light;
179
180
  }
180
181
 
181
182
  * {
@@ -191,6 +192,11 @@ textarea {
191
192
  font: inherit;
192
193
  }
193
194
 
195
+ button,
196
+ a {
197
+ touch-action: manipulation;
198
+ }
199
+
194
200
  *:focus {
195
201
  outline: none;
196
202
  }
@@ -208,6 +214,8 @@ textarea {
208
214
  }
209
215
 
210
216
  :host([data-theme="dark"]) {
217
+ color-scheme: dark;
218
+
211
219
  /* Dark theme inputs (overridable externally) */
212
220
  --background: transparent;
213
221
  --foreground: oklch(0.967 0.001 286.375);
@@ -309,7 +317,12 @@ textarea {
309
317
  border-radius: calc(var(--internal-radius) - 2px);
310
318
  cursor: pointer;
311
319
  box-shadow: var(--internal-shadow-edge-light);
312
- transition: all var(--internal-transition-fast);
320
+ transition:
321
+ background-color var(--internal-transition-fast),
322
+ border-color var(--internal-transition-fast),
323
+ box-shadow var(--internal-transition-fast),
324
+ opacity var(--internal-transition-fast),
325
+ transform var(--internal-transition-fast);
313
326
  }
314
327
 
315
328
  .external-ticket-button:hover,
@@ -362,7 +375,8 @@ textarea {
362
375
 
363
376
  .door-alert strong,
364
377
  .empty-ticketing-state strong,
365
- .message-state strong {
378
+ .message-state strong,
379
+ .message-state h2 {
366
380
  color: var(--internal-foreground);
367
381
  font-size: var(--internal-font-size-base);
368
382
  font-weight: var(--internal-font-weight-semibold);
@@ -371,12 +385,17 @@ textarea {
371
385
 
372
386
  .door-alert span,
373
387
  .empty-ticketing-state span,
374
- .message-state span {
388
+ .message-state span,
389
+ .message-state p {
375
390
  color: var(--internal-muted-foreground);
376
391
  font-size: var(--internal-font-size-sm);
377
392
  line-height: 1.4;
378
393
  }
379
394
 
395
+ .message-state .checkout-btn {
396
+ margin-top: var(--internal-spacing-sm);
397
+ }
398
+
380
399
  .external-ticket-button {
381
400
  flex-direction: column;
382
401
  gap: calc(var(--internal-spacing-xs) * 0.5);
@@ -434,7 +453,10 @@ textarea {
434
453
  margin: 0 0 var(--internal-spacing-lg) 0;
435
454
  overflow: hidden;
436
455
  width: 100%;
437
- transition: all var(--internal-transition-base);
456
+ transition:
457
+ background-color var(--internal-transition-base),
458
+ border-color var(--internal-transition-base),
459
+ box-shadow var(--internal-transition-base);
438
460
  }
439
461
 
440
462
  .tickets-wrapper > .ticket-section:last-child {
@@ -457,7 +479,6 @@ textarea {
457
479
 
458
480
  .ticket-section.section-sold-out .admissions-list {
459
481
  opacity: 0.6;
460
- pointer-events: none;
461
482
  filter: grayscale(0.3);
462
483
  }
463
484
 
@@ -598,7 +619,10 @@ textarea {
598
619
  font-size: var(--internal-font-size-xs);
599
620
  font-weight: var(--internal-font-weight-medium);
600
621
  cursor: pointer;
601
- transition: all var(--internal-transition-fast);
622
+ transition:
623
+ background-color var(--internal-transition-fast),
624
+ border-color var(--internal-transition-fast),
625
+ color var(--internal-transition-fast);
602
626
  white-space: nowrap;
603
627
  flex-shrink: 0;
604
628
  }
@@ -731,7 +755,10 @@ textarea {
731
755
  background: var(--internal-secondary);
732
756
  border-radius: calc(var(--internal-radius) - 2px);
733
757
  overflow: hidden;
734
- transition: all var(--internal-transition-fast);
758
+ transition:
759
+ background-color var(--internal-transition-fast),
760
+ border-color var(--internal-transition-fast),
761
+ box-shadow var(--internal-transition-fast);
735
762
  }
736
763
 
737
764
  .quantity-controls:has(.quantity-value:focus-visible) {
@@ -750,7 +777,11 @@ textarea {
750
777
  display: flex;
751
778
  align-items: center;
752
779
  justify-content: center;
753
- transition: all var(--internal-transition-fast);
780
+ transition:
781
+ background-color var(--internal-transition-fast),
782
+ color var(--internal-transition-fast),
783
+ opacity var(--internal-transition-fast),
784
+ transform var(--internal-transition-fast);
754
785
  position: relative;
755
786
  }
756
787
 
@@ -913,7 +944,11 @@ textarea {
913
944
  font-size: var(--internal-font-size-xs);
914
945
  font-weight: var(--internal-font-weight-medium);
915
946
  cursor: pointer;
916
- transition: all var(--internal-transition-fast);
947
+ transition:
948
+ background-color var(--internal-transition-fast),
949
+ border-color var(--internal-transition-fast),
950
+ color var(--internal-transition-fast),
951
+ opacity var(--internal-transition-fast);
917
952
  }
918
953
 
919
954
  .coupon-apply:hover:not(:disabled),
@@ -985,12 +1020,40 @@ textarea {
985
1020
  left: 0;
986
1021
  z-index: 40;
987
1022
  display: none;
988
- padding: var(--internal-spacing-md);
1023
+ padding: var(--internal-spacing-md) var(--internal-spacing-md)
1024
+ calc(var(--internal-spacing-md) + env(safe-area-inset-bottom, 0px));
989
1025
  background: var(--internal-card);
990
1026
  border-top: 1px solid var(--internal-border);
991
1027
  box-shadow: var(--internal-shadow-lg);
992
1028
  }
993
1029
 
1030
+ .sticky-door-info {
1031
+ display: flex;
1032
+ flex-direction: column;
1033
+ align-items: center;
1034
+ justify-content: center;
1035
+ gap: calc(var(--internal-spacing-xs) * 0.5);
1036
+ min-height: 2.75rem;
1037
+ padding: var(--internal-spacing-sm) var(--internal-spacing-md);
1038
+ color: var(--internal-foreground);
1039
+ background: var(--internal-secondary);
1040
+ border: 1px solid var(--internal-border);
1041
+ border-radius: calc(var(--internal-radius) - 2px);
1042
+ text-align: center;
1043
+ }
1044
+
1045
+ .sticky-door-info strong {
1046
+ font-size: var(--internal-font-size-sm);
1047
+ font-weight: var(--internal-font-weight-semibold);
1048
+ line-height: 1.25;
1049
+ }
1050
+
1051
+ .sticky-door-info span {
1052
+ color: var(--internal-muted-foreground);
1053
+ font-size: var(--internal-font-size-xs);
1054
+ line-height: 1.3;
1055
+ }
1056
+
994
1057
  .sticky-checkout-btn.selection-prompt {
995
1058
  animation: selection-prompt-shake 400ms ease-in-out;
996
1059
  }
@@ -1008,46 +1071,71 @@ textarea {
1008
1071
  }
1009
1072
  }
1010
1073
 
1011
- .retry-button {
1074
+ .error-message {
1075
+ background: oklch(
1076
+ from var(--internal-destructive) calc(l + 0.4) calc(c * 0.2) h / 0.05
1077
+ );
1078
+ border: 1px solid oklch(from var(--internal-destructive) l c h / 0.2);
1079
+ color: var(--internal-destructive);
1080
+ padding: var(--internal-spacing-sm) var(--internal-spacing-md);
1081
+ border-radius: var(--internal-radius);
1082
+ margin-bottom: var(--internal-spacing-md);
1083
+ font-size: var(--internal-font-size-sm);
1084
+ line-height: 1.4;
1085
+ }
1086
+
1087
+ .inline-status {
1088
+ display: flex;
1089
+ align-items: center;
1090
+ gap: var(--internal-spacing-sm);
1091
+ margin-bottom: var(--internal-spacing-md);
1092
+ color: var(--internal-muted-foreground);
1093
+ font-size: var(--internal-font-size-sm);
1094
+ line-height: 1.4;
1095
+ }
1096
+
1097
+ .inline-retry-btn {
1098
+ display: inline-flex;
1099
+ align-items: center;
1100
+ justify-content: center;
1012
1101
  align-self: flex-start;
1013
- margin-top: var(--internal-spacing-xs);
1102
+ margin-top: var(--internal-spacing-sm);
1103
+ padding: calc(var(--internal-spacing-xs) * 1.5) var(--internal-spacing-md);
1014
1104
  background: var(--internal-primary);
1015
1105
  color: var(--internal-primary-foreground);
1016
1106
  border: 1px solid oklch(from var(--internal-primary) calc(l - 0.08) c h);
1017
- padding: calc(var(--internal-spacing-sm) + var(--internal-spacing-xs))
1018
- var(--internal-spacing-lg);
1019
1107
  border-radius: calc(var(--internal-radius) - 2px);
1108
+ box-shadow: var(--internal-shadow-edge-light);
1020
1109
  font-size: var(--internal-font-size-sm);
1021
- font-weight: var(--internal-font-weight-medium);
1110
+ font-weight: var(--internal-font-weight-semibold);
1022
1111
  cursor: pointer;
1023
- transition: all var(--internal-transition-fast);
1024
- box-shadow: var(--internal-shadow-edge-light);
1025
- }
1026
-
1027
- .retry-button:focus-visible {
1028
- outline: 2px solid var(--internal-ring);
1029
- outline-offset: 2px;
1112
+ transition:
1113
+ background-color var(--internal-transition-fast),
1114
+ border-color var(--internal-transition-fast),
1115
+ opacity var(--internal-transition-fast),
1116
+ transform var(--internal-transition-fast);
1030
1117
  }
1031
1118
 
1032
- .retry-button:hover {
1119
+ .inline-retry-btn:hover:not(:disabled) {
1033
1120
  background: oklch(from var(--internal-primary) calc(l - 0.08) c h);
1034
1121
  transform: translateY(-1px);
1035
1122
  }
1036
1123
 
1037
- .retry-button:active {
1038
- transform: translateY(0);
1124
+ .inline-retry-btn:disabled {
1125
+ opacity: 0.5;
1126
+ cursor: not-allowed;
1039
1127
  }
1040
1128
 
1041
- .error-message {
1042
- background: oklch(
1043
- from var(--internal-destructive) calc(l + 0.4) calc(c * 0.2) h / 0.05
1044
- );
1045
- border: 1px solid oklch(from var(--internal-destructive) l c h / 0.2);
1046
- color: var(--internal-destructive);
1047
- padding: var(--internal-spacing-sm) var(--internal-spacing-md);
1048
- border-radius: var(--internal-radius);
1049
- margin-bottom: var(--internal-spacing-md);
1050
- font-size: var(--internal-font-size-sm);
1129
+ .sr-only {
1130
+ position: absolute;
1131
+ width: 1px;
1132
+ height: 1px;
1133
+ padding: 0;
1134
+ margin: -1px;
1135
+ overflow: hidden;
1136
+ clip: rect(0, 0, 0, 0);
1137
+ white-space: nowrap;
1138
+ border: 0;
1051
1139
  }
1052
1140
 
1053
1141
  /* Responsive adjustments for larger screens */
@@ -1067,6 +1155,11 @@ textarea {
1067
1155
  .sticky-checkout-bar {
1068
1156
  display: block;
1069
1157
  }
1158
+
1159
+ :host([data-sticky-checkout="true"])
1160
+ .session-services-ticketing-container {
1161
+ padding-bottom: calc(6.5rem + env(safe-area-inset-bottom, 0px));
1162
+ }
1070
1163
  }
1071
1164
 
1072
1165
  /* Modal Styles */
@@ -1080,6 +1173,7 @@ textarea {
1080
1173
  padding: var(--internal-spacing-md);
1081
1174
  background: var(--internal-backdrop);
1082
1175
  backdrop-filter: blur(4px);
1176
+ overscroll-behavior: contain;
1083
1177
  animation: modal-backdrop-fade-in var(--internal-transition-base) ease-out;
1084
1178
  }
1085
1179
 
@@ -1098,6 +1192,7 @@ textarea {
1098
1192
  max-width: 32rem;
1099
1193
  max-height: calc(100vh - var(--internal-spacing-xl) * 2);
1100
1194
  overflow-y: auto;
1195
+ overscroll-behavior: contain;
1101
1196
  background: var(--internal-popover);
1102
1197
  color: var(--internal-popover-foreground);
1103
1198
  border: 1px solid var(--internal-border);
@@ -1134,7 +1229,11 @@ textarea {
1134
1229
  color: var(--internal-muted-foreground);
1135
1230
  cursor: pointer;
1136
1231
  opacity: 0.72;
1137
- transition: all var(--internal-transition-fast);
1232
+ transition:
1233
+ background-color var(--internal-transition-fast),
1234
+ border-color var(--internal-transition-fast),
1235
+ color var(--internal-transition-fast),
1236
+ opacity var(--internal-transition-fast);
1138
1237
  }
1139
1238
 
1140
1239
  .modal-close-btn:hover {
@@ -1175,3 +1274,32 @@ textarea {
1175
1274
  white-space: pre-wrap;
1176
1275
  margin: 0;
1177
1276
  }
1277
+
1278
+ @media (prefers-reduced-motion: reduce) {
1279
+ :host,
1280
+ * {
1281
+ scroll-behavior: auto !important;
1282
+ }
1283
+
1284
+ .admission-card.loading-admission,
1285
+ .checkout-spinner,
1286
+ .sticky-checkout-btn.selection-prompt,
1287
+ .modal-backdrop,
1288
+ .modal-content {
1289
+ animation: none;
1290
+ }
1291
+
1292
+ .external-ticket-button,
1293
+ .checkout-btn,
1294
+ .ticket-section,
1295
+ .more-info-badge,
1296
+ .quantity-controls,
1297
+ .quantity-btn,
1298
+ .coupon-row,
1299
+ .coupon-apply,
1300
+ .coupon-remove,
1301
+ .inline-retry-btn,
1302
+ .modal-close-btn {
1303
+ transition: none;
1304
+ }
1305
+ }