Automatically redirect to referring page 13c684c0
Also fixes a bug I just noticed in the Subscribe help page.
Heath Stewart · 2026-02-26 20:31 2 file(s) · +70 −9
docs/docs/pages/subscribe.mdx +1 −1
147 147
148 148
The component dispatches custom events you can listen to:
149 149
150 -
| Event | Description | `detail` |
150 +
| Event | Description | Detail |
151 151
|-------|-------------|----------|
152 152
| `sequoia-subscribed` | Fired when the subscription is created successfully. | `{ publicationUri: string, recordUri: string }` |
153 153
| `sequoia-subscribe-error` | Fired when the subscription fails. | `{ message: string }` |
docs/src/routes/subscribe.ts +69 −8
35 35
const subscribe = new Hono<{ Bindings: Env }>();
36 36
37 37
const COLLECTION = "site.standard.graph.subscription";
38 +
const REDIRECT_DELAY_SECONDS = 5;
38 39
39 40
// ============================================================================
40 41
// Helpers
162 163
		);
163 164
	}
164 165
166 +
	// Prefer an explicit returnTo query param (survives the OAuth round-trip);
167 +
	// fall back to the Referer header on the first visit, ignoring self-referrals.
168 +
	const referer = c.req.header("referer");
169 +
	const returnTo =
170 +
		c.req.query("returnTo") ??
171 +
		(referer && !referer.includes("/subscribe") ? referer : undefined);
172 +
165 173
	const did = getSessionDid(c);
166 174
	if (!did) {
167 -
		return c.html(renderHandleForm(publicationUri, styleHref));
175 +
		return c.html(renderHandleForm(publicationUri, styleHref, returnTo));
168 176
	}
169 177
170 178
	try {
179 187
		);
180 188
		if (existingUri) {
181 189
			return c.html(
182 -
				renderSuccess(publicationUri, existingUri, true, styleHref),
190 +
				renderSuccess(publicationUri, existingUri, true, styleHref, returnTo),
183 191
			);
184 192
		}
185 193
193 201
		});
194 202
195 203
		return c.html(
196 -
			renderSuccess(publicationUri, result.data.uri, false, styleHref),
204 +
			renderSuccess(
205 +
				publicationUri,
206 +
				result.data.uri,
207 +
				false,
208 +
				styleHref,
209 +
				returnTo,
210 +
			),
197 211
		);
198 212
	} catch (error) {
199 213
		console.error("Subscribe GET error:", error);
202 216
			renderHandleForm(
203 217
				publicationUri,
204 218
				styleHref,
219 +
				returnTo,
205 220
				"Session expired. Please sign in again.",
206 221
			),
207 222
		);
219 234
	const body = await c.req.parseBody();
220 235
	const handle = (body["handle"] as string | undefined)?.trim();
221 236
	const publicationUri = body["publicationUri"] as string | undefined;
237 +
	const formReturnTo = (body["returnTo"] as string | undefined) || undefined;
222 238
223 239
	if (!handle || !publicationUri) {
224 240
		const styleHref = await getVocsStyleHref(c.env.ASSETS, c.req.url);
228 244
		);
229 245
	}
230 246
231 -
	const returnTo = `${c.env.CLIENT_URL}/subscribe?publicationUri=${encodeURIComponent(publicationUri)}`;
247 +
	const returnTo =
248 +
		`${c.env.CLIENT_URL}/subscribe?publicationUri=${encodeURIComponent(publicationUri)}` +
249 +
		(formReturnTo ? `&returnTo=${encodeURIComponent(formReturnTo)}` : "");
232 250
	setReturnToCookie(c, returnTo, c.env.CLIENT_URL);
233 251
234 252
	return c.redirect(
243 261
function renderHandleForm(
244 262
	publicationUri: string,
245 263
	styleHref: string,
264 +
	returnTo?: string,
246 265
	error?: string,
247 266
): string {
248 267
	const errorHtml = error
249 268
		? `<p class="vocs_Paragraph error">${escapeHtml(error)}</p>`
269 +
		: "";
270 +
	const returnToInput = returnTo
271 +
		? `<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />`
250 272
		: "";
251 273
252 274
	return page(
255 277
		<p class="vocs_Paragraph">Enter your Bluesky handle to subscribe to this publication.</p>
256 278
		${errorHtml}
257 279
		<form method="POST" action="/subscribe/login">
258 -
		<input type="hidden" name="publicationUri" value="${escapeHtml(publicationUri)}" />
280 +
			<input type="hidden" name="publicationUri" value="${escapeHtml(publicationUri)}" />
281 +
			${returnToInput}
259 282
			<input
260 283
				type="text"
261 284
				name="handle"
276 299
	recordUri: string,
277 300
	existing: boolean,
278 301
	styleHref: string,
302 +
	returnTo?: string,
279 303
): string {
280 304
	const msg = existing
281 305
		? "You're already subscribed to this publication."
282 306
		: "You've successfully subscribed!";
283 307
	const escapedPublicationUri = escapeHtml(publicationUri);
284 308
	const escapedRecordUri = escapeHtml(recordUri);
309 +
310 +
	const redirectHtml = returnTo
311 +
		? `<p class="vocs_Paragraph" id="redirect-msg">Redirecting to <a class="vocs_Anchor" href="${escapeHtml(returnTo)}">${escapeHtml(returnTo)}</a> in <span id="countdown">${REDIRECT_DELAY_SECONDS}</span>\u00a0seconds\u2026</p>
312 +
		<script>
313 +
		(function(){
314 +
			var secs = ${REDIRECT_DELAY_SECONDS};
315 +
			var el = document.getElementById('countdown');
316 +
			var iv = setInterval(function(){
317 +
				secs--;
318 +
				if (el) el.textContent = String(secs);
319 +
				if (secs <= 0) { clearInterval(iv); location.href = ${JSON.stringify(returnTo)}; }
320 +
			}, 1000);
321 +
		})();
322 +
		</script>`
323 +
		: "";
324 +
	const headExtra = returnTo
325 +
		? `<meta http-equiv="refresh" content="${REDIRECT_DELAY_SECONDS};url=${escapeHtml(returnTo)}" />`
326 +
		: "";
327 +
285 328
	return page(
286 329
		`
287 330
		<h1 class="vocs_H1 vocs_Heading">Subscribed ✓</h1>
288 331
		<p class="vocs_Paragraph">${msg}</p>
289 -
		<p class="vocs_Paragraph"><small>Publication: <code class="vocs_Code"><a href="https://pds.ls/${escapedPublicationUri}">${escapedPublicationUri}</a></code></small></p>
290 -
		<p class="vocs_Paragraph"><small>Record: <code class="vocs_Code"><a href="https://pds.ls/${escapedRecordUri}">${escapedRecordUri}</a></code></small></p>
332 +
		${redirectHtml}
333 +
		<table class="vocs_Table" style="display:table;table-layout:fixed;width:100%;overflow:hidden;">
334 +
			<colgroup><col style="width:7rem;"><col></colgroup>
335 +
			<tbody>
336 +
				<tr class="vocs_TableRow">
337 +
					<td class="vocs_TableCell">Publication</td>
338 +
					<td class="vocs_TableCell" style="overflow:hidden;">
339 +
						<div style="overflow-x:auto;white-space:nowrap;"><code class="vocs_Code"><a href="https://pds.ls/${escapedPublicationUri}">${escapedPublicationUri}</a></code></div>
340 +
					</td>
341 +
				</tr>
342 +
				<tr class="vocs_TableRow">
343 +
					<td class="vocs_TableCell">Record</td>
344 +
					<td class="vocs_TableCell" style="overflow:hidden;">
345 +
						<div style="overflow-x:auto;white-space:nowrap;"><code class="vocs_Code"><a href="https://pds.ls/${escapedRecordUri}">${escapedRecordUri}</a></code></div>
346 +
					</td>
347 +
				</tr>
348 +
			</tbody>
349 +
		</table>
291 350
	`,
292 351
		styleHref,
352 +
		headExtra,
293 353
	);
294 354
}
295 355
300 360
	);
301 361
}
302 362
303 -
function page(body: string, styleHref: string): string {
363 +
function page(body: string, styleHref: string, headExtra = ""): string {
304 364
	return `<!DOCTYPE html>
305 365
<html lang="en">
306 366
<head>
309 369
  <title>Sequoia · Subscribe</title>
310 370
  <link rel="stylesheet" href="${styleHref}" />
311 371
  <script>if(window.matchMedia('(prefers-color-scheme: dark)').matches)document.documentElement.classList.add('dark')</script>
372 +
  ${headExtra}
312 373
  <style>
313 374
    .page-container {
314 375
      max-width: calc(var(--vocs-content_width, 480px) / 1.6);