openapi-explorer 0.9.310 → 0.9.314

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 (121) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/{openapi-explorer.min.js → browser/openapi-explorer.min.js} +3 -3
  3. package/dist/{openapi-explorer.min.js.map → browser/openapi-explorer.min.js.map} +0 -0
  4. package/dist/es/components/api-request.js +936 -0
  5. package/dist/es/components/api-response.js +184 -0
  6. package/dist/es/components/json-tree.js +67 -0
  7. package/{src → dist/es}/components/mime-types.js +11 -17
  8. package/dist/es/components/schema-table.js +156 -0
  9. package/dist/es/components/schema-tree.js +191 -0
  10. package/dist/es/components/tag-input.js +67 -0
  11. package/{src → dist/es}/openapi-explorer-oauth-handler.js +2 -2
  12. package/{src → dist/es}/openapi-explorer.js +364 -371
  13. package/dist/es/styles/advanced-search-styles.js +2 -0
  14. package/dist/es/styles/api-request-styles.js +2 -0
  15. package/dist/es/styles/border-styles.js +2 -0
  16. package/dist/es/styles/endpoint-styles.js +2 -0
  17. package/dist/es/styles/flex-styles.js +2 -0
  18. package/dist/es/styles/font-styles.js +2 -0
  19. package/dist/es/styles/info-styles.js +2 -0
  20. package/dist/es/styles/input-styles.js +4 -0
  21. package/dist/es/styles/nav-styles.js +2 -0
  22. package/dist/es/styles/prism-styles.js +2 -0
  23. package/dist/es/styles/schema-styles.js +2 -0
  24. package/dist/es/styles/tab-styles.js +2 -0
  25. package/dist/es/styles/table-styles.js +2 -0
  26. package/dist/es/templates/advance-search-template.js +37 -0
  27. package/dist/es/templates/callback-template.js +7 -0
  28. package/dist/es/templates/code-samples-template.js +26 -0
  29. package/dist/es/templates/components-template.js +17 -0
  30. package/dist/es/templates/endpoint-template.js +94 -0
  31. package/dist/es/templates/expanded-endpoint-template.js +32 -0
  32. package/{src → dist/es}/templates/focused-endpoint-template.js +15 -15
  33. package/dist/es/templates/navbar-template.js +46 -0
  34. package/dist/es/templates/overview-template.js +9 -0
  35. package/dist/es/templates/responsiveViewMainBodyTemplate.js +30 -0
  36. package/dist/es/templates/security-scheme-template.js +330 -0
  37. package/dist/es/templates/server-template.js +42 -0
  38. package/{src → dist/es}/utils/color-utils.js +53 -16
  39. package/{src → dist/es}/utils/common-utils.js +18 -18
  40. package/{src → dist/es}/utils/schema-utils.js +248 -124
  41. package/{src → dist/es}/utils/spec-parser.js +112 -71
  42. package/dist/es/utils/theme.js +75 -0
  43. package/{src → dist/es}/utils/xml/xml.js +41 -38
  44. package/dist/lib/components/api-request.js +957 -0
  45. package/dist/lib/components/api-response.js +206 -0
  46. package/dist/lib/components/json-tree.js +82 -0
  47. package/dist/lib/components/mime-types.js +70 -0
  48. package/dist/lib/components/schema-table.js +170 -0
  49. package/dist/lib/components/schema-tree.js +206 -0
  50. package/dist/lib/components/tag-input.js +76 -0
  51. package/dist/lib/openapi-explorer-oauth-handler.js +19 -0
  52. package/dist/lib/openapi-explorer.js +817 -0
  53. package/dist/lib/styles/advanced-search-styles.js +10 -0
  54. package/dist/lib/styles/api-request-styles.js +10 -0
  55. package/dist/lib/styles/border-styles.js +10 -0
  56. package/dist/lib/styles/endpoint-styles.js +10 -0
  57. package/dist/lib/styles/flex-styles.js +10 -0
  58. package/dist/lib/styles/font-styles.js +10 -0
  59. package/dist/lib/styles/info-styles.js +10 -0
  60. package/dist/lib/styles/input-styles.js +11 -0
  61. package/dist/lib/styles/nav-styles.js +10 -0
  62. package/dist/lib/styles/prism-styles.js +10 -0
  63. package/dist/lib/styles/schema-styles.js +10 -0
  64. package/dist/lib/styles/tab-styles.js +10 -0
  65. package/dist/lib/styles/table-styles.js +10 -0
  66. package/dist/lib/templates/advance-search-template.js +42 -0
  67. package/dist/lib/templates/callback-template.js +12 -0
  68. package/dist/lib/templates/code-samples-template.js +36 -0
  69. package/dist/lib/templates/components-template.js +27 -0
  70. package/dist/lib/templates/endpoint-template.js +111 -0
  71. package/dist/lib/templates/expanded-endpoint-template.js +48 -0
  72. package/dist/lib/templates/focused-endpoint-template.js +95 -0
  73. package/dist/lib/templates/navbar-template.js +54 -0
  74. package/dist/lib/templates/overview-template.js +16 -0
  75. package/dist/lib/templates/responsiveViewMainBodyTemplate.js +47 -0
  76. package/dist/lib/templates/security-scheme-template.js +342 -0
  77. package/dist/lib/templates/server-template.js +49 -0
  78. package/dist/lib/utils/color-utils.js +112 -0
  79. package/dist/lib/utils/common-utils.js +156 -0
  80. package/dist/lib/utils/schema-utils.js +743 -0
  81. package/dist/lib/utils/spec-parser.js +361 -0
  82. package/dist/lib/utils/theme.js +84 -0
  83. package/dist/lib/utils/xml/xml.js +239 -0
  84. package/package.json +19 -6
  85. package/dist/openapi-explorer.min.js.LICENSE.txt +0 -71
  86. package/dist/openapi-explorer.min.js.LICENSE.txt.gz +0 -0
  87. package/dist/openapi-explorer.min.js.gz +0 -0
  88. package/dist/openapi-explorer.min.js.map.gz +0 -0
  89. package/dist/report.html +0 -38
  90. package/src/components/api-request.js +0 -1244
  91. package/src/components/api-response.js +0 -340
  92. package/src/components/json-tree.js +0 -129
  93. package/src/components/schema-table.js +0 -250
  94. package/src/components/schema-tree.js +0 -280
  95. package/src/components/tag-input.js +0 -109
  96. package/src/styles/advanced-search-styles.js +0 -84
  97. package/src/styles/api-request-styles.js +0 -111
  98. package/src/styles/border-styles.js +0 -24
  99. package/src/styles/css/main.css +0 -24
  100. package/src/styles/endpoint-styles.js +0 -222
  101. package/src/styles/flex-styles.js +0 -15
  102. package/src/styles/font-styles.js +0 -266
  103. package/src/styles/info-styles.js +0 -20
  104. package/src/styles/input-styles.js +0 -236
  105. package/src/styles/nav-styles.js +0 -141
  106. package/src/styles/prism-styles.js +0 -107
  107. package/src/styles/schema-styles.js +0 -121
  108. package/src/styles/tab-styles.js +0 -44
  109. package/src/styles/table-styles.js +0 -48
  110. package/src/templates/advance-search-template.js +0 -81
  111. package/src/templates/callback-template.js +0 -63
  112. package/src/templates/code-samples-template.js +0 -35
  113. package/src/templates/components-template.js +0 -43
  114. package/src/templates/endpoint-template.js +0 -175
  115. package/src/templates/expanded-endpoint-template.js +0 -104
  116. package/src/templates/navbar-template.js +0 -175
  117. package/src/templates/overview-template.js +0 -58
  118. package/src/templates/responsiveViewMainBodyTemplate.js +0 -72
  119. package/src/templates/security-scheme-template.js +0 -487
  120. package/src/templates/server-template.js +0 -106
  121. package/src/utils/theme.js +0 -163
@@ -0,0 +1,330 @@
1
+ import { html } from 'lit-element';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html';
3
+ import { marked } from 'marked';
4
+ import base64url from 'base64url';
5
+
6
+ function onApiKeyChange(apiKeyId, e) {
7
+ let apiKeyValue = '';
8
+ const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
9
+
10
+ if (!securityObj) {
11
+ return;
12
+ }
13
+
14
+ const trEl = e.target.closest('tr');
15
+
16
+ if (securityObj.type && securityObj.type === 'http' && securityObj.scheme && securityObj.scheme.toLowerCase() === 'basic') {
17
+ const userVal = trEl.querySelector('.api-key-user').value.trim();
18
+ const passwordVal = trEl.querySelector('.api-key-password').value.trim();
19
+
20
+ if (passwordVal) {
21
+ apiKeyValue = `Basic ${btoa(`${userVal}:${passwordVal}`)}`;
22
+ }
23
+ } else {
24
+ apiKeyValue = trEl.querySelector('.api-key-input').value.trim();
25
+
26
+ if (apiKeyValue) {
27
+ if (securityObj.scheme && securityObj.scheme.toLowerCase() === 'bearer') {
28
+ apiKeyValue = `Bearer ${apiKeyValue.replace(/^Bearer\s+/i, '')}`;
29
+ }
30
+ }
31
+ }
32
+
33
+ securityObj.finalKeyValue = apiKeyValue;
34
+ this.requestUpdate();
35
+ }
36
+
37
+ function onClearAllApiKeys() {
38
+ this.resolvedSpec.securitySchemes.forEach(v => {
39
+ v.user = '';
40
+ v.password = '';
41
+ v.value = '';
42
+ v.finalKeyValue = '';
43
+ });
44
+ this.requestUpdate();
45
+ } // Updates the OAuth Access Token (API key), so it reflects in UI and gets used in TRY calls
46
+
47
+
48
+ function updateOAuthKey(apiKeyId, tokenType = 'Bearer', accessToken) {
49
+ const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
50
+ const tokenPrefix = tokenType && tokenType.toLowerCase() === 'bearer' ? 'Bearer' : tokenType;
51
+ securityObj.finalKeyValue = `${tokenPrefix}${tokenPrefix ? ' ' : ''}${accessToken}`;
52
+ this.requestUpdate();
53
+ } // Gets Access-Token in exchange of Authorization Code
54
+
55
+
56
+ async function fetchAccessToken(tokenUrl, clientId, clientSecret, redirectUrl, grantType, authCode, sendClientSecretIn = 'header', apiKeyId, authFlowDivEl, scopes = null) {
57
+ const respDisplayEl = authFlowDivEl ? authFlowDivEl.querySelector('.oauth-resp-display') : undefined;
58
+ const urlFormParams = new URLSearchParams();
59
+ const headers = new Headers();
60
+ urlFormParams.append('grant_type', grantType);
61
+
62
+ if (redirectUrl) {
63
+ urlFormParams.append('redirect_uri', redirectUrl);
64
+ }
65
+
66
+ if (authCode) {
67
+ urlFormParams.append('code', authCode);
68
+ }
69
+
70
+ if (sendClientSecretIn === 'header') {
71
+ headers.set('Authorization', `Basic ${btoa(`${clientId}:${clientSecret}`)}`);
72
+ } else {
73
+ urlFormParams.append('client_id', clientId);
74
+
75
+ if (clientSecret) {
76
+ urlFormParams.append('client_secret', clientSecret);
77
+ }
78
+ }
79
+
80
+ if (scopes) {
81
+ urlFormParams.append('scope', scopes);
82
+ }
83
+
84
+ const {
85
+ codeVerifier
86
+ } = JSON.parse(localStorage.getItem('openapi-explorer-oauth') || '{}');
87
+ localStorage.removeItem('openapi-explorer-oauth');
88
+
89
+ if (codeVerifier) {
90
+ urlFormParams.append('code_verifier', codeVerifier);
91
+ }
92
+
93
+ try {
94
+ const resp = await fetch(tokenUrl, {
95
+ method: 'POST',
96
+ headers,
97
+ body: urlFormParams
98
+ });
99
+ const tokenResp = await resp.json();
100
+
101
+ if (!resp.ok) {
102
+ if (respDisplayEl) {
103
+ respDisplayEl.innerHTML = `<span style="color:var(--red)">${tokenResp.error_description || tokenResp.error_description || 'Unable to get access token'}</span>`;
104
+ }
105
+
106
+ return;
107
+ }
108
+
109
+ if (tokenResp.token_type && tokenResp.access_token) {
110
+ updateOAuthKey.call(this, apiKeyId, tokenResp.token_type, tokenResp.access_token);
111
+
112
+ if (respDisplayEl) {
113
+ respDisplayEl.innerHTML = '<span style="color:var(--green)">Access Token Received</span>';
114
+ }
115
+ }
116
+ } catch (err) {
117
+ if (respDisplayEl) {
118
+ respDisplayEl.innerHTML = '<span style="color:var(--red)">Failed to get access token</span>';
119
+ }
120
+ }
121
+ }
122
+
123
+ function getCookieValue(keyId) {
124
+ const foundCookie = (document.cookie || '').split(';').find(c => c.split('=')[0] === keyId);
125
+ return foundCookie && foundCookie.split('=')[1] || '';
126
+ }
127
+
128
+ function toObject(urlSearchParams) {
129
+ const result = {};
130
+ const entries = urlSearchParams && urlSearchParams.entries() || [];
131
+
132
+ for (const [key, value] of entries) {
133
+ result[key] = value;
134
+ }
135
+
136
+ return result;
137
+ } // Gets invoked when it receives the Authorization Code from the other window via message-event
138
+
139
+
140
+ export async function checkForAuthToken(redirectToApiLocation) {
141
+ const parameters = toObject(new URLSearchParams(window.location.search));
142
+ const hashQuery = toObject(new URLSearchParams(window.location.hash.slice(1)));
143
+ Object.assign(parameters, hashQuery);
144
+ const newUrl = new URL(window.location);
145
+ newUrl.searchParams.delete('nonce');
146
+ newUrl.searchParams.delete('expires_in');
147
+ newUrl.searchParams.delete('access_token');
148
+ newUrl.searchParams.delete('token_type');
149
+ newUrl.searchParams.delete('id_token');
150
+ newUrl.searchParams.delete('state');
151
+ newUrl.searchParams.delete('code');
152
+ newUrl.searchParams.delete('iss');
153
+ newUrl.searchParams.delete('scope');
154
+ newUrl.searchParams.delete('prompt');
155
+ newUrl.searchParams.delete('hd');
156
+ newUrl.searchParams.delete('authuser');
157
+ newUrl.searchParams.delete('redirect_auth');
158
+
159
+ if (!parameters.state) {
160
+ return;
161
+ }
162
+
163
+ const sanitizedUrlWithHash = newUrl.toString().replace(/#((code|state|access_token|id_token|authuser|expires_in|hd|prompt|scope|token_type)=[^&]+&?)*$/ig, '');
164
+ history.replaceState({}, undefined, sanitizedUrlWithHash);
165
+ const {
166
+ apiKeyId,
167
+ flowId,
168
+ url
169
+ } = JSON.parse(base64url.decode(parameters.state));
170
+
171
+ if (redirectToApiLocation && url && !parameters.redirect_auth) {
172
+ const apiExplorerLocation = new URL(url);
173
+ Object.keys(parameters).forEach(key => apiExplorerLocation.searchParams.append(key, parameters[key]));
174
+ apiExplorerLocation.searchParams.append('redirect_auth', true);
175
+ window.location.replace(apiExplorerLocation.toString());
176
+ return;
177
+ }
178
+
179
+ if (parameters.code) {
180
+ const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
181
+ const tokenUrl = securityObj && securityObj.flows[flowId] && new URL(securityObj.flows[flowId].tokenUrl || '', this.selectedServer.computedUrl);
182
+ await fetchAccessToken.call(this, tokenUrl, securityObj.clientId, securityObj.clientSecret, securityObj.redirectUri || window.location.href, 'authorization_code', parameters.code, null, apiKeyId);
183
+ return;
184
+ }
185
+
186
+ updateOAuthKey.call(this, apiKeyId, parameters.token_type, parameters.access_token);
187
+ }
188
+
189
+ async function onInvokeOAuthFlow(apiKeyId, flowType, authUrl, tokenUrl, e) {
190
+ const authFlowDivEl = e.target.closest('.oauth-flow');
191
+ const clientId = authFlowDivEl.querySelector('.oauth-client-id') ? authFlowDivEl.querySelector('.oauth-client-id').value.trim() : '';
192
+ const clientSecret = authFlowDivEl.querySelector('.oauth-client-secret') ? authFlowDivEl.querySelector('.oauth-client-secret').value.trim() : '';
193
+ const sendClientSecretIn = authFlowDivEl.querySelector('.oauth-send-client-secret-in') ? authFlowDivEl.querySelector('.oauth-send-client-secret-in').value.trim() : 'header';
194
+ const checkedScopeEls = [...authFlowDivEl.querySelectorAll('input[type="checkbox"]:checked')];
195
+ const securityObj = this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
196
+ let grantType = '';
197
+ let responseType = ''; // clear previous error messages
198
+
199
+ const errEls = [...authFlowDivEl.parentNode.querySelectorAll('.oauth-resp-display')];
200
+ errEls.forEach(v => {
201
+ v.innerHTML = '';
202
+ });
203
+
204
+ if (flowType === 'authorizationCode' || flowType === 'implicit') {
205
+ const authUrlObj = new URL(authUrl);
206
+ const authCodeParams = new URLSearchParams(authUrlObj.search);
207
+
208
+ if (flowType === 'authorizationCode') {
209
+ const randomBytes = new Uint32Array(3);
210
+ (window.crypto || window.msCrypto).getRandomValues(randomBytes);
211
+ authCodeParams.set('nonce', randomBytes.toString('hex').split(',').join(''));
212
+ grantType = 'authorization_code';
213
+ responseType = 'code';
214
+ const codeVerifier = randomBytes.toString('hex').split(',').join('');
215
+ const hash = await (window.crypto || window.msCrypto).subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
216
+ const codeChallenge = base64url(hash);
217
+ authCodeParams.set('code_challenge', codeChallenge);
218
+ authCodeParams.set('code_challenge_method', 'S256');
219
+ localStorage.setItem('openapi-explorer-oauth', JSON.stringify({
220
+ codeVerifier
221
+ }));
222
+ } else if (flowType === 'implicit') {
223
+ responseType = 'token';
224
+ }
225
+
226
+ const selectedScopes = checkedScopeEls.map(v => v.value).join(' ');
227
+
228
+ if (selectedScopes) {
229
+ authCodeParams.set('scope', selectedScopes);
230
+ }
231
+
232
+ authCodeParams.set('client_id', clientId);
233
+ authCodeParams.set('redirect_uri', securityObj.redirectUri || window.location.href);
234
+ authCodeParams.set('response_type', responseType);
235
+ authCodeParams.set('state', base64url.encode(JSON.stringify({
236
+ apiKeyId,
237
+ flowId: flowType,
238
+ url: window.location.href
239
+ })));
240
+ authUrlObj.search = authCodeParams.toString();
241
+ window.location.assign(authUrlObj.toString());
242
+ } else if (flowType === 'clientCredentials') {
243
+ grantType = 'client_credentials';
244
+ const selectedScopes = checkedScopeEls.map(v => v.value).join(' ');
245
+ fetchAccessToken.call(this, tokenUrl, clientId, clientSecret, '', grantType, '', sendClientSecretIn, apiKeyId, authFlowDivEl, selectedScopes);
246
+ }
247
+ }
248
+ /* eslint-disable indent */
249
+
250
+
251
+ function oAuthFlowTemplate(flowName, securityObj, authFlow) {
252
+ const apiKeyId = securityObj.apiKeyId;
253
+
254
+ const getFullUrl = url => url ? new URL(url, this.selectedServer.computedUrl) : undefined;
255
+
256
+ const authorizationUrl = getFullUrl(authFlow.authorizationUrl, this.selectedServer.computedUrl);
257
+ const tokenUrl = getFullUrl(authFlow.tokenUrl, this.selectedServer.computedUrl);
258
+ const refreshUrl = getFullUrl(authFlow.refreshUrl, this.selectedServer.computedUrl);
259
+ let flowNameDisplay;
260
+
261
+ if (flowName === 'authorizationCode') {
262
+ flowNameDisplay = 'Authorization Code Flow';
263
+ } else if (flowName === 'clientCredentials') {
264
+ flowNameDisplay = 'Client Credentials Flow';
265
+ } else if (flowName === 'implicit') {
266
+ flowNameDisplay = 'Implicit Flow';
267
+ } else {
268
+ flowNameDisplay = flowName;
269
+ }
270
+
271
+ return html` <div class="oauth-flow" style="padding:10px 0;margin-bottom:10px"> <div class="tiny-title upper" style="margin-bottom:5px">${flowNameDisplay}</div> ${authorizationUrl ? html`<div><span style="width:75px;display:inline-block">Auth URL</span> <span class="mono-font"> ${authorizationUrl} </span></div>` : ''} ${tokenUrl ? html`<div><span style="width:75px;display:inline-block">Token URL</span> <span class="mono-font">${tokenUrl}</span></div>` : ''} ${refreshUrl ? html`<div><span style="width:75px;display:inline-block">Refresh URL</span> <span class="mono-font">${refreshUrl}</span></div>` : ''} ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'implicit' ? html` ${authFlow.scopes ? html` <span> Scopes </span> <div class="oauth-scopes" part="section-auth-scopes" style="width:100%;display:flex;flex-direction:column;flex-wrap:wrap;margin:0 0 .125rem 0"> ${Object.entries(authFlow.scopes).map((scopeAndDescr, index) => html` <div class="m-checkbox" style="display:inline-flex;align-items:center"> <input type="checkbox" checked="checked" part="checkbox checkbox-auth-scope" id="${flowName}${index}" value="${scopeAndDescr[0]}"> <label for="${flowName}${index}" style="margin-left:5px"> <span class="mono-font">${scopeAndDescr[0]}</span> ${scopeAndDescr[0] !== scopeAndDescr[1] ? ` - ${scopeAndDescr[1] || ''}` : ''} </label> </div> `)} </div> ` : ''} <div style="display:flex"> <input type="text" part="textbox textbox-auth-client-id" value="${securityObj.clientId || ''}" placeholder="Client ID" spellcheck="false" class="oauth-client-id"> ${flowName === 'clientCredentials' ? html` <input type="password" part="textbox textbox-auth-client-secret" value="" placeholder="Client Secret" spellcheck="false" class="oauth-client-secret" style="margin:0 5px"> <select aria-label="oauth client secret location" style="margin-right:5px" class="oauth-send-client-secret-in"> <option value="header" selected="selected"> Authorization Header </option> <option value="request-body"> Request Body </option> </select>` : html`<div style="width:5px"></div>`} ${flowName === 'authorizationCode' || flowName === 'clientCredentials' || flowName === 'implicit' ? html` <button class="m-btn thin-border" part="btn btn-outline" @click="${e => {
272
+ onInvokeOAuthFlow.call(this, apiKeyId, flowName, authorizationUrl, tokenUrl, e);
273
+ }}">GET TOKEN</button>` : ''} </div> <div class="oauth-resp-display red-text small-font-size"></div> ` : ''} </div> `;
274
+ }
275
+
276
+ export default function securitySchemeTemplate() {
277
+ const schemes = this.resolvedSpec && this.resolvedSpec.securitySchemes;
278
+
279
+ if (!schemes) {
280
+ return undefined;
281
+ }
282
+
283
+ const providedApiKeys = schemes.filter(v => v.finalKeyValue);
284
+ return html` <section id="auth" part="section-auth" class="observe-me ${this.renderStyle === 'focused' ? 'section-gap--focused-mode' : 'section-gap'}"> <slot name="authentication"> <div class="section-padding"> <div class="sub-title regular-font">AUTHENTICATION</div> <div class="small-font-size" style="display:flex;align-items:center;min-height:30px"> ${providedApiKeys.length > 0 ? html` <div class="blue-text"> ${providedApiKeys.length} API key applied </div> <div style="flex:1"></div> <button class="m-btn thin-border" part="btn btn-outline" @click="${() => {
285
+ onClearAllApiKeys.call(this);
286
+ }}">CLEAR ALL API KEYS</button>` : html`<div class="red-text">No API key applied</div>`} </div> ${schemes.length > 0 ? html` <table role="presentation" class="m-table" style="width:100%"> ${schemes.map(v => html` <tr> <td style="max-width:500px;overflow-wrap:break-word"> <div style="min-height:24px"> <span style="font-weight:700">${v.typeDisplay}</span> ${v.finalKeyValue ? html` <span class="blue-text"> ${v.finalKeyValue ? 'Key Applied' : ''} </span> <button class="m-btn thin-border small" part="btn btn-outline" @click="${() => {
287
+ v.finalKeyValue = '';
288
+ this.requestUpdate();
289
+ }}">REMOVE</button> ` : ''} </div> ${v.description ? html` <div class="m-markdown"> ${unsafeHTML(marked(v.description || ''))} </div>` : ''} </td> <td> ${v.type && (v.type.toLowerCase() === 'apikey' || v.type.toLowerCase() === 'http' && v.scheme && v.scheme.toLowerCase() === 'bearer') ? html` ${v.type.toLowerCase() === 'apikey' ? html`Send <code>${v.name}</code> in <code>${v.in}</code> with the given value:` : html`Send <code>Authorization</code> in <code>header</code> containing the word <code>Bearer</code> followed by a space and a Token String.`} <div style="display:flex"> ${v.in === 'cookie' ? html` <div style="display:block"> <input type="text" value="${getCookieValue(v.apiKeyId)}" disabled="disabled" class="api-key-input" placeholder="IygRVGf54B59e0GAkKmigGfuiVlp/uhFfk2ifA+jMMJzau2F1jPldc09gPTfnMw13BFBxqUZIFDm55DPfwkb0A==" spellcheck="false" style="resize:horizontal;width:100%"> <br> <small> <strong>Cookies</strong>&nbsp;are set and configured by the remote service, therefore it is not possible to configure them from the browser. </small> </div>` : html` <input type="text" value="${v.value}" class="api-key-input" placeholder="api-token" spellcheck="false"> <button class="m-btn thin-border" style="margin-left:5px" part="btn btn-outline" @click="${e => {
290
+ onApiKeyChange.call(this, v.apiKeyId, e);
291
+ }}"> ${v.finalKeyValue ? 'UPDATE' : 'SET'} </button>`} </div>` : ''} ${v.type && v.type.toLowerCase() === 'http' && v.scheme && v.scheme.toLowerCase() === 'basic' ? html` Send the <code>Authorization</code> header containing the type <code>Basic</code> followed by a space and a base64 encoded string of <code>username:password</code>. <div style="display:flex"> <input type="text" value="${v.user}" placeholder="username" spellcheck="false" class="api-key-user" style="width:100px"> <input type="password" value="${v.password}" placeholder="password" spellcheck="false" class="api-key-password" style="width:100px;margin:0 5px"> <button class="m-btn thin-border" @click="${e => {
292
+ onApiKeyChange.call(this, v.apiKeyId, e);
293
+ }}" part="btn btn-outline"> ${v.finalKeyValue ? 'UPDATE' : 'SET'} </button> </div>` : ''} </td> </tr> ${v.type.toLowerCase() === 'oauth2' ? html` <tr> <td colspan="2" style="border:none;padding-left:48px"> ${Object.keys(v.flows).map(f => oAuthFlowTemplate.call(this, f, v, v.flows[f]))} </td> </tr> ` : ''} `)} </table>` : ''} </div> </slot> </section> `;
294
+ }
295
+
296
+ function getOauthScopeTemplate(scopes) {
297
+ if (!scopes || !scopes.length || !Array.isArray(scopes)) {
298
+ return '';
299
+ }
300
+
301
+ return html` <div> <b>Required scopes:</b> <br> <div style="margin-left:8px"> ${scopes.map(scope => html`<span>${scope}</span>&nbsp;`)} </div> </div>`;
302
+ }
303
+
304
+ export function pathSecurityTemplate(pathSecurity) {
305
+ if (this.resolvedSpec.securitySchemes && pathSecurity) {
306
+ const orSecurityKeys1 = [];
307
+ pathSecurity.forEach(pSecurity => {
308
+ const andSecurityKeys1 = [];
309
+ const andKeyTypes = [];
310
+ Object.keys(pSecurity).forEach(pathSecurityKey => {
311
+ const s = this.resolvedSpec.securitySchemes.find(ss => ss.apiKeyId === pathSecurityKey);
312
+
313
+ if (s) {
314
+ andKeyTypes.push(s.typeDisplay);
315
+ andSecurityKeys1.push({ ...s,
316
+ scopes: pSecurity[pathSecurityKey]
317
+ });
318
+ }
319
+ });
320
+ orSecurityKeys1.push({
321
+ securityTypes: andKeyTypes.length > 1 ? `${andKeyTypes[0]} + ${andKeyTypes.length - 1} more` : andKeyTypes[0],
322
+ securityDefs: andSecurityKeys1
323
+ });
324
+ });
325
+ return html`<div class="security-info-button" data-content-id="auth" @click="${e => this.scrollToEventTarget(e, false)}"> <div style="position:relative;display:flex;min-width:350px;max-width:700px;justify-content:flex-end"> <svg width="16" height="24" style="cursor:pointer"> <g> <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"/> </g> </svg> ${orSecurityKeys1.map((orSecurityItem1, i) => html` ${i !== 0 ? html`<div style="padding:3px 4px"> OR </div>` : ''} <div class="tooltip" style="cursor:pointer"> <div style="padding:2px 4px;white-space:nowrap;text-overflow:ellipsis;max-width:150px;overflow:hidden"> <span part="anchor anchor-operation-security"> ${orSecurityItem1.securityTypes} </span> </div> <div class="tooltip-text" style="position:absolute;color:var(--fg);top:26px;right:0;border:1px solid var(--border-color);padding:2px 4px;display:block"> ${orSecurityItem1.securityDefs.length > 1 ? html`<div>Requires <b>all</b> of the following </div>` : ''} <div style="padding-left:8px"> ${orSecurityItem1.securityDefs.map((andSecurityItem, j) => html` ${andSecurityItem.type === 'oauth2' ? html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`} OAuth token (${andSecurityItem.apiKeyId}) in <b>Authorization header</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>` : andSecurityItem.type === 'http' ? html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`} ${andSecurityItem.scheme === 'basic' ? 'Base 64 encoded username:password' : 'Bearer Token'} in <b>Authorization header</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>` : html` <div> ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`} Token in <b>${andSecurityItem.name} ${andSecurityItem.in}</b> ${getOauthScopeTemplate(andSecurityItem.scopes)} </div>`} `)} </div> </div> </div> `)} </div> </div>`;
326
+ }
327
+
328
+ return '';
329
+ }
330
+ /* eslint-enable indent */
@@ -0,0 +1,42 @@
1
+ import { html } from 'lit-element';
2
+ import { marked } from 'marked';
3
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html';
4
+
5
+ function onApiServerChange(e, server) {
6
+ if (e && e.target.checked) {
7
+ this.selectedServer = server;
8
+ this.requestUpdate();
9
+ }
10
+ }
11
+
12
+ function onApiServerVarChange(e, serverObj) {
13
+ const inputEls = [...e.currentTarget.closest('table').querySelectorAll('input, select')];
14
+ let tempUrl = serverObj.url;
15
+ inputEls.forEach(v => {
16
+ const regex = new RegExp(`{${v.dataset.var}}`, 'g');
17
+ tempUrl = tempUrl.replace(regex, v.value);
18
+ });
19
+ serverObj.computedUrl = tempUrl;
20
+ this.requestUpdate();
21
+ }
22
+ /* eslint-disable indent */
23
+
24
+
25
+ function serverVarsTemplate() {
26
+ return this.selectedServer && this.selectedServer.variables ? html` <div class="table-title"> SERVER VARIABLES</div> <table role="presentation" class="m-table"> ${Object.entries(this.selectedServer.variables).map(kv => html` <tr> <td style="vertical-align:middle">${kv[0]}</td> <td> ${kv[1].enum ? html` <select data-var="${kv[0]}" @input="${e => {
27
+ onApiServerVarChange.call(this, e, this.selectedServer);
28
+ }}"> ${Object.entries(kv[1].enum).map(e => kv[1].default === e[1] ? html` <option selected="selected" label="${e[1]}" value="${e[1]}">` : html` <option label="${e[1]}" value="${e[1]}">`)} </select>` : html` <input type="text" part="textbox textbox-server-var" spellcheck="false" data-var="${kv[0]}" value="${kv[1].default}" @input="${e => {
29
+ onApiServerVarChange.call(this, e, this.selectedServer);
30
+ }}">`} </td> </tr> ${kv[1].description ? html`<tr><td colspan="2" style="border:none"><span class="m-markdown-small"> ${unsafeHTML(marked(kv[1].description))} </span></td></tr>` : ''} `)} </table> ` : '';
31
+ }
32
+
33
+ export default function serverTemplate() {
34
+ if (!this.resolvedSpec) {
35
+ return undefined;
36
+ }
37
+
38
+ return html` <section id="servers" part="section-servers" style="margin-top:24px;margin-bottom:24px" class="regular-font observe-me section-padding ${this.renderStyle === 'read' ? 'section-gap--read-mode' : this.renderStyle === 'focused' ? 'section-gap--focused-mode' : 'section-gap'}"> <div class="sub-title">API SERVER</div> <div class="mono-font" style="margin:12px 0;font-size:calc(var(--font-size-small) + 1px)"> ${!this.resolvedSpec.servers || !this.resolvedSpec.servers.length ? '' : html` ${this.resolvedSpec.servers.map((server, i) => html` <input type="radio" name="api_server" id="srvr-opt-${i}" value="${server.url}" @change="${e => {
39
+ onApiServerChange.call(this, e, server);
40
+ }}" .checked="${this.selectedServer.url === server.url}" style="margin:4px 0;cursor:pointer"> <label style="cursor:pointer" for="srvr-opt-${i}"> ${server.url} ${server.description ? html`- <span class="regular-font">${server.description} </span>` : ''} </label> <br> `)} `} <div class="table-title primary-text" part="label-selected-server"> SELECTED: ${this.selectedServer && this.selectedServer.computedUrl || 'none'}</div> </div> <slot name="servers"></slot> ${serverVarsTemplate.call(this)} </section>`;
41
+ }
42
+ /* eslint-enable indent */
@@ -1,69 +1,106 @@
1
1
  /* eslint-disable no-mixed-operators */
2
+
2
3
  /* eslint-disable no-bitwise */
3
4
  export default {
4
5
  color: {
5
6
  inputReverseFg: '#fff',
6
7
  inputReverseBg: '#333',
7
8
  headerBg: '#444',
9
+
8
10
  getRgb(hexStr) {
9
11
  let hex = (hexStr || '').trim();
12
+
10
13
  if (hex.indexOf('#') === 0) {
11
14
  hex = hex.slice(1, 7);
12
- }
13
- // convert 3-digit hex to 6-digits.
15
+ } // convert 3-digit hex to 6-digits.
16
+
17
+
14
18
  if (hex.length === 3 || hex.length === 4) {
15
19
  hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
16
20
  }
21
+
17
22
  if (hex.length !== 6) {
18
23
  // eslint-disable-next-line no-console
19
24
  console.error(`Invalid HEX color: '${hexStr}'`);
20
- return { r: 0, g: 0, b: 0 };
25
+ return {
26
+ r: 0,
27
+ g: 0,
28
+ b: 0
29
+ };
21
30
  }
31
+
22
32
  return {
23
33
  r: parseInt(hex.slice(0, 2), 16),
24
34
  g: parseInt(hex.slice(2, 4), 16),
25
- b: parseInt(hex.slice(4, 6), 16),
35
+ b: parseInt(hex.slice(4, 6), 16)
26
36
  };
27
37
  },
38
+
28
39
  luminanace(hexColorCode) {
29
40
  const rgb = this.getRgb(hexColorCode);
30
- return (rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114);
41
+ return rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
31
42
  },
43
+
32
44
  invert(hexColorCode) {
33
45
  // compare with `>=128`, but giving little more preference to white over black
34
46
  return this.luminanace(hexColorCode) > 149 ? '#000000' : '#ffffff';
35
47
  },
48
+
36
49
  // https://stackoverflow.com/a/41491220/5091874
37
50
  selectTextColorFromBackground(bcHexColor) {
38
- const { r, g, b } = this.getRgb(bcHexColor);
51
+ const {
52
+ r,
53
+ g,
54
+ b
55
+ } = this.getRgb(bcHexColor);
39
56
  const colors = [r / 255, g / 255, b / 255];
40
- const c = colors.map((col) => {
57
+ const c = colors.map(col => {
41
58
  if (col <= 0.03928) {
42
59
  return col / 12.92;
43
60
  }
61
+
44
62
  return ((col + 0.055) / 1.055) ** 2.4;
45
63
  });
46
- const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
47
- return (L > 0.179) ? '#000000' : '#FFFFFF';
64
+ const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
65
+ return L > 0.179 ? '#000000' : '#FFFFFF';
48
66
  },
67
+
49
68
  opacity(hex, opacity) {
50
69
  const rgb = this.getRgb(hex);
51
70
  return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
52
71
  },
72
+
53
73
  brightness(hex, amt) {
54
74
  const rgb = this.getRgb(hex);
55
75
  rgb.r += amt;
56
76
  rgb.g += amt;
57
77
  rgb.b += amt;
58
- if (rgb.r > 255) {rgb.r = 255;} else if (rgb.r < 0) {rgb.r = 0;}
59
78
 
60
- if (rgb.g > 255) {rgb.g = 255;} else if (rgb.g < 0) {rgb.g = 0;}
79
+ if (rgb.r > 255) {
80
+ rgb.r = 255;
81
+ } else if (rgb.r < 0) {
82
+ rgb.r = 0;
83
+ }
84
+
85
+ if (rgb.g > 255) {
86
+ rgb.g = 255;
87
+ } else if (rgb.g < 0) {
88
+ rgb.g = 0;
89
+ }
90
+
91
+ if (rgb.b > 255) {
92
+ rgb.b = 255;
93
+ } else if (rgb.b < 0) {
94
+ rgb.b = 0;
95
+ }
61
96
 
62
- if (rgb.b > 255) {rgb.b = 255;} else if (rgb.b < 0) {rgb.b = 0;}
63
97
  return `#${rgb.r.toString(16).padStart(2, '0')}${rgb.g.toString(16).padStart(2, '0')}${rgb.b.toString(16).padStart(2, '0')}`;
64
- },
98
+ }
99
+
65
100
  },
101
+
66
102
  isValidHexColor(colorCode) {
67
- return (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/i).test(colorCode);
68
- },
69
- };
103
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/i.test(colorCode);
104
+ }
105
+
106
+ };
@@ -9,21 +9,21 @@ export function debounce(fn, delay) {
9
9
  }, delay);
10
10
  };
11
11
  }
12
-
13
12
  export const invalidCharsRegEx = new RegExp(/[\s#:?&={}]/, 'g'); // used for generating valid html element ids by replacing the invalid chars with hyphen (-)
14
13
 
15
14
  export function sleep(ms) {
16
- return new Promise((resolve) => setTimeout(resolve, ms));
15
+ return new Promise(resolve => setTimeout(resolve, ms));
17
16
  }
18
-
19
17
  export function copyToClipboard(data, e) {
20
18
  const btnEl = e.currentTarget;
21
19
  const textArea = document.createElement('textarea');
22
20
  textArea.value = data;
23
21
  textArea.style.position = 'fixed'; // avoid scrolling to bottom
22
+
24
23
  document.body.appendChild(textArea);
25
24
  textArea.focus();
26
25
  textArea.select();
26
+
27
27
  try {
28
28
  document.execCommand('copy');
29
29
  btnEl.innerText = 'Copied';
@@ -33,29 +33,28 @@ export function copyToClipboard(data, e) {
33
33
  } catch (err) {
34
34
  console.error('Unable to copy', err); // eslint-disable-line no-console
35
35
  }
36
+
36
37
  document.body.removeChild(textArea);
37
38
  }
38
-
39
39
  export function getBaseUrlFromUrl(url) {
40
40
  const pathArray = url.split('/');
41
41
  return `${pathArray[0]}//${pathArray[2]}`;
42
42
  }
43
-
44
43
  export function componentIsInSearch(searchVal, component) {
45
44
  return component.name.toLowerCase().includes(searchVal.toLowerCase());
46
45
  }
47
-
48
46
  export function pathIsInSearch(searchVal, path) {
49
47
  const stringToSearch = `${path.method} ${path.path} ${path.summary || path.description || ''} ${path.operationId || ''}`.toLowerCase();
50
48
  return stringToSearch.includes(searchVal.toLowerCase());
51
49
  }
52
-
53
50
  export function schemaKeys(schemaProps, result = new Set()) {
54
51
  if (!schemaProps) {
55
52
  return result;
56
53
  }
57
- Object.keys(schemaProps).forEach((key) => {
54
+
55
+ Object.keys(schemaProps).forEach(key => {
58
56
  result.add(key);
57
+
59
58
  if (schemaProps[key].properties) {
60
59
  schemaKeys(schemaProps[key].properties, result);
61
60
  } else if (schemaProps[key].items && schemaProps[key].items.properties) {
@@ -64,38 +63,42 @@ export function schemaKeys(schemaProps, result = new Set()) {
64
63
  });
65
64
  return result;
66
65
  }
67
-
68
66
  export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
69
67
  if (!searchVal.trim() || searchOptions.length === 0) {
70
68
  return undefined;
71
69
  }
72
70
 
73
71
  const pathsMatched = [];
74
- allSpecTags.forEach((tag) => {
75
- tag.paths.forEach((path) => {
72
+ allSpecTags.forEach(tag => {
73
+ tag.paths.forEach(path => {
76
74
  let stringToSearch = '';
75
+
77
76
  if (searchOptions.includes('search-api-path')) {
78
77
  stringToSearch = path.path;
79
78
  }
79
+
80
80
  if (searchOptions.includes('search-api-descr')) {
81
81
  stringToSearch = `${stringToSearch} ${path.summary || path.description || ''}`;
82
82
  }
83
+
83
84
  if (searchOptions.includes('search-api-params')) {
84
- stringToSearch = `${stringToSearch} ${path.parameters && path.parameters.map((v) => v.name).join(' ') || ''}`;
85
+ stringToSearch = `${stringToSearch} ${path.parameters && path.parameters.map(v => v.name).join(' ') || ''}`;
85
86
  }
86
87
 
87
88
  if (searchOptions.includes('search-api-request-body') && path.requestBody) {
88
89
  let schemaKeySet = new Set();
90
+
89
91
  for (const contentType in path.requestBody && path.requestBody.content) {
90
92
  if (path.requestBody.content[contentType].schema && path.requestBody.content[contentType].schema.properties) {
91
93
  schemaKeySet = schemaKeys(path.requestBody.content[contentType].schema.properties);
92
94
  }
95
+
93
96
  stringToSearch = `${stringToSearch} ${[...schemaKeySet].join(' ')}`;
94
97
  }
95
98
  }
96
99
 
97
100
  if (searchOptions.includes('search-api-resp-descr')) {
98
- stringToSearch = `${stringToSearch} ${Object.values(path.responses).map((v) => v.description || '').join(' ')}`;
101
+ stringToSearch = `${stringToSearch} ${Object.values(path.responses).map(v => v.description || '').join(' ')}`;
99
102
  }
100
103
 
101
104
  if (stringToSearch.toLowerCase().includes(searchVal.trim().toLowerCase())) {
@@ -104,27 +107,24 @@ export function advancedSearch(searchVal, allSpecTags, searchOptions = []) {
104
107
  method: path.method,
105
108
  path: path.path,
106
109
  summary: path.summary || path.description || '',
107
- deprecated: path.deprecated,
110
+ deprecated: path.deprecated
108
111
  });
109
112
  }
110
113
  });
111
114
  });
112
115
  return pathsMatched;
113
116
  }
114
-
115
117
  export function getCurrentElement() {
116
118
  const currentQuery = (window.location.hash || '').split('?')[1];
117
119
  const query = new URLSearchParams(currentQuery);
118
120
  return decodeURIComponent(query.get('route') || '');
119
121
  }
120
-
121
122
  export function replaceState(rawElementId) {
122
123
  const elementId = rawElementId && rawElementId.replace(/^#/, '') || '';
123
-
124
124
  const currentNavigationHashPart = (window.location.hash || '').split('?')[0].replace(/^#/, '');
125
125
  const currentQuery = (window.location.hash || '').split('?')[1];
126
126
  const query = new URLSearchParams(currentQuery);
127
127
  query.delete('route');
128
128
  const newQuery = query.toString().length > 1 ? `${query.toString()}&route=${elementId}` : `route=${elementId}`;
129
129
  window.history.replaceState(null, null, `#${currentNavigationHashPart}?${newQuery}`);
130
- }
130
+ }