rapidoc 9.3.2 → 9.3.4

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 (47) hide show
  1. package/.github/workflow/words.yaml +22 -0
  2. package/dist/index.html +1 -1
  3. package/dist/index.html.gz +0 -0
  4. package/dist/rapidoc-min.js +857 -738
  5. package/dist/rapidoc-min.js.LICENSE.txt +9 -10
  6. package/dist/rapidoc-min.js.LICENSE.txt.gz +0 -0
  7. package/dist/rapidoc-min.js.gz +0 -0
  8. package/dist/rapidoc-min.js.map +1 -1
  9. package/dist/rapidoc-min.js.map.gz +0 -0
  10. package/dist/rapidoc.js +9341 -25092
  11. package/dist/report.html +5 -4
  12. package/index.html +2 -3
  13. package/package.json +20 -19
  14. package/src/components/api-request.js +344 -154
  15. package/src/components/api-response.js +16 -12
  16. package/src/components/json-tree.js +17 -12
  17. package/src/components/schema-table.js +50 -42
  18. package/src/components/schema-tree.js +56 -53
  19. package/src/components/tag-input.js +7 -4
  20. package/src/json-schema-viewer.js +1 -2
  21. package/src/rapidoc-mini.js +1 -3
  22. package/src/rapidoc.js +112 -45
  23. package/src/styles/endpoint-styles.js +1 -1
  24. package/src/styles/font-styles.js +1 -1
  25. package/src/styles/info-styles.js +2 -1
  26. package/src/styles/input-styles.js +2 -16
  27. package/src/styles/nav-styles.js +57 -10
  28. package/src/styles/schema-styles.js +27 -15
  29. package/src/styles/tab-styles.js +1 -0
  30. package/src/templates/callback-template.js +6 -7
  31. package/src/templates/code-samples-template.js +1 -0
  32. package/src/templates/components-template.js +3 -5
  33. package/src/templates/endpoint-template.js +41 -27
  34. package/src/templates/expanded-endpoint-template.js +63 -13
  35. package/src/templates/focused-endpoint-template.js +4 -1
  36. package/src/templates/header-template.js +2 -2
  37. package/src/templates/json-schema-viewer-template.js +2 -1
  38. package/src/templates/main-body-template.js +19 -18
  39. package/src/templates/navbar-template.js +98 -81
  40. package/src/templates/overview-template.js +1 -1
  41. package/src/templates/security-scheme-template.js +48 -25
  42. package/src/templates/server-template.js +1 -1
  43. package/src/utils/common-utils.js +2 -5
  44. package/src/utils/schema-utils.js +208 -83
  45. package/src/utils/spec-parser.js +1 -0
  46. package/src/utils/theme.js +11 -5
  47. package/webpack.config.js +2 -1
@@ -4,70 +4,87 @@ import { pathIsInSearch } from '~/utils/common-utils';
4
4
 
5
5
  export function expandCollapseNavBarTag(navLinkEl, action = 'toggle') {
6
6
  const tagAndPathEl = navLinkEl?.closest('.nav-bar-tag-and-paths');
7
+ const pathsUnderTagEl = tagAndPathEl?.querySelector('.nav-bar-paths-under-tag');
7
8
  if (tagAndPathEl) {
8
9
  const isExpanded = tagAndPathEl.classList.contains('expanded');
9
10
  if (isExpanded && (action === 'toggle' || action === 'collapse')) {
11
+ pathsUnderTagEl.style.maxHeight = 0;
10
12
  tagAndPathEl.classList.replace('expanded', 'collapsed');
11
13
  } else if (!isExpanded && (action === 'toggle' || action === 'expand')) {
12
14
  tagAndPathEl.classList.replace('collapsed', 'expanded');
15
+ pathsUnderTagEl.style.maxHeight = `${pathsUnderTagEl.scrollHeight}px`;
13
16
  }
14
17
  }
15
18
  }
16
19
 
17
- export function expandCollapseAll(navEl, action = 'expand-all') {
20
+ export function expandCollapseAll(event, action = 'expand-all') {
21
+ if (!(event.type === 'click' || (event.type === 'keyup' && event.keyCode === 13))) {
22
+ return;
23
+ }
24
+ const navEl = event.target.closest('.nav-scroll');
18
25
  const elList = [...navEl.querySelectorAll('.nav-bar-tag-and-paths')];
19
26
  if (action === 'expand-all') {
20
- elList.map((el) => {
27
+ elList.forEach((el) => {
28
+ const navBarPathsUnderTagEl = el.querySelector('.nav-bar-paths-under-tag');
21
29
  el.classList.replace('collapsed', 'expanded');
30
+ navBarPathsUnderTagEl.style.maxHeight = `${navBarPathsUnderTagEl?.scrollHeight}px`;
22
31
  });
23
32
  } else {
24
- elList.map((el) => {
33
+ elList.forEach((el) => {
25
34
  el.classList.replace('expanded', 'collapsed');
35
+ el.querySelector('.nav-bar-paths-under-tag').style.maxHeight = 0;
26
36
  });
27
37
  }
28
38
  }
29
39
 
30
- function onExpandCollapse(e) {
31
- expandCollapseNavBarTag(e.target, 'toggle');
32
- }
33
-
34
- function onExpandCollapseAll(e, action = 'expand-all') {
35
- expandCollapseAll(e.target.closest('.nav-scroll'), action);
40
+ export function navBarClickAndEnterHandler(event) {
41
+ if (!(event.type === 'click' || (event.type === 'keyup' && event.keyCode === 13))) {
42
+ return;
43
+ }
44
+ const navEl = event.target;
45
+ event.stopPropagation();
46
+ if (navEl.dataset?.action === 'navigate') {
47
+ this.scrollToEventTarget(event, false);
48
+ } else if (navEl.dataset?.action === 'expand-all' || (navEl.dataset?.action === 'collapse-all')) {
49
+ expandCollapseAll(event, navEl.dataset.action);
50
+ } else if (navEl.dataset?.action === 'expand-collapse-tag') {
51
+ expandCollapseNavBarTag(navEl, 'toggle');
52
+ }
36
53
  }
37
54
 
38
55
  /* eslint-disable indent */
39
56
  export default function navbarTemplate() {
40
57
  if (!this.resolvedSpec || this.resolvedSpec.specLoadError) {
41
58
  return html`
42
- <nav class='nav-bar' part="section-navbar">
43
- <slot name="nav-logo" class="logo"></slot>
59
+ <nav class='nav-bar' part='section-navbar'>
60
+ <slot name='nav-logo' class='logo'></slot>
44
61
  </nav>
45
62
  `;
46
63
  }
47
64
  return html`
48
- <nav class='nav-bar ${this.renderStyle}' part="section-navbar">
49
- <slot name="nav-logo" class="logo"></slot>
65
+ <nav class='nav-bar ${this.renderStyle}' part='section-navbar'>
66
+ <slot name='nav-logo' class='logo'></slot>
50
67
  ${(this.allowSearch === 'false' && this.allowAdvancedSearch === 'false')
51
68
  ? ''
52
69
  : html`
53
- <div style="display:flex; flex-direction:row; justify-content:center; align-items:stretch; padding:8px 24px 12px 24px; ${this.allowAdvancedSearch === 'false' ? 'border-bottom: 1px solid var(--nav-hover-bg-color)' : ''}">
70
+ <div style='display:flex; flex-direction:row; justify-content:center; align-items:stretch; padding:8px 24px 12px 24px; ${this.allowAdvancedSearch === 'false' ? 'border-bottom: 1px solid var(--nav-hover-bg-color)' : ''}' part='section-navbar-search'>
54
71
  ${this.allowSearch === 'false'
55
72
  ? ''
56
73
  : html`
57
- <div style="display:flex; flex:1; line-height:22px;">
58
- <input id="nav-bar-search"
59
- part = "textbox textbox-nav-filter"
60
- style = "width:100%; padding-right:20px; color:var(--nav-hover-text-color); border-color:var(--nav-accent-color); background-color:var(--nav-hover-bg-color)"
61
- type = "text"
62
- placeholder = "Filter"
63
- @change = "${this.onSearchChange}"
64
- spellcheck = "false"
74
+ <div style = 'display:flex; flex:1; line-height:22px;'>
75
+ <input id = 'nav-bar-search'
76
+ part = 'textbox textbox-nav-filter'
77
+ style = 'width:100%; padding-right:20px; color:var(--nav-hover-text-color); border-color:var(--nav-accent-color); background-color:var(--nav-hover-bg-color)'
78
+ type = 'text'
79
+ placeholder = 'Filter'
80
+ @change = '${this.onSearchChange}'
81
+ spellcheck = 'false'
65
82
  >
66
- <div style="margin: 6px 5px 0 -24px; font-size:var(--font-size-regular); cursor:pointer;">&#x21a9;</div>
83
+ <div style='margin: 6px 5px 0 -24px; font-size:var(--font-size-regular); cursor:pointer;'>&#x21a9;</div>
67
84
  </div>
68
85
  ${this.matchPaths
69
86
  ? html`
70
- <button @click = '${this.onClearSearch}' class="m-btn thin-border" style="margin-left:5px; color:var(--nav-text-color); width:75px; padding:6px 8px;" part="btn btn-outline btn-clear-filter">
87
+ <button @click = '${this.onClearSearch}' class='m-btn thin-border' style='margin-left:5px; color:var(--nav-text-color); width:75px; padding:6px 8px;' part='btn btn-outline btn-clear-filter'>
71
88
  CLEAR
72
89
  </button>`
73
90
  : ''
@@ -77,7 +94,7 @@ export default function navbarTemplate() {
77
94
  ${this.allowAdvancedSearch === 'false' || this.matchPaths
78
95
  ? ''
79
96
  : html`
80
- <button class="m-btn primary" part="btn btn-fill btn-search" style="margin-left:5px; padding:6px 8px; width:75px" @click="${this.onShowSearchModalClicked}">
97
+ <button class='m-btn primary' part='btn btn-fill btn-search' style='margin-left:5px; padding:6px 8px; width:75px' @click='${this.onShowSearchModalClicked}'>
81
98
  SEARCH
82
99
  </button>
83
100
  `
@@ -85,25 +102,25 @@ export default function navbarTemplate() {
85
102
  </div>
86
103
  `
87
104
  }
88
- ${html`<nav class='nav-scroll' part="section-navbar-scroll">
105
+ ${html`<nav class='nav-scroll' tabindex='-1' part='section-navbar-scroll' @click='${(e) => navBarClickAndEnterHandler.call(this, e)}' @keyup='${(e) => navBarClickAndEnterHandler.call(this, e)}' >
89
106
  ${(this.showInfo === 'false' || !this.resolvedSpec.info)
90
107
  ? ''
91
108
  : html`
92
109
  ${(this.infoDescriptionHeadingsInNavBar === 'true')
93
110
  ? html`
94
111
  ${this.resolvedSpec.infoDescriptionHeaders.length > 0
95
- ? html`<div class='nav-bar-info' id='link-overview' data-content-id='overview' @click = '${(e) => this.scrollToEventTarget(e, false)}'>
112
+ ? html`<div class='nav-bar-info ${this.navActiveItemMarker}' id='link-overview' data-content-id='overview' data-action='navigate' tabindex='0' part='section-navbar-item section-navbar-overview'>
96
113
  ${this.resolvedSpec.info?.title?.trim() || 'Overview'}
97
114
  </div>`
98
115
  : ''
99
116
  }
100
- <div class="overview-headers">
117
+ <div class='overview-headers'>
101
118
  ${this.resolvedSpec.infoDescriptionHeaders.map((header) => html`
102
- <div
103
- class='nav-bar-h${header.depth}'
104
- id="link-overview--${new marked.Slugger().slug(header.text)}"
119
+ <div
120
+ class='nav-bar-h${header.depth} ${this.navActiveItemMarker}'
121
+ id='link-overview--${new marked.Slugger().slug(header.text)}'
122
+ data-action='navigate'
105
123
  data-content-id='overview--${new marked.Slugger().slug(header.text)}'
106
- @click='${(e) => this.scrollToEventTarget(e, false)}'
107
124
  >
108
125
  ${header.text}
109
126
  </div>`)
@@ -111,28 +128,36 @@ export default function navbarTemplate() {
111
128
  </div>
112
129
  ${this.resolvedSpec.infoDescriptionHeaders.length > 0 ? html`<hr style='border-top: 1px solid var(--nav-hover-bg-color); border-width:1px 0 0 0; margin: 15px 0 0 0'/>` : ''}
113
130
  `
114
- : html`<div class='nav-bar-info' id='link-overview' data-content-id='overview' @click = '${(e) => this.scrollToEventTarget(e, false)}'>
115
- ${this.resolvedSpec.info?.title?.trim() || 'Overview'}
116
- </div>`
131
+ : html`<div class='nav-bar-info ${this.navActiveItemMarker}' id='link-overview' data-action='navigate' data-content-id='overview' tabindex='0'>
132
+ ${this.resolvedSpec.info?.title?.trim() || 'Overview'}
133
+ </div>`
117
134
  }
118
135
  `
119
136
  }
120
137
 
121
138
  ${this.allowServerSelection === 'false'
122
139
  ? ''
123
- : html`<div class='nav-bar-info' id='link-servers' data-content-id='servers' @click = '${(e) => this.scrollToEventTarget(e, false)}'> API Servers </div>`
140
+ : html`<div class='nav-bar-info ${this.navActiveItemMarker}' id='link-servers' data-action='navigate' data-content-id='servers' tabindex='0' part='section-navbar-item section-navbar-servers'> API Servers </div>`
124
141
  }
125
142
  ${(this.allowAuthentication === 'false' || !this.resolvedSpec.securitySchemes)
126
143
  ? ''
127
- : html`<div class='nav-bar-info' id='link-auth' data-content-id='auth' @click = '${(e) => this.scrollToEventTarget(e, false)}'> Authentication </div>`
144
+ : html`<div class='nav-bar-info ${this.navActiveItemMarker}' id='link-auth' data-action='navigate' data-content-id='auth' tabindex='0' part='section-navbar-item section-navbar-auth'> Authentication </div>`
128
145
  }
129
146
 
130
- <div id='link-operations-top' class='nav-bar-section operations' data-content-id='operations-top' @click = '${(e) => this.scrollToEventTarget(e, false)}'>
131
- <div style="font-size:16px; display:flex; margin-left:10px;">
147
+ <div id='link-operations-top' class='nav-bar-section operations' data-action='navigate' data-content-id='${this.renderStyle === 'focused' ? '' : 'operations-top'}' part='section-navbar-item section-navbar-operations-top'>
148
+ <div style='font-size:16px; display:flex; margin-left:10px;'>
132
149
  ${this.renderStyle === 'focused'
133
150
  ? html`
134
- <div @click="${(e) => { onExpandCollapseAll.call(this, e, 'expand-all'); }}" title="Expand all" style="transform: rotate(90deg); cursor:pointer; margin-right:10px;">▸</div>
135
- <div @click="${(e) => { onExpandCollapseAll.call(this, e, 'collapse-all'); }}" title="Collapse all" style="transform: rotate(270deg); cursor:pointer;">▸</div>`
151
+ <div class='nav-bar-expand-all'
152
+ data-action='expand-all'
153
+ tabindex='0'
154
+ title='Expand all'
155
+ >▸</div>
156
+ <div class='nav-bar-collapse-all'
157
+ data-action='collapse-all'
158
+ tabindex='0'
159
+ title='Collapse all'
160
+ >▸</div>`
136
161
  : ''
137
162
  }
138
163
  </div>
@@ -143,30 +168,21 @@ export default function navbarTemplate() {
143
168
  ${this.resolvedSpec.tags
144
169
  .filter((tag) => tag.paths.filter((path) => pathIsInSearch(this.matchPaths, path, this.matchType)).length)
145
170
  .map((tag) => html`
146
- <div class='nav-bar-tag-and-paths ${tag.expanded ? 'expanded' : 'collapsed'}'>
171
+ <div class='nav-bar-tag-and-paths ${(this.renderStyle === 'read' ? 'expanded' : (tag.expanded ? 'expanded' : 'collapsed'))}' >
147
172
  ${tag.name === 'General ⦂'
148
- ? html`<hr style="border:none; border-top: 1px dotted var(--nav-text-color); opacity:0.3; margin:-1px 0 0 0;"/>`
173
+ ? html`<hr style='border:none; border-top: 1px dotted var(--nav-text-color); opacity:0.3; margin:-1px 0 0 0;'/>`
149
174
  : html`
150
175
  <div
151
- class='nav-bar-tag'
152
- id="link-${tag.elementId}"
153
- data-content-id='${tag.elementId}'
176
+ class='nav-bar-tag ${this.navActiveItemMarker}'
177
+ part='section-navbar-item section-navbar-tag'
178
+ id='link-${tag.elementId}'
179
+ data-action='${(this.renderStyle === 'read' ? 'navigate' : this.onNavTagClick === 'show-description') ? 'navigate' : 'expand-collapse-tag'}'
180
+ data-content-id='${(this.renderStyle === 'read' ? `${tag.elementId}` : this.onNavTagClick === 'show-description') ? `${tag.elementId}` : ''}'
154
181
  data-first-path-id='${tag.firstPathId}'
155
- @click='${(e) => {
156
- if (this.renderStyle === 'focused' && this.onNavTagClick === 'expand-collapse') {
157
- onExpandCollapse.call(this, e);
158
- } else {
159
- this.scrollToEventTarget(e, false);
160
- }
161
- }}'
182
+ tabindex='0'
162
183
  >
163
- <div>${tag.name}</div>
164
- <div class="nav-bar-tag-icon" @click="${(e) => {
165
- if (this.renderStyle === 'focused' && this.onNavTagClick === 'show-description') {
166
- onExpandCollapse.call(this, e);
167
- }
168
- }}">
169
- </div>
184
+ <div style="pointer-events:none;">${tag.name}</div>
185
+ <div class='nav-bar-tag-icon' tabindex='0' data-action='expand-collapse-tag'></div>
170
186
  </div>
171
187
  `
172
188
  }
@@ -177,19 +193,19 @@ export default function navbarTemplate() {
177
193
  : html`
178
194
  <div class='tag-headers'>
179
195
  ${tag.headers.map((header) => html`
180
- <div
181
- class='nav-bar-h${header.depth}'
182
- id="link-${tag.elementId}--${new marked.Slugger().slug(header.text)}"
183
- data-content-id='${tag.elementId}--${new marked.Slugger().slug(header.text)}'
184
- @click='${(e) => this.scrollToEventTarget(e, false)}'
196
+ <div
197
+ class='nav-bar-h${header.depth} ${this.navActiveItemMarker}'
198
+ part='section-navbar-item section-navbar-h${header.depth}'
199
+ id='link-${tag.elementId}--${new marked.Slugger().slug(header.text)}'
200
+ data-action='navigate'
201
+ data-content-id='${tag.elementId}--${new marked.Slugger().slug(header.text)}'
202
+ tabindex='0'
185
203
  > ${header.text}</div>`)}
186
204
  </div>`
187
205
  }`
188
206
  : ''
189
207
  }
190
-
191
-
192
- <div class='nav-bar-paths-under-tag'>
208
+ <div class='nav-bar-paths-under-tag' style='max-height:${(tag.expanded || this.renderStyle === 'read') ? ((tag.paths?.length || 1) * 50) : 0}px;'>
193
209
  <!-- Paths in each tag (endpoints) -->
194
210
  ${tag.paths.filter((v) => {
195
211
  if (this.matchPaths) {
@@ -198,22 +214,21 @@ export default function navbarTemplate() {
198
214
  return true;
199
215
  }).map((p) => html`
200
216
  <div
201
- class='nav-bar-path
202
- ${this.usePathInNavBar === 'true' ? 'small-font' : ''}'
217
+ class='nav-bar-path ${this.navActiveItemMarker} ${this.usePathInNavBar === 'true' ? 'small-font' : ''}'
218
+ part='section-navbar-item section-navbar-path'
219
+ data-action='navigate'
203
220
  data-content-id='${p.elementId}'
204
221
  id='link-${p.elementId}'
205
- @click = '${(e) => {
206
- this.scrollToEventTarget(e, false);
207
- }}'
222
+ tabindex='0'
208
223
  >
209
- <span style = "display:flex; align-items:start; ${p.deprecated ? 'filter:opacity(0.5)' : ''}">
210
- ${html`<span class="nav-method ${this.showMethodInNavBar} ${p.method}">
224
+ <span style = 'display:flex; pointer-events: none; align-items:start; ${p.deprecated ? 'filter:opacity(0.5)' : ''}'>
225
+ ${html`<span class='nav-method ${this.showMethodInNavBar} ${p.method}' style='pointer-events: none;'>
211
226
  ${this.showMethodInNavBar === 'as-colored-block' ? p.method.substring(0, 3).toUpperCase() : p.method.toUpperCase()}
212
227
  </span>`
213
228
  }
214
- ${p.isWebhook ? html`<span style="font-weight:bold; margin-right:8px; font-size: calc(var(--font-size-small) - 2px)">WEBHOOK</span>` : ''}
229
+ ${p.isWebhook ? html`<span style='font-weight:bold; pointer-events: none; margin-right:8px; font-size: calc(var(--font-size-small) - 2px)'>WEBHOOK</span>` : ''}
215
230
  ${this.usePathInNavBar === 'true'
216
- ? html`<span class='mono-font'>${p.path}</span>`
231
+ ? html`<span style='pointer-events: none;' class='mono-font'>${p.path}</span>`
217
232
  : p.summary || p.shortSummary
218
233
  }
219
234
  </span>
@@ -232,14 +247,16 @@ export default function navbarTemplate() {
232
247
  </div>
233
248
  ${this.resolvedSpec.components.map((component) => (component.subComponents.length
234
249
  ? html`
235
- <div class='nav-bar-tag'
250
+ <div class='nav-bar-tag'
251
+ part='section-navbar-item section-navbar-tag'
252
+ data-action='navigate'
236
253
  data-content-id='cmp--${component.name.toLowerCase()}'
237
- id='link-cmp--${component.name.toLowerCase()}'
238
- @click='${(e) => this.scrollToEventTarget(e, false)}'>
254
+ id='link-cmp--${component.name.toLowerCase()}'
255
+ >
239
256
  ${component.name}
240
257
  </div>
241
258
  ${component.subComponents.filter((p) => p.expanded !== false).map((p) => html`
242
- <div class='nav-bar-path' data-content-id='cmp--${p.id}' id='link-cmp--${p.id}' @click='${(e) => this.scrollToEventTarget(e, false)}'>
259
+ <div class='nav-bar-path' data-action='navigate' data-content-id='cmp--${p.id}' id='link-cmp--${p.id}'>
243
260
  <span> ${p.name} </span>
244
261
  </div>`)
245
262
  }`
@@ -50,7 +50,7 @@ export default function overviewTemplate() {
50
50
  ${this.specUrl && this.allowSpecFileDownload === 'true'
51
51
  ? html`
52
52
  <div style="display:flex; margin:12px 0; gap:8px; justify-content: start;">
53
- <button class="m-btn thin-border" style="width:170px" part="btn btn-outline" @click='${(e) => { downloadResource(this.specUrl, 'openapi-spec', e); }}'>Download OpenAPI spec</button>
53
+ <button class="m-btn thin-border" style="min-width:170px" part="btn btn-outline" @click='${(e) => { downloadResource(this.specUrl, 'openapi-spec', e); }}'>Download OpenAPI spec</button>
54
54
  ${this.specUrl?.trim().toLowerCase().endsWith('json')
55
55
  ? html`<button class="m-btn thin-border" style="width:200px" part="btn btn-outline" @click='${(e) => { viewResource(this.specUrl, e); }}'>View OpenAPI spec (New Tab)</button>`
56
56
  : ''
@@ -16,7 +16,7 @@ export function applyApiKey(securitySchemeId, username = '', password = '', prov
16
16
  let finalApiKeyValue = '';
17
17
  if (securityObj.scheme?.toLowerCase() === 'basic') {
18
18
  if (username) {
19
- finalApiKeyValue = `Basic ${btoa(`${username}:${password}`)}`;
19
+ finalApiKeyValue = `Basic ${Buffer.from(`${username}:${password}`, 'utf8').toString('base64')}`;
20
20
  }
21
21
  } else if (providedApikeyVal) {
22
22
  securityObj.value = providedApikeyVal;
@@ -92,6 +92,10 @@ async function fetchAccessToken(tokenUrl, clientId, clientSecret, redirectUrl, g
92
92
  const urlFormParams = new URLSearchParams();
93
93
  const headers = new Headers();
94
94
  urlFormParams.append('grant_type', grantType);
95
+ if (grantType === 'authorization_code') {
96
+ urlFormParams.append('client_id', clientId);
97
+ urlFormParams.append('client_secret', clientSecret);
98
+ }
95
99
  if (grantType !== 'client_credentials' && grantType !== 'password') {
96
100
  urlFormParams.append('redirect_uri', redirectUrl);
97
101
  }
@@ -100,7 +104,7 @@ async function fetchAccessToken(tokenUrl, clientId, clientSecret, redirectUrl, g
100
104
  urlFormParams.append('code_verifier', codeVerifier); // for PKCE
101
105
  }
102
106
  if (sendClientSecretIn === 'header') {
103
- headers.set('Authorization', `Basic ${btoa(`${clientId}:${clientSecret}`)}`);
107
+ headers.set('Authorization', `Basic ${Buffer.from(`${clientId}:${clientSecret}`, 'utf8').toString('base64')}`);
104
108
  } else {
105
109
  urlFormParams.append('client_id', clientId);
106
110
  urlFormParams.append('client_secret', clientSecret);
@@ -185,8 +189,8 @@ async function onInvokeOAuthFlow(securitySchemeId, flowType, authUrl, tokenUrl,
185
189
  const sendClientSecretIn = authFlowDivEl.querySelector('.oauth-send-client-secret-in') ? authFlowDivEl.querySelector('.oauth-send-client-secret-in').value.trim() : 'header';
186
190
  const checkedScopeEls = [...authFlowDivEl.querySelectorAll('.scope-checkbox:checked')];
187
191
  const pkceCheckboxEl = authFlowDivEl.querySelector(`#${securitySchemeId}-pkce`);
188
- const state = (`${Math.random().toString(36)}random`).slice(2, 9);
189
- const nonce = (`${Math.random().toString(36)}random`).slice(2, 9);
192
+ const state = (`${Math.random().toString(36).slice(2, 9)}random${Math.random().toString(36).slice(2, 9)}`);
193
+ const nonce = (`${Math.random().toString(36).slice(2, 9)}random${Math.random().toString(36).slice(2, 9)}`);
190
194
  // const codeChallenge = await generateCodeChallenge(codeVerifier);
191
195
  const redirectUrlObj = new URL(`${window.location.origin}${window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'))}/${this.oauthReceiver}`);
192
196
  let grantType = '';
@@ -254,15 +258,20 @@ async function onInvokeOAuthFlow(securitySchemeId, flowType, authUrl, tokenUrl,
254
258
 
255
259
  function oAuthFlowTemplate(flowName, clientId, clientSecret, securitySchemeId, authFlow, defaultScopes = [], receiveTokenIn = 'header') {
256
260
  let { authorizationUrl, tokenUrl, refreshUrl } = authFlow;
261
+ const pkceOnly = authFlow['x-pkce-only'] || false;
257
262
  const isUrlAbsolute = (url) => (url.indexOf('://') > 0 || url.indexOf('//') === 0);
263
+ // Calculcate base URL
264
+ const url = new URL(this.selectedServer.computedUrl);
265
+ const baseUrl = url.origin;
266
+
258
267
  if (refreshUrl && !isUrlAbsolute(refreshUrl)) {
259
- refreshUrl = `${this.selectedServer.computedUrl}/${refreshUrl.replace(/^\//, '')}`;
268
+ refreshUrl = `${baseUrl}/${refreshUrl.replace(/^\//, '')}`;
260
269
  }
261
270
  if (tokenUrl && !isUrlAbsolute(tokenUrl)) {
262
- tokenUrl = `${this.selectedServer.computedUrl}/${tokenUrl.replace(/^\//, '')}`;
271
+ tokenUrl = `${baseUrl}/${tokenUrl.replace(/^\//, '')}`;
263
272
  }
264
273
  if (authorizationUrl && !isUrlAbsolute(authorizationUrl)) {
265
- authorizationUrl = `${this.selectedServer.computedUrl}/${authorizationUrl.replace(/^\//, '')}`;
274
+ authorizationUrl = `${baseUrl}/${authorizationUrl.replace(/^\//, '')}`;
266
275
  }
267
276
  let flowNameDisplay;
268
277
  if (flowName === 'authorizationCode') {
@@ -322,7 +331,7 @@ function oAuthFlowTemplate(flowName, clientId, clientSecret, securitySchemeId, a
322
331
  ${flowName === 'authorizationCode'
323
332
  ? html`
324
333
  <div style="margin: 16px 0 4px">
325
- <input type="checkbox" part="checkbox checkbox-auth-scope" id="${securitySchemeId}-pkce" checked>
334
+ <input type="checkbox" part="checkbox checkbox-auth-scope" id="${securitySchemeId}-pkce" checked ?disabled=${pkceOnly}>
326
335
  <label for="${securitySchemeId}-pkce" style="margin:0 16px 0 4px; line-height:24px; cursor:pointer">
327
336
  Send Proof Key for Code Exchange (PKCE)
328
337
  </label>
@@ -333,15 +342,17 @@ function oAuthFlowTemplate(flowName, clientId, clientSecret, securitySchemeId, a
333
342
  <input type="text" part="textbox textbox-auth-client-id" value = "${clientId || ''}" placeholder="client-id" spellcheck="false" class="oauth2 ${flowName} ${securitySchemeId} oauth-client-id">
334
343
  ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'password'
335
344
  ? html`
336
- <input type="password" part="textbox textbox-auth-client-secret" value = "${clientSecret || ''}" placeholder="client-secret" spellcheck="false" class="oauth2 ${flowName} ${securitySchemeId} oauth-client-secret" style = "margin:0 5px;">
337
- ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'password'
338
- ? html`
339
- <select style="margin-right:5px;" class="${flowName} ${securitySchemeId} oauth-send-client-secret-in">
340
- <option value = 'header' .selected = ${receiveTokenIn === 'header'} > Authorization Header </option>
341
- <option value = 'request-body' .selected = ${receiveTokenIn === 'request-body'}> Request Body </option>
342
- </select>`
343
- : ''
344
- }`
345
+ <input
346
+ type="password" part="textbox textbox-auth-client-secret"
347
+ value = "${clientSecret || ''}" placeholder="client-secret" spellcheck="false"
348
+ class="oauth2 ${flowName} ${securitySchemeId}
349
+ oauth-client-secret"
350
+ style = "margin:0 5px;${pkceOnly ? 'display:none;' : ''}"
351
+ >
352
+ <select style="margin-right:5px;${pkceOnly ? 'display:none;' : ''}" class="${flowName} ${securitySchemeId} oauth-send-client-secret-in">
353
+ <option value = 'header' .selected = ${receiveTokenIn === 'header'} > Authorization Header </option>
354
+ <option value = 'request-body' .selected = ${receiveTokenIn === 'request-body'}> Request Body </option>
355
+ </select>`
345
356
  : ''
346
357
  }
347
358
  ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'implicit' || flowName === 'password'
@@ -395,7 +406,7 @@ export default function securitySchemeTemplate() {
395
406
  </div>
396
407
  ${this.resolvedSpec.securitySchemes && this.resolvedSpec.securitySchemes.length > 0
397
408
  ? html`
398
- <table id="auth-table" class='m-table padded-12' style="width:100%;">
409
+ <table role="presentation" id="auth-table" class='m-table padded-12' style="width:100%;">
399
410
  ${this.resolvedSpec.securitySchemes.map((v) => html`
400
411
  <tr id="security-scheme-${v.securitySchemeId}" class="${v.type.toLowerCase()}">
401
412
  <td style="max-width:500px; overflow-wrap: break-word;">
@@ -462,7 +473,17 @@ export default function securitySchemeTemplate() {
462
473
  ? html`
463
474
  <tr>
464
475
  <td style="border:none; padding-left:48px">
465
- ${Object.keys(v.flows).map((f) => oAuthFlowTemplate.call(this, f, v['x-client-id'], v['x-client-secret'], v.securitySchemeId, v.flows[f], v['x-default-scopes'], v['x-receive-token-in']))}
476
+ ${Object.keys(v.flows).map((f) => oAuthFlowTemplate
477
+ .call(
478
+ this,
479
+ f,
480
+ (v.flows[f]['x-client-id'] || v['x-client-id'] || ''),
481
+ (v.flows[f]['x-client-secret'] || v['x-client-secret'] || ''),
482
+ v.securitySchemeId,
483
+ v.flows[f],
484
+ (v.flows[f]['x-default-scopes'] || v['x-default-scopes']),
485
+ (v.flows[f]['x-receive-token-in'] || v['x-receive-token-in']),
486
+ ))}
466
487
  </td>
467
488
  </tr>
468
489
  `
@@ -480,6 +501,13 @@ export default function securitySchemeTemplate() {
480
501
  export function pathSecurityTemplate(pathSecurity) {
481
502
  if (this.resolvedSpec.securitySchemes && pathSecurity) {
482
503
  const orSecurityKeys1 = [];
504
+ if (Array.isArray(pathSecurity)) {
505
+ if (pathSecurity.length === 0) {
506
+ return '';
507
+ }
508
+ } else {
509
+ return '';
510
+ }
483
511
  pathSecurity.forEach((pSecurity) => {
484
512
  const andSecurityKeys1 = [];
485
513
  const andKeyTypes = [];
@@ -508,13 +536,8 @@ export function pathSecurityTemplate(pathSecurity) {
508
536
  });
509
537
  return html`<div style="position:absolute; top:3px; right:2px; font-size:var(--font-size-small); line-height: 1.5;">
510
538
  <div style="position:relative; display:flex; min-width:350px; max-width:700px; justify-content: flex-end;">
511
- <svg width="16" height="24">
512
- <g>
513
- <path style="fill: var(--fg3)" d="m13.8,8.5l0,-2.6l0,0c0,-3.2 -2.6,-5.8 -5.8,-5.8s-5.8,2.6 -5.8,5.8l0,0l0,2.6l-2.1,0l0,11.2l16,0l0,-11.2l-2.1,0l-0,0l0,0l0,0l-0,0zm-9.8,-2.6c0,0 0,0 0,0c0,-2.2 1.8,-4 4,-4c2.2,0 4,1.8 4,4c0,0 0,0 0,0l0,2.6l-8.03,0l0,-2.6l0,0l0,0z" />
514
- </g>
515
- </svg>
539
+ <svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" style="stroke:var(--fg3)"> <rect x="5" y="11" width="14" height="10" rx="2" /> <circle cx="12" cy="16" r="1" /> <path d="M8 11v-4a4 4 0 0 1 8 0v4" /></svg>
516
540
  ${orSecurityKeys1.map((orSecurityItem1, i) => html`
517
-
518
541
  ${orSecurityItem1.securityTypes
519
542
  ? html`
520
543
  ${i !== 0 ? html`<div style="padding:3px 4px;"> OR </div>` : ''}
@@ -36,7 +36,7 @@ function serverVarsTemplate() {
36
36
  return this.selectedServer && this.selectedServer.variables
37
37
  ? html`
38
38
  <div class="table-title">SERVER VARIABLES</div>
39
- <table class='m-table'>
39
+ <table class='m-table' role='presentation'>
40
40
  ${Object.entries(this.selectedServer.variables).map((kv) => html`
41
41
  <tr>
42
42
  <td style="vertical-align: middle;" >${kv[0]}</td>
@@ -19,7 +19,7 @@ export function sleep(ms) {
19
19
  }
20
20
 
21
21
  export function copyToClipboard(data, e) {
22
- const btnEl = e.currentTarget;
22
+ const btnEl = e.target;
23
23
  const textArea = document.createElement('textarea');
24
24
  textArea.value = data;
25
25
  textArea.style.position = 'fixed'; // avoid scrolling to bottom
@@ -124,6 +124,7 @@ export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
124
124
  return pathsMatched;
125
125
  }
126
126
 
127
+ /*
127
128
  export function prettyXml(sourceXmlString) {
128
129
  const xmlDoc = new DOMParser().parseFromString(sourceXmlString, 'text/xml');
129
130
  const xsltDoc = new DOMParser().parseFromString([
@@ -144,10 +145,6 @@ export function prettyXml(sourceXmlString) {
144
145
  const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
145
146
  return new XMLSerializer().serializeToString(resultDoc);
146
147
  }
147
- /*
148
- export function hasValidPathInUrlHash(tags) {
149
- return tags.find((tag) => tag.paths.find((path) => window.location.hash.substring(1) === path.elementId));
150
- }
151
148
  */
152
149
 
153
150
  export function downloadResource(url, fileName) {