Skip to content

Instantly share code, notes, and snippets.

@tschoffelen
Created June 1, 2025 17:11
Show Gist options
  • Save tschoffelen/5fa4ca1841ee09f96c9a0f21b4787eff to your computer and use it in GitHub Desktop.
Save tschoffelen/5fa4ca1841ee09f96c9a0f21b4787eff to your computer and use it in GitHub Desktop.
export class Plist {
options: Record<string, any>;
constructor(options: Record<string, any>) {
this.options = options;
}
toXML(): string {
const plistHeader = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">`;
const plistFooter = `</plist>`;
const dictEntries = Object.entries(this.options)
.map(([key, value]) => {
if (typeof value === "boolean") {
return `<key>${key}</key><${value ? "true" : "false"}/>`;
} else if (typeof value === "string") {
return `<key>${key}</key><string>${value}</string>`;
} else if (Array.isArray(value)) {
return `<key>${key}</key><array>${value.map((item) => `<string>${item}</string>`).join("")}</array>`;
} else if (typeof value === "number") {
return `<key>${key}</key><integer>${value}</integer>`;
}
return "";
})
.join("");
return `${plistHeader}<dict>${dictEntries}</dict>${plistFooter}`;
}
/**
* Converts the plist options to a SEB JSON format.
* https://safeexambrowser.org/developer/seb-config-key.html
*
* @return {string} The SEB JSON string representation of the options.
*/
toSebJSON(): string {
const json = { ...this.options };
if (json.originatorVersion) delete json.originatorVersion; // Remove originatorVersion if it exists
// Order the the JSON by keys alphabetically
const orderedJson = Object.keys(json)
.sort()
.reduce((acc, key) => {
acc[key] = json[key];
return acc;
}, {});
return JSON.stringify(orderedJson);
}
async _hash(str: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hash = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hash));
return hashArray
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
.toLowerCase();
}
/**
* Generate a SHA256 hash from the SEB-JSON string
* Encode the hash value as a Base16 string. The result should be a 32 Bytes, 64 chars string.
*
* @returns {Promise<string>} The SHA256 hash of the SEB JSON string, for a specific start URL.
*/
async getConfigKey(): Promise<string> {
const sebJson = this.toSebJSON();
const configKey = await this._hash(sebJson);
return await this._hash(this.options.startURL + configKey);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment