Skip to content

Commit

Permalink
Use blob urls instead of base64 urls
Browse files Browse the repository at this point in the history
Chrome doesn't allow top-level navigation to base64 urls, so this increases device support for showing attachments and documents
  • Loading branch information
PurelyAnecdotal committed Dec 26, 2024
1 parent f128e6d commit b661b42
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 43 deletions.
5 changes: 4 additions & 1 deletion bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"workspaces": {
"": {
"dependencies": {
"buffer": "^6.0.3",
"fast-xml-parser": "^4.5.1",
"file-type": "^19.6.0",
},
Expand Down Expand Up @@ -306,7 +307,7 @@

"browserslist": ["[email protected]", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA=="],

"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],

"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA=="],

Expand Down Expand Up @@ -864,6 +865,8 @@

"anymatch/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],

"bl/buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],

"fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],

"import-fresh/resolve-from": ["[email protected]", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"vite": "^6.0.5"
},
"dependencies": {
"buffer": "^6.0.3",
"fast-xml-parser": "^4.5.1",
"file-type": "^19.6.0"
},
Expand Down
15 changes: 15 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { fileTypeFromBuffer } from 'file-type';
import { Buffer } from 'buffer';

export function getColorForGrade(grade: string | number) {
if (typeof grade == 'number') {
if (grade > 100) return 'blue';
Expand Down Expand Up @@ -47,3 +50,15 @@ export const shortDateFormatter = new Intl.DateTimeFormat('en-US', {
export const fullDateFormatter = new Intl.DateTimeFormat('en-US', {
dateStyle: 'full'
});

export async function getBlobURLFromBase64String(base64: string) {
const byteArray = new Uint8Array(Buffer.from(base64, 'base64'));

const mimeType = (await fileTypeFromBuffer(byteArray))?.mime;

if (!mimeType) throw new Error('Could not determine MIME type');

const blob = new Blob([byteArray], { type: mimeType });

return URL.createObjectURL(blob);
}
11 changes: 3 additions & 8 deletions src/lib/synergy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { AuthToken } from '$lib/types/AuthToken';
import type { Documents } from '$lib/types/Documents';
import type { Gradebook } from '$lib/types/Gradebook';
import type { MailData } from '$lib/types/MailData';
import type { ReportCard } from '$lib/types/ReportCard';
import type { ReportCard, ReportCardNotFound } from '$lib/types/ReportCard';
import type { StudentInfo } from '$lib/types/StudentInfo';
import { XMLBuilder, XMLParser } from 'fast-xml-parser';

Expand Down Expand Up @@ -122,13 +122,8 @@ export class StudentAccount {
}

async reportCard(documentGU: string): Promise<ReportCard> {
const documentData: ReportCard = (
await this.request('GetReportCardDocumentData', { DocumentGU: documentGU })
).DocumentData;

if ('Base64Code' in documentData) return documentData;

throw new Error('Document not found');
return (await this.request('GetReportCardDocumentData', { DocumentGU: documentGU }))
.DocumentData;
}

async mailData(): Promise<MailData> {
Expand Down
15 changes: 9 additions & 6 deletions src/lib/types/ReportCard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
export interface ReportCard {
Base64Code?: string;
export interface ReportCardNotFound {
'_xmlns:xsd': string;
'_xmlns:xsi': string;
_DocumentGU?: string;
_FileName?: string;
_DocFileName?: string;
_DocType?: string;
}

export interface ReportCard extends ReportCardNotFound {
Base64Code: string;
_DocumentGU: string;
_FileName: string;
_DocFileName: string;
_DocType: string;
}
27 changes: 17 additions & 10 deletions src/routes/(authed)/documents/document/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<script lang="ts">
import { page } from '$app/state';
import { getBlobURLFromBase64String } from '$lib';
import LoadingBanner from '$lib/components/LoadingBanner.svelte';
import { studentAccount } from '$lib/stores';
import type { ReportCard } from '$lib/types/ReportCard';
import { Button, Card } from 'flowbite-svelte';
import { onMount } from 'svelte';
let reportCardPromise: Promise<ReportCard>;
const documentGU = page.url.searchParams.get('documentGU');
let reportCardURLPromise: Promise<string> | undefined = $state();
onMount(async () => {
reportCardPromise = new Promise(async (resolve, reject) => {
reportCardURLPromise = new Promise(async (resolve, reject) => {
if (!documentGU) {
reject(new Error('DocumentGU not provided'));
return;
Expand All @@ -22,26 +23,32 @@
return;
}
let reportCard: ReportCard;
try {
resolve(await $studentAccount.reportCard(documentGU));
reportCard = await $studentAccount.reportCard(documentGU);
} catch {
reject(new Error('Document not found'));
return;
}
});
const reportCard = await reportCardPromise;
try {
resolve(getBlobURLFromBase64String(reportCard.Base64Code));
} catch (error) {
reject(error);
}
});
location.assign(`data:application/pdf;base64,${reportCard.Base64Code}`);
location.assign(await reportCardURLPromise);
});
</script>

<svelte:head>
<title>Document - GradeVue</title>
</svelte:head>

{#if reportCardPromise}
{#await reportCardPromise}
{#if reportCardURLPromise}
{#await reportCardURLPromise}
<LoadingBanner loadingMsg="Loading document..." />
{:then}
<LoadingBanner loadingMsg="Redirecting..." />
Expand All @@ -50,7 +57,7 @@
<Card class="space-y-4 text-sm leading-relaxed dark:text-gray-200">
<h1 class="text-2xl dark:text-white">{error}</h1>

<Button href="/documents" class="w-full">Return</Button>
<Button href="/documents" class="w-full">Return to Documents</Button>
</Card>
</div>
{/await}
Expand Down
28 changes: 10 additions & 18 deletions src/routes/(authed)/mail/attachment/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<script lang="ts">
import { page } from '$app/state';
import { getBlobURLFromBase64String } from '$lib';
import LoadingBanner from '$lib/components/LoadingBanner.svelte';
import { studentAccount } from '$lib/stores';
import type { Attachment } from '$lib/types/Attachment';
import { Buffer } from 'buffer';
import { fileTypeFromBuffer } from 'file-type';
import { Button, Card } from 'flowbite-svelte';
import { onMount } from 'svelte';
const attachmentGU = page.url.searchParams.get('attachmentGU');
let attachmentPromise: Promise<{ attachment: Attachment; mimeType: string }>;
let attachmentURLPromise: Promise<string> | undefined = $state();
onMount(async () => {
attachmentPromise = new Promise(async (resolve, reject) => {
attachmentURLPromise = new Promise(async (resolve, reject) => {
if (!attachmentGU) {
reject(new Error('AttachmentGU not provided'));
return;
Expand All @@ -33,30 +32,23 @@
return;
}
const mimeType = (
await fileTypeFromBuffer(new Uint8Array(Buffer.from(attachment.Base64Code, 'base64')))
)?.mime;
if (!mimeType) {
reject(new Error('Could not determine MIME type of attachment'));
return;
try {
resolve(getBlobURLFromBase64String(attachment.Base64Code));
} catch (error) {
reject(error);
}
resolve({ attachment, mimeType });
});
const { attachment, mimeType } = await attachmentPromise;
location.assign(`data:${mimeType};base64,${attachment.Base64Code}`);
location.assign(await attachmentURLPromise);
});
</script>

<svelte:head>
<title>Attachment - GradeVue</title>
</svelte:head>

{#if $studentAccount}
{#await attachmentPromise}
{#if attachmentURLPromise}
{#await attachmentURLPromise}
<LoadingBanner loadingMsg="Loading attachment..." />
{:then}
<LoadingBanner loadingMsg="Redirecting..." />
Expand Down

0 comments on commit b661b42

Please sign in to comment.