@matdata/yasgui 5.4.0 → 5.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@matdata/yasgui",
3
3
  "description": "Yet Another SPARQL GUI",
4
- "version": "5.4.0",
4
+ "version": "5.6.0",
5
5
  "main": "build/yasgui.min.js",
6
6
  "types": "build/ts/src/index.d.ts",
7
7
  "license": "MIT",
@@ -304,6 +304,9 @@ export function parseFromTurtle(turtle: string): Partial<PersistedJson> {
304
304
  headers: {},
305
305
  withCredentials: false,
306
306
  adjustQueryBeforeRequest: false,
307
+ basicAuth: undefined,
308
+ bearerAuth: undefined,
309
+ apiKeyAuth: undefined,
307
310
  },
308
311
  yasr: {
309
312
  settings: {},
@@ -1,5 +1,5 @@
1
1
  import { Storage as YStorage } from "@matdata/yasgui-utils";
2
- import Yasgui, { EndpointButton } from "./";
2
+ import Yasgui, { EndpointButton, EndpointConfig } from "./";
3
3
  import * as Tab from "./Tab";
4
4
  export var storageNamespace = "triply";
5
5
  export interface PersistedJson {
@@ -10,9 +10,11 @@ export interface PersistedJson {
10
10
  lastClosedTab: { index: number; tab: Tab.PersistedJson } | undefined;
11
11
  prefixes?: string;
12
12
  autoCaptureEnabled?: boolean;
13
- customEndpointButtons?: EndpointButton[];
13
+ customEndpointButtons?: EndpointButton[]; // Legacy, kept for backwards compatibility
14
+ endpointConfigs?: EndpointConfig[]; // New endpoint-based storage with auth
14
15
  theme?: "light" | "dark";
15
16
  orientation?: "vertical" | "horizontal";
17
+ showSnippetsBar?: boolean;
16
18
  }
17
19
  function getDefaults(): PersistedJson {
18
20
  return {
@@ -24,6 +26,7 @@ function getDefaults(): PersistedJson {
24
26
  prefixes: "",
25
27
  autoCaptureEnabled: true,
26
28
  customEndpointButtons: [],
29
+ endpointConfigs: [],
27
30
  };
28
31
  }
29
32
 
@@ -163,6 +166,55 @@ export default class PersistentConfig {
163
166
  this.persistedJson.customEndpointButtons = buttons;
164
167
  this.toStorage();
165
168
  }
169
+
170
+ public getShowSnippetsBar(): boolean | undefined {
171
+ return this.persistedJson.showSnippetsBar;
172
+ }
173
+
174
+ public setShowSnippetsBar(show: boolean) {
175
+ this.persistedJson.showSnippetsBar = show;
176
+ this.toStorage();
177
+ }
178
+
179
+ // New endpoint configuration methods
180
+ public getEndpointConfigs(): EndpointConfig[] {
181
+ return this.persistedJson.endpointConfigs || [];
182
+ }
183
+
184
+ public setEndpointConfigs(configs: EndpointConfig[]) {
185
+ this.persistedJson.endpointConfigs = configs;
186
+ this.toStorage();
187
+ }
188
+
189
+ public addOrUpdateEndpoint(endpoint: string, updates: Partial<Omit<EndpointConfig, "endpoint">>) {
190
+ const configs = this.getEndpointConfigs();
191
+ const existingIndex = configs.findIndex((c) => c.endpoint === endpoint);
192
+
193
+ if (existingIndex >= 0) {
194
+ // Update existing endpoint
195
+ const merged = { ...configs[existingIndex], ...updates };
196
+ if ("authentication" in updates && updates.authentication === undefined) {
197
+ delete merged.authentication;
198
+ }
199
+ configs[existingIndex] = merged;
200
+ } else {
201
+ // Add new endpoint
202
+ configs.push({ endpoint, ...updates });
203
+ }
204
+
205
+ this.setEndpointConfigs(configs);
206
+ }
207
+
208
+ public getEndpointConfig(endpoint: string): EndpointConfig | undefined {
209
+ const configs = this.getEndpointConfigs();
210
+ return configs.find((c) => c.endpoint === endpoint);
211
+ }
212
+
213
+ public deleteEndpointConfig(endpoint: string) {
214
+ const configs = this.getEndpointConfigs();
215
+ const filtered = configs.filter((c) => c.endpoint !== endpoint);
216
+ this.setEndpointConfigs(filtered);
217
+ }
166
218
  public static clear() {
167
219
  const storage = new YStorage(storageNamespace);
168
220
  storage.removeNamespace();
package/src/Tab.ts CHANGED
@@ -20,9 +20,11 @@ const VERTICAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24" class="svgImg">
20
20
  <rect x="2" y="2" width="20" height="8" stroke="currentColor" stroke-width="2" fill="none"/>
21
21
  <rect x="2" y="12" width="20" height="10" stroke="currentColor" stroke-width="2" fill="none"/>
22
22
  </svg>`;
23
+
23
24
  export interface PersistedJsonYasr extends YasrPersistentConfig {
24
25
  responseSummary: Parser.ResponseSummary;
25
26
  }
27
+
26
28
  export interface PersistedJson {
27
29
  name: string;
28
30
  id: string;
@@ -37,6 +39,7 @@ export interface PersistedJson {
37
39
  requestConfig: YasguiRequestConfig;
38
40
  orientation?: "vertical" | "horizontal";
39
41
  }
42
+
40
43
  export interface Tab {
41
44
  on(event: string | symbol, listener: (...args: any[]) => void): this;
42
45
 
@@ -59,6 +62,7 @@ export interface Tab {
59
62
  on(event: "autocompletionClose", listener: (tab: Tab) => void): this;
60
63
  emit(event: "autocompletionClose", tab: Tab): boolean;
61
64
  }
65
+
62
66
  export class Tab extends EventEmitter {
63
67
  private persistentJson: PersistedJson;
64
68
  public yasgui: Yasgui;
@@ -73,6 +77,7 @@ export class Tab extends EventEmitter {
73
77
  private settingsModal?: TabSettingsModal;
74
78
  private currentOrientation: "vertical" | "horizontal";
75
79
  private orientationToggleButton?: HTMLButtonElement;
80
+
76
81
  constructor(yasgui: Yasgui, conf: PersistedJson) {
77
82
  super();
78
83
  if (!conf || conf.id === undefined) throw new Error("Expected a valid configuration to initialize tab with");
@@ -80,15 +85,19 @@ export class Tab extends EventEmitter {
80
85
  this.persistentJson = conf;
81
86
  this.currentOrientation = this.yasgui.config.orientation || "vertical";
82
87
  }
88
+
83
89
  public name() {
84
90
  return this.persistentJson.name;
85
91
  }
92
+
86
93
  public getPersistedJson() {
87
94
  return this.persistentJson;
88
95
  }
96
+
89
97
  public getId() {
90
98
  return this.persistentJson.id;
91
99
  }
100
+
92
101
  private draw() {
93
102
  if (this.rootEl) return; //aready drawn
94
103
  this.rootEl = document.createElement("div");
@@ -128,10 +137,12 @@ export class Tab extends EventEmitter {
128
137
  this.initYasr();
129
138
  this.yasgui._setPanel(this.persistentJson.id, this.rootEl);
130
139
  }
140
+
131
141
  public hide() {
132
142
  removeClass(this.rootEl, "active");
133
143
  this.detachKeyboardListeners();
134
144
  }
145
+
135
146
  public show() {
136
147
  this.draw();
137
148
  addClass(this.rootEl, "active");
@@ -200,9 +211,11 @@ export class Tab extends EventEmitter {
200
211
  private detachKeyboardListeners() {
201
212
  document.removeEventListener("keydown", this.handleKeyDown);
202
213
  }
214
+
203
215
  public select() {
204
216
  this.yasgui.selectTabId(this.persistentJson.id);
205
217
  }
218
+
206
219
  public close() {
207
220
  this.detachKeyboardListeners();
208
221
  if (this.yasqe) this.yasqe.abortQuery();
@@ -222,12 +235,14 @@ export class Tab extends EventEmitter {
222
235
  this.yasgui.tabElements.get(this.persistentJson.id).delete();
223
236
  delete this.yasgui._tabs[this.persistentJson.id];
224
237
  }
238
+
225
239
  public getQuery() {
226
240
  if (!this.yasqe) {
227
241
  throw new Error("Cannot get value from uninitialized editor");
228
242
  }
229
243
  return this.yasqe?.getValue();
230
244
  }
245
+
231
246
  public setQuery(query: string) {
232
247
  if (!this.yasqe) {
233
248
  throw new Error("Cannot set value for uninitialized editor");
@@ -237,12 +252,14 @@ export class Tab extends EventEmitter {
237
252
  this.emit("change", this, this.persistentJson);
238
253
  return this;
239
254
  }
255
+
240
256
  public getRequestConfig() {
241
257
  return this.persistentJson.requestConfig;
242
258
  }
259
+
243
260
  private initControlbar() {
244
- this.initEndpointSelectField();
245
261
  this.initOrientationToggle();
262
+ this.initEndpointSelectField();
246
263
  this.initEndpointButtons();
247
264
  if (this.yasgui.config.endpointInfo && this.controlBarEl) {
248
265
  this.controlBarEl.appendChild(this.yasgui.config.endpointInfo());
@@ -313,12 +330,15 @@ export class Tab extends EventEmitter {
313
330
  }
314
331
  }
315
332
  }
333
+
316
334
  public getYasqe() {
317
335
  return this.yasqe;
318
336
  }
337
+
319
338
  public getYasr() {
320
339
  return this.yasr;
321
340
  }
341
+
322
342
  private initTabSettingsMenu() {
323
343
  if (!this.controlBarEl) throw new Error("Need to initialize wrapper elements before drawing tab settings");
324
344
  this.settingsModal = new TabSettingsModal(this, this.controlBarEl);
@@ -359,10 +379,19 @@ export class Tab extends EventEmitter {
359
379
  // Clear existing buttons
360
380
  this.endpointButtonsContainer.innerHTML = "";
361
381
 
362
- // Merge config buttons with custom user buttons
382
+ // Get config buttons (for backwards compatibility)
363
383
  const configButtons = this.yasgui.config.endpointButtons || [];
384
+
385
+ // Get endpoint configs where showAsButton is true
386
+ const endpointConfigs = this.yasgui.persistentConfig.getEndpointConfigs();
387
+ const endpointButtons = endpointConfigs
388
+ .filter((config) => config.showAsButton && config.label)
389
+ .map((config) => ({ endpoint: config.endpoint, label: config.label! }));
390
+
391
+ // Also include legacy custom buttons for backwards compatibility
364
392
  const customButtons = this.yasgui.persistentConfig.getCustomEndpointButtons();
365
- const allButtons = [...configButtons, ...customButtons];
393
+
394
+ const allButtons = [...configButtons, ...endpointButtons, ...customButtons];
366
395
 
367
396
  if (allButtons.length === 0) {
368
397
  // Hide container if no buttons
@@ -388,40 +417,32 @@ export class Tab extends EventEmitter {
388
417
  });
389
418
  }
390
419
 
391
- private checkEndpointForCors(endpoint: string) {
392
- if (this.yasgui.config.corsProxy && !(endpoint in Yasgui.corsEnabled)) {
393
- const askUrl = new URL(endpoint);
394
- askUrl.searchParams.append("query", "ASK {?x ?y ?z}");
395
- fetch(askUrl.toString())
396
- .then(() => {
397
- Yasgui.corsEnabled[endpoint] = true;
398
- })
399
- .catch((e) => {
400
- // CORS error throws `TypeError: NetworkError when attempting to fetch resource.`
401
- Yasgui.corsEnabled[endpoint] = e instanceof TypeError ? false : true;
402
- });
403
- }
404
- }
405
420
  public setEndpoint(endpoint: string, endpointHistory?: string[]) {
406
421
  if (endpoint) endpoint = endpoint.trim();
407
422
  if (endpointHistory && !eq(endpointHistory, this.yasgui.persistentConfig.getEndpointHistory())) {
408
423
  this.yasgui.emit("endpointHistoryChange", this.yasgui, endpointHistory);
409
424
  }
410
- this.checkEndpointForCors(endpoint); //little cost in checking this as we're caching the check results
411
425
 
412
426
  if (this.persistentJson.requestConfig.endpoint !== endpoint) {
413
427
  this.persistentJson.requestConfig.endpoint = endpoint;
414
428
  this.emit("change", this, this.persistentJson);
415
429
  this.emit("endpointChange", this, endpoint);
430
+
431
+ // Auto-track this endpoint in endpoint configs (if not already present)
432
+ if (endpoint && !this.yasgui.persistentConfig.getEndpointConfig(endpoint)) {
433
+ this.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {});
434
+ }
416
435
  }
417
436
  if (this.endpointSelect instanceof EndpointSelect) {
418
437
  this.endpointSelect.setEndpoint(endpoint, endpointHistory);
419
438
  }
420
439
  return this;
421
440
  }
441
+
422
442
  public getEndpoint(): string {
423
443
  return getAsValue(this.persistentJson.requestConfig.endpoint, this.yasgui);
424
444
  }
445
+
425
446
  /**
426
447
  * Updates the position of the Tab's contextmenu
427
448
  * Useful for when being scrolled
@@ -429,21 +450,26 @@ export class Tab extends EventEmitter {
429
450
  public updateContextMenu(): void {
430
451
  this.getTabListEl().redrawContextMenu();
431
452
  }
453
+
432
454
  public getShareableLink(baseURL?: string): string {
433
455
  return shareLink.createShareLink(baseURL || window.location.href, this);
434
456
  }
457
+
435
458
  public getShareObject() {
436
459
  return shareLink.createShareConfig(this);
437
460
  }
461
+
438
462
  private getTabListEl(): TabListEl {
439
463
  return this.yasgui.tabElements.get(this.persistentJson.id);
440
464
  }
465
+
441
466
  public setName(newName: string) {
442
467
  this.getTabListEl().rename(newName);
443
468
  this.persistentJson.name = newName;
444
469
  this.emit("change", this, this.persistentJson);
445
470
  return this;
446
471
  }
472
+
447
473
  public hasResults() {
448
474
  return !!this.yasr?.results;
449
475
  }
@@ -451,10 +477,12 @@ export class Tab extends EventEmitter {
451
477
  public getName() {
452
478
  return this.persistentJson.name;
453
479
  }
480
+
454
481
  public query(): Promise<any> {
455
482
  if (!this.yasqe) return Promise.reject(new Error("No yasqe editor initialized"));
456
483
  return this.yasqe.query();
457
484
  }
485
+
458
486
  public setRequestConfig(requestConfig: Partial<YasguiRequestConfig>) {
459
487
  this.persistentJson.requestConfig = {
460
488
  ...this.persistentJson.requestConfig,
@@ -464,6 +492,47 @@ export class Tab extends EventEmitter {
464
492
  this.emit("change", this, this.persistentJson);
465
493
  }
466
494
 
495
+ /**
496
+ * Get authentication configuration for the current endpoint
497
+ * This retrieves auth from the endpoint-based storage
498
+ */
499
+ private getAuthForCurrentEndpoint() {
500
+ const endpoint = this.getEndpoint();
501
+ if (!endpoint) return undefined;
502
+
503
+ const endpointConfig = this.yasgui.persistentConfig.getEndpointConfig(endpoint);
504
+ if (!endpointConfig || !endpointConfig.authentication) return undefined;
505
+
506
+ // Convert endpoint auth to requestConfig format
507
+ const auth = endpointConfig.authentication;
508
+ if (auth.type === "basic") {
509
+ return {
510
+ type: "basic" as const,
511
+ config: {
512
+ username: auth.username,
513
+ password: auth.password,
514
+ },
515
+ };
516
+ } else if (auth.type === "bearer") {
517
+ return {
518
+ type: "bearer" as const,
519
+ config: {
520
+ token: auth.token,
521
+ },
522
+ };
523
+ } else if (auth.type === "apiKey") {
524
+ return {
525
+ type: "apiKey" as const,
526
+ config: {
527
+ headerName: auth.headerName,
528
+ apiKey: auth.apiKey,
529
+ },
530
+ };
531
+ }
532
+
533
+ return undefined;
534
+ }
535
+
467
536
  /**
468
537
  * The Yasgui configuration object may contain a custom request config
469
538
  * This request config object can contain getter functions, or plain json
@@ -503,6 +572,8 @@ export class Tab extends EventEmitter {
503
572
  persistenceId: null, //yasgui handles persistent storing
504
573
  consumeShareLink: null, //not handled by this tab, but by parent yasgui instance
505
574
  createShareableLink: () => this.getShareableLink(),
575
+ // Use global showSnippetsBar setting if it exists
576
+ showSnippetsBar: this.yasgui.config.showSnippetsBar !== false,
506
577
  requestConfig: () => {
507
578
  const processedReqConfig: YasguiRequestConfig = {
508
579
  //setting defaults
@@ -524,18 +595,20 @@ export class Tab extends EventEmitter {
524
595
  //The adjustQueryBeforeRequest is meant to be a function though, so let's copy that as is
525
596
  adjustQueryBeforeRequest: this.yasgui.config.requestConfig.adjustQueryBeforeRequest,
526
597
  };
527
- if (this.yasgui.config.corsProxy && !Yasgui.corsEnabled[this.getEndpoint()]) {
528
- return {
529
- ...processedReqConfig,
530
- args: [
531
- ...(Array.isArray(processedReqConfig.args) ? processedReqConfig.args : []),
532
- { name: "endpoint", value: this.getEndpoint() },
533
- { name: "method", value: this.persistentJson.requestConfig.method },
534
- ],
535
- method: "POST",
536
- endpoint: this.yasgui.config.corsProxy,
537
- } as PlainRequestConfig;
598
+
599
+ // Inject authentication from endpoint-based storage
600
+ // Only inject endpoint-based auth if the corresponding auth type is not already set
601
+ const endpointAuth = this.getAuthForCurrentEndpoint();
602
+ if (endpointAuth) {
603
+ if (endpointAuth.type === "basic" && typeof processedReqConfig.basicAuth === "undefined") {
604
+ processedReqConfig.basicAuth = endpointAuth.config;
605
+ } else if (endpointAuth.type === "bearer" && typeof processedReqConfig.bearerAuth === "undefined") {
606
+ processedReqConfig.bearerAuth = endpointAuth.config;
607
+ } else if (endpointAuth.type === "apiKey" && typeof processedReqConfig.apiKeyAuth === "undefined") {
608
+ processedReqConfig.apiKeyAuth = endpointAuth.config;
609
+ }
538
610
  }
611
+
539
612
  return processedReqConfig as PlainRequestConfig;
540
613
  },
541
614
  };
@@ -564,6 +637,7 @@ export class Tab extends EventEmitter {
564
637
  // Add Ctrl+Click handler for URIs
565
638
  this.attachYasqeMouseHandler();
566
639
  }
640
+
567
641
  private destroyYasqe() {
568
642
  // As Yasqe extends of CM instead of eventEmitter, it doesn't expose the removeAllListeners function, so we should unregister all events manually
569
643
  this.yasqe?.off("blur", this.handleYasqeBlur);
@@ -578,12 +652,14 @@ export class Tab extends EventEmitter {
578
652
  this.yasqe?.destroy();
579
653
  this.yasqe = undefined;
580
654
  }
655
+
581
656
  handleYasqeBlur = (yasqe: Yasqe) => {
582
657
  this.persistentJson.yasqe.value = yasqe.getValue();
583
658
  // Capture prefixes from query if auto-capture is enabled
584
659
  this.settingsModal?.capturePrefixesFromQuery();
585
660
  this.emit("change", this, this.persistentJson);
586
661
  };
662
+
587
663
  handleYasqeQuery = (yasqe: Yasqe) => {
588
664
  //the blur event might not have fired (e.g. when pressing ctrl-enter). So, we'd like to persist the query as well if needed
589
665
  if (yasqe.getValue() !== this.persistentJson.yasqe.value) {
@@ -592,6 +668,7 @@ export class Tab extends EventEmitter {
592
668
  }
593
669
  this.emit("query", this);
594
670
  };
671
+
595
672
  handleYasqeQueryAbort = () => {
596
673
  this.emit("queryAbort", this);
597
674
  // Hide loading indicator in Yasr
@@ -599,6 +676,7 @@ export class Tab extends EventEmitter {
599
676
  this.yasr.hideLoading();
600
677
  }
601
678
  };
679
+
602
680
  handleYasqeQueryBefore = () => {
603
681
  this.emit("queryBefore", this);
604
682
  // Show loading indicator in Yasr
@@ -606,16 +684,20 @@ export class Tab extends EventEmitter {
606
684
  this.yasr.showLoading();
607
685
  }
608
686
  };
687
+
609
688
  handleYasqeResize = (_yasqe: Yasqe, newSize: string) => {
610
689
  this.persistentJson.yasqe.editorHeight = newSize;
611
690
  this.emit("change", this, this.persistentJson);
612
691
  };
692
+
613
693
  handleAutocompletionShown = (_yasqe: Yasqe, widget: string) => {
614
694
  this.emit("autocompletionShown", this, widget);
615
695
  };
696
+
616
697
  handleAutocompletionClose = (_yasqe: Yasqe) => {
617
698
  this.emit("autocompletionClose", this);
618
699
  };
700
+
619
701
  handleQueryResponse = (_yasqe: Yasqe, response: any, duration: number) => {
620
702
  this.emit("queryResponse", this);
621
703
  if (!this.yasr) throw new Error("Resultset visualizer not initialized. Cannot draw results");
@@ -692,7 +774,8 @@ WHERE {
692
774
  } LIMIT 1000`;
693
775
 
694
776
  // Execute query in background without changing editor content
695
- this.executeBackgroundQuery(constructQuery);
777
+ // Note: void operator is intentional - errors are handled in the catch block of executeBackgroundQuery
778
+ void this.executeBackgroundQuery(constructQuery);
696
779
  };
697
780
 
698
781
  private async executeBackgroundQuery(query: string) {
@@ -703,75 +786,21 @@ WHERE {
703
786
  this.yasr.showLoading();
704
787
  this.emit("queryBefore", this);
705
788
 
706
- // Get the request configuration
707
- const requestConfig = this.yasqe.config.requestConfig;
708
- const config = typeof requestConfig === "function" ? requestConfig(this.yasqe) : requestConfig;
709
-
710
- if (!config.endpoint) {
711
- throw new Error("No endpoint configured");
712
- }
713
-
714
- const endpoint = typeof config.endpoint === "function" ? config.endpoint(this.yasqe) : config.endpoint;
715
- const method = typeof config.method === "function" ? config.method(this.yasqe) : config.method || "POST";
716
- const headers = typeof config.headers === "function" ? config.headers(this.yasqe) : config.headers || {};
717
-
718
- // Prepare request
719
- const searchParams = new URLSearchParams();
720
- searchParams.append("query", query);
721
-
722
- // Add any additional args
723
- if (config.args && Array.isArray(config.args)) {
724
- config.args.forEach((arg: any) => {
725
- if (arg.name && arg.value) {
726
- searchParams.append(arg.name, arg.value);
727
- }
728
- });
729
- }
789
+ // Track query execution time
790
+ const startTime = Date.now();
730
791
 
731
- const fetchOptions: RequestInit = {
732
- method: method,
733
- headers: {
734
- Accept: "text/turtle",
735
- ...headers,
792
+ // Use yasqe's executeQuery with custom query and accept header
793
+ const queryResponse = await Yasqe.Sparql.executeQuery(
794
+ this.yasqe,
795
+ undefined, // Use default config
796
+ {
797
+ customQuery: query,
798
+ customAccept: "text/turtle",
736
799
  },
737
- };
738
-
739
- let url = endpoint;
740
- if (method === "POST") {
741
- fetchOptions.headers = {
742
- ...fetchOptions.headers,
743
- "Content-Type": "application/x-www-form-urlencoded",
744
- };
745
- fetchOptions.body = searchParams.toString();
746
- } else {
747
- const urlObj = new URL(endpoint);
748
- searchParams.forEach((value, key) => {
749
- urlObj.searchParams.append(key, value);
750
- });
751
- url = urlObj.toString();
752
- }
800
+ );
753
801
 
754
- const startTime = Date.now();
755
- const response = await fetch(url, fetchOptions);
756
802
  const duration = Date.now() - startTime;
757
803
 
758
- if (!response.ok) {
759
- throw new Error(`Query failed: ${response.statusText}`);
760
- }
761
-
762
- const result = await response.text();
763
-
764
- // Create a query response object similar to what Yasqe produces
765
- // This includes headers so the Parser can detect the content type
766
- const queryResponse = {
767
- ok: response.ok,
768
- status: response.status,
769
- statusText: response.statusText,
770
- headers: response.headers,
771
- type: response.type,
772
- content: result,
773
- };
774
-
775
804
  // Set the response in Yasr
776
805
  this.yasr.setResponse(queryResponse, duration);
777
806
 
@@ -787,10 +816,17 @@ WHERE {
787
816
  console.error("Background query failed:", error);
788
817
  if (this.yasr) {
789
818
  this.yasr.hideLoading();
790
- // Set error response
819
+ // Set error response with detailed HTTP status if available
820
+ const errorObj: any = error;
821
+ let errorText = error instanceof Error ? error.message : String(error);
822
+
791
823
  this.yasr.setResponse(
792
824
  {
793
- error: error instanceof Error ? error.message : "Unknown error",
825
+ error: {
826
+ status: errorObj.status,
827
+ statusText: errorObj.statusText || (error instanceof Error ? error.name : undefined),
828
+ text: errorText,
829
+ },
794
830
  },
795
831
  0,
796
832
  );
@@ -866,6 +902,7 @@ WHERE {
866
902
  this.emit("change", this, this.persistentJson);
867
903
  });
868
904
  }
905
+
869
906
  destroy() {
870
907
  this.removeAllListeners();
871
908
  this.settingsModal?.destroy();
@@ -875,6 +912,7 @@ WHERE {
875
912
  this.yasr = undefined;
876
913
  this.destroyYasqe();
877
914
  }
915
+
878
916
  public static getDefaults(yasgui?: Yasgui): PersistedJson {
879
917
  return {
880
918
  yasqe: {
@@ -904,11 +942,21 @@ const safeEndpoint = (endpoint: string): string => {
904
942
 
905
943
  function getCorsErrorRenderer(tab: Tab) {
906
944
  return async (error: Parser.ErrorSummary): Promise<HTMLElement | undefined> => {
945
+ // Only show CORS/mixed-content warning for actual network failures (no status code)
946
+ // AND when querying HTTP from HTTPS
907
947
  if (!error.status) {
908
- // Only show this custom error if
909
948
  const shouldReferToHttp =
910
949
  new URL(tab.getEndpoint()).protocol === "http:" && window.location.protocol === "https:";
911
- if (shouldReferToHttp) {
950
+
951
+ // Check if this looks like a network error (not just missing status)
952
+ const isNetworkError =
953
+ !error.text ||
954
+ error.text.indexOf("Request has been terminated") >= 0 ||
955
+ error.text.indexOf("Failed to fetch") >= 0 ||
956
+ error.text.indexOf("NetworkError") >= 0 ||
957
+ error.text.indexOf("Network request failed") >= 0;
958
+
959
+ if (shouldReferToHttp && isNetworkError) {
912
960
  const errorEl = document.createElement("div");
913
961
  const errorSpan = document.createElement("p");
914
962
  errorSpan.innerHTML = `You are trying to query an HTTP endpoint (<a href="${safeEndpoint(
@@ -917,14 +965,7 @@ function getCorsErrorRenderer(tab: Tab) {
917
965
  tab.getEndpoint(),
918
966
  )}</a>) from an HTTP<strong>S</strong> website (<a href="${safeEndpoint(window.location.href)}">${safeEndpoint(
919
967
  window.location.href,
920
- )}</a>).<br>This is not allowed in modern browsers, see <a target="_blank" rel="noopener noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy</a>.`;
921
- if (tab.yasgui.config.nonSslDomain) {
922
- const errorLink = document.createElement("p");
923
- errorLink.innerHTML = `As a workaround, you can use the HTTP version of Yasgui instead: <a href="${tab.getShareableLink(
924
- tab.yasgui.config.nonSslDomain,
925
- )}" target="_blank">${tab.yasgui.config.nonSslDomain}</a>`;
926
- errorSpan.appendChild(errorLink);
927
- }
968
+ )}</a>).<br>This can be blocked in modern browsers, see <a target="_blank" rel="noopener noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy</a>. See also <a href="https://yasgui-doc.matdata.eu/docs/user-guide#querying-local-endpoints">the YasGUI documentation</a> for possible workarounds.`;
928
969
  errorEl.appendChild(errorSpan);
929
970
  return errorEl;
930
971
  }