Skip to content

Commit 3011fee

Browse files
authored
sandbox=allow-forms for Chrome, new APIClient class
In Chrome the form wouldn't submit because no allow-forms so I added that Got Claude to write a neat APIClient class using promises so I can do const response = await api.callAPI(url); https://gist.github.com/simonw/54d91de17fdde1bce715f6a7859c935e
1 parent 52c094f commit 3011fee

File tree

1 file changed

+79
-31
lines changed

1 file changed

+79
-31
lines changed

iframe-api-explorer.html

+79-31
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
<!DOCTYPE html>
23
<html>
34
<head>
@@ -19,7 +20,7 @@
1920
</style>
2021
</head>
2122
<body>
22-
<iframe id="explorerFrame" sandbox="allow-scripts"></iframe>
23+
<iframe id="explorerFrame" sandbox="allow-scripts allow-forms"></iframe>
2324

2425
<script>
2526
// Create the HTML content for the iframe
@@ -81,16 +82,76 @@ <h1>API Explorer</h1>
8182
<form onsubmit="makeRequest(); return false;">
8283
<div class="input-group">
8384
<input type="text" id="urlInput" placeholder="Enter API URL" value="https://datasette.io/content/stats.json?_size=1">
84-
<button onclick="makeRequest()">Submit</button>
85+
<button type="submit">Submit</button>
8586
</div>
8687
</form>
8788
<pre id="output">// Response will appear here</pre>
8889
</div>
8990
9091
<script>
92+
class APIClient {
93+
constructor(targetOrigin = '*') {
94+
this.targetOrigin = targetOrigin;
95+
this.pendingRequests = new Map();
96+
this.requestId = 0;
97+
98+
// Set up message listener
99+
window.addEventListener('message', this.handleMessage.bind(this));
100+
}
101+
102+
handleMessage(event) {
103+
if (event.data?.type !== 'api-response' || !event.data?.requestId) {
104+
return;
105+
}
106+
107+
const { requestId, error, response } = event.data;
108+
const pendingRequest = this.pendingRequests.get(requestId);
109+
110+
if (!pendingRequest) {
111+
return;
112+
}
113+
114+
this.pendingRequests.delete(requestId);
115+
116+
if (error) {
117+
pendingRequest.reject(new Error(error));
118+
} else {
119+
pendingRequest.resolve(response);
120+
}
121+
}
122+
123+
async callAPI(url) {
124+
const requestId = ++this.requestId;
125+
126+
const promise = new Promise((resolve, reject) => {
127+
this.pendingRequests.set(requestId, { resolve, reject });
128+
129+
// Set a timeout to prevent hanging promises
130+
setTimeout(() => {
131+
if (this.pendingRequests.has(requestId)) {
132+
this.pendingRequests.delete(requestId);
133+
reject(new Error('Request timed out'));
134+
}
135+
}, 30000); // 30 second timeout
136+
});
137+
138+
// Send the request to parent
139+
window.parent.postMessage({
140+
type: 'api-request',
141+
requestId,
142+
url,
143+
method: 'GET'
144+
}, this.targetOrigin);
145+
146+
return promise;
147+
}
148+
}
149+
150+
const api = new APIClient();
151+
152+
// Height management
91153
let currentHeight = null;
92154
function updateParentHeight() {
93-
// Borrowed from https://github.com/FaiblUG/setIframeHeight/blob/5509feb0f281251d03065991b1c5f451f40fa8bd/dist/set-iframe-height-child.js#L31C1-L31C220
94155
let newHeight = Math.min(
95156
Math.max(
96157
document.body.offsetHeight, document.documentElement.offsetHeight
@@ -103,7 +164,7 @@ <h1>API Explorer</h1>
103164
document.body.clientHeight, document.documentElement.clientHeight
104165
)
105166
)
106-
);
167+
) + 10;
107168
if (currentHeight === newHeight) {
108169
return;
109170
}
@@ -115,41 +176,26 @@ <h1>API Explorer</h1>
115176
}
116177
117178
updateParentHeight();
118-
119179
setInterval(updateParentHeight, 1000);
120180
121-
function makeRequest() {
181+
async function makeRequest() {
122182
const url = document.getElementById('urlInput').value;
123183
const output = document.getElementById('output');
124184
125-
// Send message to parent window
126-
window.parent.postMessage({
127-
type: 'api-request',
128-
url: url,
129-
method: 'GET'
130-
}, '*');
131-
132185
output.textContent = 'Loading...';
133-
}
134-
135-
// Listen for response from parent
136-
window.addEventListener('message', function(event) {
137-
const output = document.getElementById('output');
138186
139-
if (event.data.type === 'api-response') {
140-
if (event.data.error) {
141-
output.textContent = 'Error: ' + event.data.error;
142-
output.classList.add('error');
143-
} else {
144-
output.textContent = JSON.stringify(event.data.response, null, 2);
145-
output.classList.remove('error');
146-
}
147-
// Content has changed, update height
148-
setTimeout(() => {
149-
updateParentHeight();
150-
}, 100);
187+
try {
188+
const response = await api.callAPI(url);
189+
output.textContent = JSON.stringify(response, null, 2);
190+
output.classList.remove('error');
191+
} catch (error) {
192+
output.textContent = 'Error: ' + error.message;
193+
output.classList.add('error');
151194
}
152-
});
195+
196+
// Content has changed, update height
197+
setTimeout(updateParentHeight, 100);
198+
}
153199
SCUB
154200
</body>
155201
</html>
@@ -172,12 +218,14 @@ <h1>API Explorer</h1>
172218
// Send response back to iframe
173219
iframe.contentWindow.postMessage({
174220
type: 'api-response',
221+
requestId: event.data.requestId,
175222
response: data
176223
}, '*');
177224
} catch (error) {
178225
// Send error back to iframe
179226
iframe.contentWindow.postMessage({
180227
type: 'api-response',
228+
requestId: event.data.requestId,
181229
error: error.message
182230
}, '*');
183231
}

0 commit comments

Comments
 (0)