Compare commits
6
Commits
e11a7aad83
...
cd418f513d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd418f513d | ||
|
|
f68aa305fa | ||
|
|
9edaa687a5 | ||
|
|
b571953f10 | ||
|
|
71ef7fa36f | ||
|
|
73c68cc1e8 |
@@ -0,0 +1,46 @@
|
|||||||
|
name: build-extension
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [feat/ismis-helper, prod]
|
||||||
|
tags: ['v*']
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install dev dependencies
|
||||||
|
run: npm ci || npm install
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Build both variants
|
||||||
|
run: node scripts/build.js all
|
||||||
|
|
||||||
|
- name: Upload zip artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ismis-helper-zips
|
||||||
|
path: dist/*.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Publish release on version tag
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
# softprops/action-gh-release works on Gitea Actions: it uses the
|
||||||
|
# automatically-provided GITHUB_TOKEN to create a Release for the tag
|
||||||
|
# and attaches the supplied files.
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: dist/*.zip
|
||||||
|
name: ${{ github.ref_name }}
|
||||||
|
generate_release_notes: true
|
||||||
@@ -1 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
docs/
|
||||||
|
dist/
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# ISMIS Helper — Agent Guide
|
||||||
|
|
||||||
|
Guidance for AI coding agents and contributors working in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A Firefox/Chrome **MV3** extension that automates the USC ISMIS student portal
|
||||||
|
(`ismis.usc.edu.ph`): auto-login, privacy-policy auto-accept, and a **Study Load
|
||||||
|
PDF** export that visually matches the official document (bundled **Segoe UI**
|
||||||
|
fonts, USC logo, course table, units total).
|
||||||
|
|
||||||
|
No build step is required for the source itself; `scripts/build.js` only packages
|
||||||
|
the two browser variants into `dist/`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # dev deps (only needed for tests)
|
||||||
|
npm test # run the test suite (node --test)
|
||||||
|
node scripts/build.js all # build both browser zips -> dist/
|
||||||
|
node scripts/build.js firefox # build just firefox
|
||||||
|
node scripts/build.js chrome # build just chrome
|
||||||
|
node scripts/build.js all --version 1.3.0 # versioned zips
|
||||||
|
npm run build # shorthand for `node scripts/build.js all`
|
||||||
|
```
|
||||||
|
|
||||||
|
Never edit anything under `dist/` — it is generated and gitignored.
|
||||||
|
|
||||||
|
## Repo layout
|
||||||
|
|
||||||
|
- `manifest.json` — **Firefox** manifest (the single source of truth). The Chrome
|
||||||
|
manifest is derived at build time.
|
||||||
|
- `background.js` — message handler; builds the PDF via `pdf.js` and downloads it.
|
||||||
|
Also has the cross-browser `browser.*`/`chrome.*` alias shim at the top.
|
||||||
|
- `content.js` — runs on ismis.usc.edu.ph: auto-login, privacy modal auto-accept,
|
||||||
|
scrapes the study load, injects the on-page **Download PDF** button.
|
||||||
|
- `parsers.js` — pure HTML-scraping helpers (study load table, profile page,
|
||||||
|
offered courses, filtering/summing).
|
||||||
|
- `pdf.js` — builds the PDF; exports `buildStudyLoadPdf` plus formatting helpers
|
||||||
|
(`compactProgram`, `formatSchedule`, `formatFullName`, `formatTeacher`,
|
||||||
|
`deriveDateRange`, `truncateToWidth`).
|
||||||
|
- `popup.html` / `popup.js` / `popup.css` — settings UI (profiles, master toggle,
|
||||||
|
PDF settings like truncation and Year Level).
|
||||||
|
- `libs/` — vendored `pdf-lib.min.js` and `fontkit.min.js` (needed to embed the
|
||||||
|
custom Segoe UI fonts).
|
||||||
|
- `assets/` — the transparent PNG logo and `fonts/Segoe-UI*.ttf`.
|
||||||
|
- `icons/` — 16/32/48/128 extension icons.
|
||||||
|
- `scripts/build.js` — zero-dependency packaging script for both browsers.
|
||||||
|
- `.gitea/workflows/build.yml` — Gitea Actions: builds + tests on push, creates a
|
||||||
|
Gitea Release with both zips when a `v*` tag is pushed.
|
||||||
|
- `test/` — `node --test` suite (`pdf.test.js`, `parsers.test.js`).
|
||||||
|
|
||||||
|
## Build model (Firefox vs Chrome)
|
||||||
|
|
||||||
|
- The **root is the Firefox source**: `manifest.json` uses `background.scripts`
|
||||||
|
(multiple files) and `browser.*` APIs.
|
||||||
|
- The **Chrome build** derives a manifest with
|
||||||
|
`"background": { "service_worker": "background.chrome.js" }` and assembles that
|
||||||
|
single worker by concatenating, in order:
|
||||||
|
`libs/fontkit.min.js` → `libs/pdf-lib.min.js` → `pdf.js` → `background.js`.
|
||||||
|
- The cross-browser shim (top of `background.js`, `content.js`, `popup.js`) aliases
|
||||||
|
`browser` to `chrome` when the former is absent. It is a no-op in Firefox.
|
||||||
|
- `scripts/build.js` **excludes** `test/`, `docs/`, `scripts/`, `dist/`, and
|
||||||
|
`.git` from the packaged output.
|
||||||
|
|
||||||
|
## Conventions & constraints
|
||||||
|
|
||||||
|
- Plain ES5+ JavaScript. No framework, no bundler, no build step for the source.
|
||||||
|
- Use Promise-based `browser.*` APIs (Chrome MV3 supports `browser.*`; the shim
|
||||||
|
covers builds that only expose `chrome.*`).
|
||||||
|
- Login profiles are stored **plaintext** in `browser.storage.local`. At most one
|
||||||
|
profile is enabled at a time; a master `enabled` toggle disables all behavior.
|
||||||
|
- The on-page **Download PDF** button must never touch another window's DOM; it
|
||||||
|
sends a message to the background which downloads via
|
||||||
|
`browser.downloads.download({ saveAs: true })` (blob → object URL).
|
||||||
|
- If you change `pdf.js` layout constants, verify against the reference PDF
|
||||||
|
(`/Users/adrianbonpin/Downloads/BSIT - Y3S1 - Study Load.pdf`) and re-check the
|
||||||
|
position measurements, since offsets can be font-metric dependent.
|
||||||
|
- Keep `docs/` and `dist/` out of git (they are ignored).
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Run `npm test` before and after changes.
|
||||||
|
2. Build (`npm run build`) to make sure both variants assemble.
|
||||||
|
3. Manually load `dist/firefox` in Firefox (`about:debugging`) and `dist/chrome`
|
||||||
|
in Chrome (`chrome://extensions`) to verify PDF generation.
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# ISMIS Helper
|
||||||
|
|
||||||
|
A lightweight **[Firefox](https://addons.mozilla.org/)** / **[Chrome](https://chromewebstore.google.com/)**
|
||||||
|
extension for the USC ISMIS student portal that:
|
||||||
|
|
||||||
|
- Auto-fills and auto-submits your login.
|
||||||
|
- Auto-accepts the privacy-policy modal.
|
||||||
|
- Exports a **Study Load PDF** that matches the official layout — bundled **Segoe UI**
|
||||||
|
fonts, the USC logo, the course table, and the units total.
|
||||||
|
|
||||||
|
One codebase builds for **both browsers** (see [Build](#build)).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Saved login profiles** (stored in `browser.storage.local`) — only one enabled at
|
||||||
|
a time, with a master on/off toggle.
|
||||||
|
- **One-click Download PDF** button on the Study Load page.
|
||||||
|
- PDF settings in the popup:
|
||||||
|
- Truncate long course descriptions to match the official PDF (default on).
|
||||||
|
- Show/hide `Year Level`, and set a custom Year Level value.
|
||||||
|
- **Bundled Segoe UI fonts** for a 1:1 look with the official document.
|
||||||
|
- Builds and packages automatically via Gitea Actions (zips + versioned releases).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
> Temporary/development install. For distribution, publish the built zips to
|
||||||
|
> Firefox AMO and the Chrome Web Store — they are store-ready.
|
||||||
|
|
||||||
|
### Firefox (load temporary add-on)
|
||||||
|
|
||||||
|
1. Build the Firefox variant (or download `ismis-helper-firefox.zip` from CI):
|
||||||
|
```bash
|
||||||
|
node scripts/build.js firefox
|
||||||
|
```
|
||||||
|
The output is at `dist/ismis-helper-firefox.zip` (unzip it, or use `dist/firefox/`
|
||||||
|
directly).
|
||||||
|
2. Open `about:debugging` in Firefox.
|
||||||
|
3. Click **This Firefox** → **Load Temporary Add-on…**.
|
||||||
|
4. Select `dist/firefox/manifest.json`.
|
||||||
|
5. Open `https://ismis.usc.edu.ph` and use the extension.
|
||||||
|
|
||||||
|
### Chrome (load unpacked)
|
||||||
|
|
||||||
|
1. Build the Chrome variant (or download `ismis-helper-chrome.zip` from CI):
|
||||||
|
```bash
|
||||||
|
node scripts/build.js chrome
|
||||||
|
```
|
||||||
|
2. Unzip `dist/ismis-helper-chrome.zip` (or use `dist/chrome/` directly).
|
||||||
|
3. Open `chrome://extensions` in Chrome.
|
||||||
|
4. Enable **Developer mode** (toggle, top-right).
|
||||||
|
5. Click **Load unpacked** and select the unzipped `dist/chrome` folder.
|
||||||
|
6. Open `https://ismis.usc.edu.ph` and use the extension.
|
||||||
|
|
||||||
|
> For a persistent install, package and publish via Firefox AMO / Chrome Web Store
|
||||||
|
> with the same built zips.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Requires **Node.js** (the `build.js` script itself is dependency-free; `npm install`
|
||||||
|
is only needed to run the tests).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # dev deps for tests
|
||||||
|
npm test # run the test suite
|
||||||
|
npm run build # build both -> dist/*.zip
|
||||||
|
node scripts/build.js all # build both explicitly
|
||||||
|
node scripts/build.js chrome # just chrome
|
||||||
|
node scripts/build.js all --version 1.3.0 # versioned zips
|
||||||
|
```
|
||||||
|
|
||||||
|
**CI (Gitea Actions):** on every push, both zips are built, tests run, and the
|
||||||
|
zips are uploaded as artifacts. When you push a `v*` tag (e.g. `v1.3.0`), it
|
||||||
|
additionally creates a **Gitea Release** with both versioned zips attached.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Open the extension popup to:
|
||||||
|
- Manage login profiles (add / edit / enable).
|
||||||
|
- Toggle the master on/off switch.
|
||||||
|
- Set PDF options: truncate descriptions, show/hide `Year Level`, custom Year Level.
|
||||||
|
|
||||||
|
## Data & privacy
|
||||||
|
|
||||||
|
- Auto-login stores your portal credentials **in plaintext** on your own device.
|
||||||
|
- This is a personal-use helper for the university portal; use it only with your
|
||||||
|
own account.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
See [`AGENTS.md`](AGENTS.md) for the repository layout, build model, and coding
|
||||||
|
conventions for this project.
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
// Cross-browser API namespace. Firefox provides Promise-based `browser.*`;
|
||||||
|
// Chrome MV3 exposes `browser.*` too, but some builds only populate the
|
||||||
|
// `chrome.*` global. Alias to whichever is present.
|
||||||
|
if (typeof browser === 'undefined' || browser == null) {
|
||||||
|
var browser = (typeof chrome !== 'undefined' ? chrome : {});
|
||||||
|
}
|
||||||
|
|
||||||
function errMsg(err) {
|
function errMsg(err) {
|
||||||
return String((err && err.message) || err);
|
return String((err && err.message) || err);
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// Cross-browser API namespace (see background.js). No-op in Firefox.
|
||||||
|
if (typeof browser === 'undefined' || browser == null) {
|
||||||
|
var browser = (typeof chrome !== 'undefined' ? chrome : {});
|
||||||
|
}
|
||||||
|
|
||||||
const MASTER_KEY = 'enabled';
|
const MASTER_KEY = 'enabled';
|
||||||
|
|
||||||
async function isEnabled() {
|
async function isEnabled() {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,145 +0,0 @@
|
|||||||
# ISMIS Helper — Firefox Extension Design
|
|
||||||
|
|
||||||
**Date:** 2026-08-18
|
|
||||||
**Status:** Approved
|
|
||||||
**Target site:** `https://ismis.usc.edu.ph` (USC ISMIS student portal)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A personal Firefox extension (Manifest V3, no build step) that improves the USC ISMIS
|
|
||||||
student portal experience. It provides three features:
|
|
||||||
|
|
||||||
1. **Auto-login** — auto-fill and auto-submit the login form from a saved profile.
|
|
||||||
2. **Auto-accept Privacy Policy** — dismiss the recurring privacy-policy modal.
|
|
||||||
3. **Study Load PDF** — an on-page "Download PDF" button on the Study Load page that
|
|
||||||
generates a PDF replicating the official ISMIS Study Load document (Letter size,
|
|
||||||
with course descriptions looked up from the Offered Courses endpoint) and
|
|
||||||
downloads it directly.
|
|
||||||
|
|
||||||
A single **master toggle** disables all extension behavior on the portal.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Single MV3 extension, plain JavaScript, no bundler. No background script — all logic
|
|
||||||
runs in the content script (scraping and print-document building happen in-page).
|
|
||||||
|
|
||||||
```
|
|
||||||
ismis-helper/
|
|
||||||
├── manifest.json # MV3 manifest, permissions, content script, popup, background
|
|
||||||
├── background.js # message handler: build PDF (pdf-lib) + download via downloads API
|
|
||||||
├── content.js # runs on ismis.usc.edu.ph: auto-login, privacy, button, scrape
|
|
||||||
├── parsers.js # UMD: table/profile/offered-courses parsing + filtering
|
|
||||||
├── pdf.js # UMD: buildStudyLoadPdf (Letter PDF, official layout, pagination)
|
|
||||||
├── popup.html # popup UI (master toggle + profiles)
|
|
||||||
├── popup.js # popup logic
|
|
||||||
├── popup.css # popup styles
|
|
||||||
├── libs/pdf-lib.min.js # vendored pdf-lib library (used by background)
|
|
||||||
└── icons/ # extension icons (16, 32, 48, 128)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permissions (manifest.json)
|
|
||||||
- `storage` — persist profiles and the master toggle.
|
|
||||||
- `downloads` — save the generated Study Load PDF.
|
|
||||||
- `host_permissions` — `https://ismis.usc.edu.ph/*` for the content script.
|
|
||||||
- Background loads `libs/pdf-lib.min.js` + `pdf.js` via `background.scripts`.
|
|
||||||
|
|
||||||
## Background script
|
|
||||||
- `background.js` handles the `generate-pdf` message from the content script:
|
|
||||||
builds the PDF via `ISMIStools.buildStudyLoadPdf`, wraps the bytes in a Blob,
|
|
||||||
downloads via `browser.downloads.download({ saveAs: true })`, and revokes the
|
|
||||||
object URL when the download reaches `complete`/`interrupted` (and on failure).
|
|
||||||
|
|
||||||
## Component 1 — Auto-login
|
|
||||||
|
|
||||||
- Content script runs on `https://ismis.usc.edu.ph/Account/Login`.
|
|
||||||
- Reads the active (enabled) profile from `browser.storage.local`.
|
|
||||||
- Locates the username and password input fields (`name`/`id` = `Username` /
|
|
||||||
`Password`, with fallback variants).
|
|
||||||
- Fills both fields, then submits the form (auto-submit).
|
|
||||||
- If no profile is enabled, or the master toggle is off, does nothing.
|
|
||||||
|
|
||||||
## Component 2 — Auto-accept Privacy Policy
|
|
||||||
|
|
||||||
- On any portal page, if the privacy modal `#myModal` is present and visible:
|
|
||||||
- Check the `#Confirmation` checkbox ("I have read and understood the Privacy
|
|
||||||
Policy Statement").
|
|
||||||
- Click the `#Submit` button (Continue), which is disabled until the checkbox is
|
|
||||||
checked.
|
|
||||||
- Gated by the master toggle.
|
|
||||||
|
|
||||||
## Component 3 — Profiles
|
|
||||||
|
|
||||||
- Stored in `browser.storage.local` under key `profiles` as an array of:
|
|
||||||
`{ id, name, username, password, enabled }`.
|
|
||||||
- Popup provides add, edit, delete, and enable/disable per profile.
|
|
||||||
- Only one profile can be enabled at a time (enabling one disables the others);
|
|
||||||
that profile is used for auto-login.
|
|
||||||
- Master toggle stored under key `enabled` (boolean).
|
|
||||||
|
|
||||||
## Component 4 — Study Load PDF
|
|
||||||
|
|
||||||
### On-page button
|
|
||||||
- The content script watches the Study Load page (`#StudyLoad`). When the course
|
|
||||||
table appears (after the user selects academic period/year and searches), it
|
|
||||||
injects a **"Download PDF"** button inline with the
|
|
||||||
`12.00 Units / Total Units: 18.00` footer (button on the left, units text on the
|
|
||||||
right, via a flex row), styled like the page's buttons (`btn green`).
|
|
||||||
|
|
||||||
### Scrape
|
|
||||||
- Parse the course table (columns: Group #, Course Code, Schedule, Faculty, Units,
|
|
||||||
Status) via `parseStudyLoadTable`.
|
|
||||||
- **Header info** (full name, student ID, program, year level, semester) is scraped
|
|
||||||
from the **My Profile** page (`/Manage/Student`).
|
|
||||||
|
|
||||||
### Filtering
|
|
||||||
- **DISSOLVED courses are excluded** (`filterStudyLoadCourses`).
|
|
||||||
- Total units = sum of the included courses' units (`sumUnits`).
|
|
||||||
|
|
||||||
### Description lookup
|
|
||||||
- For each unique course code, POST to
|
|
||||||
`https://ismis.usc.edu.ph/CourseSchedule/CourseScheduleOffered` with the
|
|
||||||
`__RequestVerificationToken` and selected `AcademicPeriod`/`AcademicYear`, then
|
|
||||||
parse the returned table for the **Course Description**.
|
|
||||||
- If lookup fails for a course, the description is left blank and generation
|
|
||||||
continues.
|
|
||||||
|
|
||||||
### PDF generation
|
|
||||||
- `pdf.js` (`buildStudyLoadPdf`, using **pdf-lib**) builds a Letter-size (612×792pt)
|
|
||||||
PDF replicating the official layout: title block (STUDENT'S STUDY LOAD, semester,
|
|
||||||
date range, date enrolled), student info row (ID, name, program, year level), the
|
|
||||||
course table (GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE — NOTE is
|
|
||||||
the status), and a footer (total units, "Printed by: …"). Multi-page
|
|
||||||
pagination re-draws the table header. NOTE column is wide enough for `REGULAR`.
|
|
||||||
- The content script sends the data to the background (`generate-pdf`), which
|
|
||||||
downloads the PDF via the `downloads` API (`saveAs: true`).
|
|
||||||
|
|
||||||
## Data flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Study Load page ──▶ content script (scrape + filter DISSOLVED + description lookup)
|
|
||||||
│
|
|
||||||
▼ browser.runtime.sendMessage({ type: 'generate-pdf', data })
|
|
||||||
background.js (pdf-lib buildStudyLoadPdf)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
browser.downloads.download → Study Load PDF file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error handling
|
|
||||||
|
|
||||||
- Master toggle off → no extension behavior on the portal.
|
|
||||||
- No enabled profile → no auto-login.
|
|
||||||
- Description lookup fails for a course → blank description, PDF still generated.
|
|
||||||
- No enrolled (non-DISSOLVED) courses → error toast, no download.
|
|
||||||
- Download fails → error toast; object URL is revoked.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Node `node:test` suites: parsers (table/profile/offered-courses/filter/sum) and
|
|
||||||
pdf (valid Letter PDF output).
|
|
||||||
- Load via `about:debugging` → "Load Temporary Add-on".
|
|
||||||
- Verify auto-login on the login page.
|
|
||||||
- Verify privacy modal auto-accept.
|
|
||||||
- Verify profile add/edit/delete/toggle and master toggle.
|
|
||||||
- Verify the on-page Download PDF button: appears inline with the total-units
|
|
||||||
footer, excludes DISSOLVED courses, and downloads a Letter-size PDF.
|
|
||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "Firefox extension for the USC ISMIS portal",
|
"description": "Firefox extension for the USC ISMIS portal",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test"
|
"test": "node --test",
|
||||||
|
"build": "node scripts/build.js all"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"pdf-lib": "^1.17.1"
|
"pdf-lib": "^1.17.1"
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// Cross-browser API namespace (see background.js). No-op in Firefox.
|
||||||
|
if (typeof browser === 'undefined' || browser == null) {
|
||||||
|
var browser = (typeof chrome !== 'undefined' ? chrome : {});
|
||||||
|
}
|
||||||
|
|
||||||
const $ = (sel) => document.querySelector(sel);
|
const $ = (sel) => document.querySelector(sel);
|
||||||
|
|
||||||
const MASTER_KEY = 'enabled';
|
const MASTER_KEY = 'enabled';
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
'use strict';
|
||||||
|
// Zero-dependency build script: packages the extension for Firefox and Chrome.
|
||||||
|
// Usage: node scripts/build.js [firefox|chrome|all] [--version X.Y.Z]
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const DIST = path.join(ROOT, 'dist');
|
||||||
|
const FF_GECKO_ID = process.env.FF_GECKO_ID || 'ismis-helper@adrianbonpin';
|
||||||
|
|
||||||
|
// Order matters for the single-file Chrome service worker:
|
||||||
|
// fontkit -> pdf-lib -> pdf.js -> background.js
|
||||||
|
const CHROME_BG_PARTS = [
|
||||||
|
'libs/fontkit.min.js',
|
||||||
|
'libs/pdf-lib.min.js',
|
||||||
|
'pdf.js',
|
||||||
|
'background.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Everything to copy from ROOT into a target, relative paths.
|
||||||
|
function collectFiles(srcDir, out = [], prefix = '') {
|
||||||
|
for (const entry of fs.readdirSync(srcDir)) {
|
||||||
|
const abs = path.join(srcDir, entry);
|
||||||
|
const rel = prefix ? prefix + '/' + entry : entry;
|
||||||
|
if (entry === '.git' || entry === 'node_modules' || entry === 'dist' ||
|
||||||
|
entry === 'docs' || entry === 'scripts' || entry === 'test' ||
|
||||||
|
entry === 'manifest.json' || entry.startsWith('.')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fs.statSync(abs).isDirectory()) collectFiles(abs, out, rel);
|
||||||
|
else out.push(rel);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyDir(relList, destDir) {
|
||||||
|
for (const rel of relList) {
|
||||||
|
const src = path.join(ROOT, rel);
|
||||||
|
const dst = path.join(destDir, rel);
|
||||||
|
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
||||||
|
fs.copyFileSync(src, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readManifest() {
|
||||||
|
return JSON.parse(fs.readFileSync(path.join(ROOT, 'manifest.json'), 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- STORED zip writer (no compression, valid for both browsers) ----
|
||||||
|
function crc32(buf) {
|
||||||
|
let table = [];
|
||||||
|
for (let n = 0; n < 256; n++) {
|
||||||
|
let c = n;
|
||||||
|
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||||
|
table[n] = c >>> 0;
|
||||||
|
}
|
||||||
|
let crc = 0xffffffff;
|
||||||
|
for (let i = 0; i < buf.length; i++) crc = table[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
||||||
|
return (crc ^ 0xffffffff) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeStoredZip(files, baseDir, zipPath) {
|
||||||
|
const chunks = [];
|
||||||
|
const central = [];
|
||||||
|
let offset = 0;
|
||||||
|
for (const rel of files) {
|
||||||
|
const data = fs.readFileSync(path.join(baseDir, rel));
|
||||||
|
const name = Buffer.from(rel, 'utf8');
|
||||||
|
const crc = crc32(data);
|
||||||
|
const lh = Buffer.alloc(30);
|
||||||
|
lh.writeUInt32LE(0x04034b50, 0);
|
||||||
|
lh.writeUInt16LE(20, 4);
|
||||||
|
lh.writeUInt16LE(0, 6);
|
||||||
|
lh.writeUInt16LE(0, 8); // method: stored
|
||||||
|
lh.writeUInt16LE(0, 10);
|
||||||
|
lh.writeUInt16LE(0, 12);
|
||||||
|
lh.writeUInt32LE(crc, 14);
|
||||||
|
lh.writeUInt32LE(data.length, 18);
|
||||||
|
lh.writeUInt32LE(data.length, 22);
|
||||||
|
lh.writeUInt16LE(name.length, 26);
|
||||||
|
lh.writeUInt16LE(0, 28);
|
||||||
|
chunks.push(lh, name, data);
|
||||||
|
const ch = Buffer.alloc(46);
|
||||||
|
ch.writeUInt32LE(0x02014b50, 0);
|
||||||
|
ch.writeUInt16LE(20, 4);
|
||||||
|
ch.writeUInt16LE(20, 6);
|
||||||
|
ch.writeUInt16LE(0, 8);
|
||||||
|
ch.writeUInt16LE(0, 10);
|
||||||
|
ch.writeUInt16LE(0, 12);
|
||||||
|
ch.writeUInt32LE(crc, 16);
|
||||||
|
ch.writeUInt32LE(data.length, 20);
|
||||||
|
ch.writeUInt32LE(data.length, 24);
|
||||||
|
ch.writeUInt16LE(name.length, 28);
|
||||||
|
ch.writeUInt16LE(0, 30);
|
||||||
|
ch.writeUInt16LE(0, 32);
|
||||||
|
ch.writeUInt16LE(0, 34);
|
||||||
|
ch.writeUInt16LE(0, 36);
|
||||||
|
ch.writeUInt32LE(0, 38);
|
||||||
|
ch.writeUInt32LE(offset, 42);
|
||||||
|
central.push(ch, name);
|
||||||
|
offset += lh.length + name.length + data.length;
|
||||||
|
}
|
||||||
|
const cdStart = offset;
|
||||||
|
let cdSize = 0;
|
||||||
|
central.forEach((b) => { cdSize += b.length; });
|
||||||
|
const eocd = Buffer.alloc(22);
|
||||||
|
eocd.writeUInt32LE(0x06054b50, 0);
|
||||||
|
eocd.writeUInt16LE(files.length, 8);
|
||||||
|
eocd.writeUInt16LE(files.length, 10);
|
||||||
|
eocd.writeUInt32LE(cdSize, 12);
|
||||||
|
eocd.writeUInt32LE(cdStart, 16);
|
||||||
|
eocd.writeUInt16LE(0, 20);
|
||||||
|
const all = Buffer.concat([...chunks, ...central, eocd]);
|
||||||
|
fs.writeFileSync(zipPath, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTarget(target, version) {
|
||||||
|
const dir = path.join(DIST, target);
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
|
||||||
|
const relList = collectFiles(ROOT);
|
||||||
|
copyDir(relList, dir);
|
||||||
|
|
||||||
|
if (target === 'firefox') {
|
||||||
|
const m = readManifest();
|
||||||
|
if (version) m.version = version;
|
||||||
|
m.browser_specific_settings = { gecko: { id: FF_GECKO_ID } };
|
||||||
|
fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(m, null, 2) + '\n');
|
||||||
|
} else { // chrome
|
||||||
|
const m = readManifest();
|
||||||
|
if (version) m.version = version;
|
||||||
|
delete m.background;
|
||||||
|
m.background = { service_worker: 'background.chrome.js' };
|
||||||
|
delete m.browser_specific_settings;
|
||||||
|
fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(m, null, 2) + '\n');
|
||||||
|
const worker = CHROME_BG_PARTS
|
||||||
|
.map((p) => fs.readFileSync(path.join(ROOT, p), 'utf8'))
|
||||||
|
.join('\n;\n');
|
||||||
|
fs.writeFileSync(path.join(dir, 'background.chrome.js'), worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffix = version ? '-' + version : '';
|
||||||
|
const zipName = path.join(DIST, `ismis-helper-${target}${suffix}.zip`);
|
||||||
|
writeStoredZip(collectFiles(dir), dir, zipName);
|
||||||
|
console.log(`built ${target}: ${zipName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const rawTargets = args.filter((a) => !a.startsWith('--'));
|
||||||
|
const vi = args.indexOf('--version');
|
||||||
|
let version = null;
|
||||||
|
if (vi !== -1 && args[vi + 1]) version = String(args[vi + 1]).replace(/^v/, '');
|
||||||
|
const requested = rawTargets.length ? rawTargets : ['all'];
|
||||||
|
const targets = requested.includes('all')
|
||||||
|
? ['firefox', 'chrome']
|
||||||
|
: requested.filter((t) => t === 'firefox' || t === 'chrome');
|
||||||
|
if (!targets.length) {
|
||||||
|
console.error('Usage: node scripts/build.js [firefox|chrome|all] [--version X.Y.Z]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
for (const t of targets) buildTarget(t, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Reference in New Issue
Block a user