rapidoc 9.3.3 → 9.3.5

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 (48) hide show
  1. package/.github/workflow/words.yaml +22 -0
  2. package/README.md +2 -7
  3. package/dist/index.html +1 -1
  4. package/dist/index.html.gz +0 -0
  5. package/dist/rapidoc-min.js +858 -748
  6. package/dist/rapidoc-min.js.LICENSE.txt +14 -9
  7. package/dist/rapidoc-min.js.LICENSE.txt.gz +0 -0
  8. package/dist/rapidoc-min.js.gz +0 -0
  9. package/dist/rapidoc-min.js.map +1 -1
  10. package/dist/rapidoc-min.js.map.gz +0 -0
  11. package/dist/rapidoc.js +11948 -32008
  12. package/dist/report.html +6 -5
  13. package/index.html +2 -1
  14. package/package.json +25 -24
  15. package/src/components/api-request.js +266 -122
  16. package/src/components/api-response.js +11 -10
  17. package/src/components/json-tree.js +17 -12
  18. package/src/components/schema-table.js +58 -49
  19. package/src/components/schema-tree.js +60 -56
  20. package/src/components/tag-input.js +7 -4
  21. package/src/json-schema-viewer.js +2 -1
  22. package/src/rapidoc-mini.js +1 -1
  23. package/src/rapidoc.js +108 -35
  24. package/src/styles/endpoint-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/advance-search-template.js +3 -3
  31. package/src/templates/callback-template.js +3 -3
  32. package/src/templates/code-samples-template.js +1 -0
  33. package/src/templates/components-template.js +3 -5
  34. package/src/templates/endpoint-template.js +39 -26
  35. package/src/templates/expanded-endpoint-template.js +58 -9
  36. package/src/templates/focused-endpoint-template.js +5 -2
  37. package/src/templates/header-template.js +2 -2
  38. package/src/templates/json-schema-viewer-template.js +2 -1
  39. package/src/templates/main-body-template.js +19 -18
  40. package/src/templates/navbar-template.js +99 -82
  41. package/src/templates/overview-template.js +1 -1
  42. package/src/templates/security-scheme-template.js +51 -27
  43. package/src/templates/server-template.js +1 -1
  44. package/src/utils/common-utils.js +2 -2
  45. package/src/utils/schema-utils.js +192 -67
  46. package/src/utils/spec-parser.js +9 -3
  47. package/src/utils/theme.js +9 -3
  48. 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)' : ''}" part="section-navbar-search">
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.displayName}</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,15 +247,17 @@ 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)}'>
243
- <span> ${p.name} </span>
259
+ <div class='nav-bar-path' data-action='navigate' data-content-id='cmp--${p.id}' id='link-cmp--${p.id}'>
260
+ <span style = 'pointer-events: none;'> ${p.name} </span>
244
261
  </div>`)
245
262
  }`
246
263
  : ''))
@@ -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,8 +104,8 @@ 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}`)}`);
104
- } else {
107
+ headers.set('Authorization', `Basic ${Buffer.from(`${clientId}:${clientSecret}`, 'utf8').toString('base64')}`);
108
+ } else if (grantType !== 'authorization_code') {
105
109
  urlFormParams.append('client_id', clientId);
106
110
  urlFormParams.append('client_secret', clientSecret);
107
111
  }
@@ -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 = '';
@@ -252,17 +256,22 @@ async function onInvokeOAuthFlow(securitySchemeId, flowType, authUrl, tokenUrl,
252
256
 
253
257
  /* eslint-disable indent */
254
258
 
255
- function oAuthFlowTemplate(flowName, clientId, clientSecret, securitySchemeId, authFlow, defaultScopes = [], receiveTokenIn = 'header') {
259
+ function oAuthFlowTemplate(flowName, clientId, clientSecret, securitySchemeId, authFlow, defaultScopes = [], receiveTokenIn = 'header', receiveTokenInOptions = undefined) {
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
+ ${(!receiveTokenInOptions || receiveTokenInOptions.includes('header')) ? html`<option value = 'header' .selected = ${receiveTokenIn === 'header'} > Authorization Header </option>` : ''}
354
+ ${(!receiveTokenInOptions || receiveTokenInOptions.includes('request-body')) ? html` <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,18 @@ 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
+ (v.flows[f]['x-receive-token-in-options'] || v['x-receive-token-in-options']),
487
+ ))}
466
488
  </td>
467
489
  </tr>
468
490
  `
@@ -480,6 +502,13 @@ export default function securitySchemeTemplate() {
480
502
  export function pathSecurityTemplate(pathSecurity) {
481
503
  if (this.resolvedSpec.securitySchemes && pathSecurity) {
482
504
  const orSecurityKeys1 = [];
505
+ if (Array.isArray(pathSecurity)) {
506
+ if (pathSecurity.length === 0) {
507
+ return '';
508
+ }
509
+ } else {
510
+ return '';
511
+ }
483
512
  pathSecurity.forEach((pSecurity) => {
484
513
  const andSecurityKeys1 = [];
485
514
  const andKeyTypes = [];
@@ -508,13 +537,8 @@ export function pathSecurityTemplate(pathSecurity) {
508
537
  });
509
538
  return html`<div style="position:absolute; top:3px; right:2px; font-size:var(--font-size-small); line-height: 1.5;">
510
539
  <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>
540
+ <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
541
  ${orSecurityKeys1.map((orSecurityItem1, i) => html`
517
-
518
542
  ${orSecurityItem1.securityTypes
519
543
  ? html`
520
544
  ${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
@@ -55,7 +55,7 @@ export function componentIsInSearch(searchVal, component) {
55
55
 
56
56
  export function pathIsInSearch(searchVal, path, matchType = 'includes') {
57
57
  if (matchType === 'includes') {
58
- const stringToSearch = `${path.method} ${path.path} ${path.summary || path.description || ''} ${path.operationId || ''}`.toLowerCase();
58
+ const stringToSearch = `${path.method} ${path.path} ${path.summary || ''} ${path.description || ''} ${path.operationId || ''}`.toLowerCase();
59
59
  return stringToSearch.includes(searchVal.toLowerCase());
60
60
  }
61
61
  const regex = new RegExp(searchVal, 'i');