Files
inbuxa-admin/src/lib/sievepad.ts
T
jcoffey-dev 3a33f3d514 Mark the files this fork changed (AGPL section 5(a))
Publishing the source is what asks for it: a modified version has to carry
prominent notices saying it was modified, with a date. These files already
added a Coffey Labs copyright line beside upstream's, which implies as much
without saying it; now they say it.

38 files, found by diffing against the merge base with upstream rather than
by guessing. Upstream's own notices are untouched. The build is unchanged.
2026-09-19 23:48:44 -07:00

81 lines
2.4 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/
export const SIEVEPAD_URL = 'https://sievepad.com/';
const SIEVEPAD_FORMAT_VERSION = 1;
const SIEVEPAD_MAX_NAME_LENGTH = 80;
const SIEVEPAD_MAIN_SCRIPT = 'main';
const BASE64_CHUNK_SIZE = 0x8000;
const WARNING_DISMISSED_KEY = 'inbuxa-sievepad-warning-dismissed';
const SIEVE_SCRIPT_FIELDS: Record<string, string> = {
'x:SieveSystemScript': 'contents',
'x:SieveUserScript': 'contents',
SieveScript: 'blobId',
};
export function isSieveScriptField(objectName: string, fieldName: string): boolean {
return SIEVE_SCRIPT_FIELDS[objectName] === fieldName;
}
function toBase64Url(bytes: Uint8Array): string {
let binary = '';
for (let i = 0; i < bytes.length; i += BASE64_CHUNK_SIZE) {
binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK_SIZE));
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
export async function sievepadLink(name: string, source: string, base = SIEVEPAD_URL): Promise<string> {
const json = JSON.stringify({
v: SIEVEPAD_FORMAT_VERSION,
name: name.slice(0, SIEVEPAD_MAX_NAME_LENGTH),
scripts: [{ name: SIEVEPAD_MAIN_SCRIPT, source: source.replace(/\r\n/g, '\n') }],
messages: [],
settings: {},
});
const stream = new Blob([new TextEncoder().encode(json)]).stream().pipeThrough(new CompressionStream('deflate-raw'));
const packed = new Uint8Array(await new Response(stream).arrayBuffer());
return `${base}#w=${toBase64Url(packed)}`;
}
export async function openInSievepad(name: string, source: string): Promise<void> {
const tab = window.open('about:blank', '_blank');
if (tab) tab.opener = null;
let link: string;
try {
link = await sievepadLink(name, source);
} catch (err) {
tab?.close();
throw err;
}
if (tab) {
tab.location.replace(link);
} else {
window.open(link, '_blank', 'noopener,noreferrer');
}
}
export function isSievepadWarningDismissed(): boolean {
try {
return localStorage.getItem(WARNING_DISMISSED_KEY) === 'true';
} catch {
return false;
}
}
export function dismissSievepadWarning(): void {
try {
localStorage.setItem(WARNING_DISMISSED_KEY, 'true');
} catch {
return;
}
}