fix: fix qr code to use safer methoed & bump ver

This commit is contained in:
2026-03-25 08:48:47 +10:30
parent a0367be686
commit d692f60291
6 changed files with 82 additions and 45 deletions
+26
View File
@@ -11,6 +11,30 @@ import { main } from "@/seqta/main";
import { delay } from "./seqta/utils/delay";
import { initializeHideSensitiveToggle } from "@/seqta/utils/hideSensitiveToggle";
function registerFetchSeqtaAppLinkListener() {
browser.runtime.onMessage.addListener((request, _sender, sendResponse) => {
if (request?.type !== "fetchSeqtaAppLink") return false;
void (async () => {
try {
const res = await fetch(`${location.origin}/seqta/student/load/profile`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({}),
});
const data = await res.json();
const statusOk = data?.status === "200" || data?.status === 200;
const raw = data?.payload?.app_link;
const appLink = typeof raw === "string" && raw.length > 0 ? raw : null;
sendResponse({ appLink: statusOk ? appLink : null });
} catch {
sendResponse({ appLink: null });
}
})();
return true;
});
}
export let MenuOptionsOpen = false;
var IsSEQTAPage = false;
@@ -30,6 +54,8 @@ async function init() {
IsSEQTAPage = true;
console.info("[BetterSEQTA+] Verified SEQTA Page");
registerFetchSeqtaAppLinkListener();
const documentLoadStyle = document.createElement("style");
documentLoadStyle.textContent = documentLoadCSS;
document.head.appendChild(documentLoadStyle);
+38 -18
View File
@@ -167,8 +167,19 @@ function handleCloudFavorite(request: any, sendResponse: MessageSender): boolean
return true;
}
/** Handler for a message type; receives request and sendResponse callback */
type MessageHandler = { (request: any, sendResponse: MessageSender): boolean | void };
/** Handler for a message type; receives request, sendResponse, and optional sender (for tab routing) */
type MessageHandler = {
(request: any, sendResponse: MessageSender, sender?: browser.Runtime.MessageSender): boolean | void;
};
function isSeqtaOrigin(origin: string): boolean {
try {
const u = new URL(origin);
return u.hostname.includes("seqta") || u.hostname.endsWith(".edu.au");
} catch {
return false;
}
}
const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
reloadTabs: () => reloadSeqtaPages(),
@@ -200,29 +211,38 @@ const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
cloudLogin: handleCloudLogin,
cloudRefresh: handleCloudRefresh,
cloudFavorite: handleCloudFavorite,
getSeqtaSession: (req: { baseUrl?: string }, sendResponse: MessageSender) => {
getSeqtaSession: (req: { baseUrl?: string }, sendResponse: MessageSender, sender?: browser.Runtime.MessageSender) => {
(async () => {
try {
let baseUrl = req.baseUrl;
if (!baseUrl) {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
let tabId = sender?.tab?.id;
let originForCheck: string | undefined = req.baseUrl;
if (tabId == null) {
const tabs = await browser.tabs.query({ active: true, lastFocusedWindow: true });
const tab = tabs[0];
if (!tab?.url) {
sendResponse({ session: null });
if (!tab?.id || !tab.url) {
sendResponse({ appLink: null });
return;
}
baseUrl = new URL(tab.url).origin;
tabId = tab.id;
if (!originForCheck) originForCheck = new URL(tab.url).origin;
} else if (!originForCheck && sender?.tab?.url) {
originForCheck = new URL(sender.tab.url).origin;
}
const cookies = await browser.cookies.getAll({ url: baseUrl });
const jsession = cookies.find((c) => c.name === "JSESSIONID");
if (jsession?.value) {
sendResponse({ session: { baseUrl, jsessionId: jsession.value } });
} else {
sendResponse({ session: null });
if (!originForCheck || !isSeqtaOrigin(originForCheck)) {
sendResponse({ appLink: null });
return;
}
const reply = (await browser.tabs.sendMessage(tabId, { type: "fetchSeqtaAppLink" })) as
| { appLink?: string | null }
| undefined;
const appLink = typeof reply?.appLink === "string" && reply.appLink.length > 0 ? reply.appLink : null;
sendResponse({ appLink });
} catch (err) {
console.error("[Background] getSeqtaSession error:", err);
sendResponse({ session: null });
sendResponse({ appLink: null });
}
})();
return true;
@@ -231,10 +251,10 @@ const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
browser.runtime.onMessage.addListener(
// @ts-ignore - OnMessageListener expects literal true for async, we return boolean
(request: any, _: any, sendResponse: MessageSender) => {
(request: any, sender: browser.Runtime.MessageSender, sendResponse: MessageSender) => {
const handler = MESSAGE_HANDLERS[request.type];
if (handler) {
const result = handler(request, sendResponse);
const result = handler(request, sendResponse, sender);
return result === true;
}
console.log("Unknown request type");
@@ -4,11 +4,9 @@
import QRCode from "qrcode";
import { portal } from "../utils/portal";
const DEEPLINK_PREFIX = "desqta://connect/";
let showQrModal = $state(false);
let qrDataUrl = $state<string | null>(null);
let deeplink = $state<string | null>(null);
let appLink = $state<string | null>(null);
let errorMessage = $state<string | null>(null);
let isLoading = $state(false);
let isStandalone = $state(false);
@@ -38,30 +36,21 @@
}
}
function buildDesqtaConnectPayload(baseUrl: string, jsessionId: string): string {
const payload = JSON.stringify({ u: baseUrl, s: jsessionId });
const base64 = btoa(unescape(encodeURIComponent(payload)));
const encoded = encodeURIComponent(base64);
return `${DEEPLINK_PREFIX}${encoded}`;
}
async function getSession(): Promise<{ baseUrl: string; jsessionId: string } | null> {
async function getAppLink(): Promise<string | null> {
let baseUrl: string | undefined;
if (isExtensionPage()) {
// Extension popup: background will get URL from active tab
baseUrl = undefined;
} else {
// In-page (settings inside SEQTA): pass current page URL (cookies API not available in content scripts)
baseUrl = normalizeBaseUrl(window.location.href);
if (!isSeqtaUrl(baseUrl)) return null;
}
const { session } = (await browser.runtime.sendMessage({
const { appLink: link } = (await browser.runtime.sendMessage({
type: "getSeqtaSession",
baseUrl,
})) as { session: { baseUrl: string; jsessionId: string } | null };
return session ?? null;
})) as { appLink: string | null };
return link ?? null;
}
async function generateQrCode() {
@@ -71,9 +60,9 @@
try {
isStandalone = isExtensionPage();
const session = await getSession();
const link = await getAppLink();
if (!session) {
if (!link) {
if (isStandalone) {
errorMessage =
"Open SEQTA Learn in a tab and log in, then open settings from that tab to generate a QR code.";
@@ -83,9 +72,8 @@
return;
}
const link = buildDesqtaConnectPayload(session.baseUrl, session.jsessionId);
const dataUrl = await QRCode.toDataURL(link, { width: 256, margin: 2 });
deeplink = link;
appLink = link;
qrDataUrl = dataUrl;
showQrModal = true;
} catch (err) {
@@ -99,12 +87,12 @@
function closeModal() {
showQrModal = false;
qrDataUrl = null;
deeplink = null;
appLink = null;
errorMessage = null;
}
function openInDesqta() {
if (deeplink) window.location.href = deeplink;
function openAppLink() {
if (appLink) window.location.href = appLink;
}
function downloadQrImage() {
@@ -160,12 +148,12 @@
</button>
</div>
<div class="flex justify-center p-4 bg-white rounded-xl dark:bg-zinc-900">
<img src={qrDataUrl} alt="DesQTA QR Code" class="w-64 h-64" />
<img src={qrDataUrl} alt="SEQTA Learn app link QR code" class="w-64 h-64" />
</div>
<div class="flex flex-col gap-2 mt-4">
<button
type="button"
onclick={openInDesqta}
onclick={openAppLink}
class="px-4 py-2.5 w-full text-sm font-medium text-white bg-indigo-600 rounded-lg transition-colors dark:bg-indigo-500 hover:bg-indigo-700 dark:hover:bg-indigo-600">
Sign into DesQTA Desktop
</button>
+1 -1
View File
@@ -15,7 +15,7 @@
"64": "resources/icons/icon-64.png"
}
},
"permissions": ["tabs", "notifications", "storage", "cookies"],
"permissions": ["tabs", "notifications", "storage"],
"host_permissions": ["https://newsapi.org/", "https://betterseqta.org/", "https://accounts.betterseqta.org/", "*://*/*"],
"background": {
"service_worker": "background.ts"
@@ -32,6 +32,9 @@ export function OpenWhatsNewPopup() {
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
<h1>3.5.1 - QR & session link fix</h1>
<li>Fixed DesQTA Connect Mobile App QR generation on Chrome</li>
<h1>3.5.0 - Adaptive Theme, Timetable Editor & More</h1>
<li>Added adaptive theme colour</li>
<li>Added optional soft gradient for adaptive theme when viewing a class</li>