IRCC Security Checker

Check IRCC application security status from tracker data, show all history with explanations

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @tampermonkey-safari-promotion-code-request c9263fb7-83eb-4bf0-9703-778d05b1b33e
// @license      MIT
// @name         IRCC Security Checker
// @namespace    http://tampermonkey.net/
// @version      1.7.0
// @description  Check IRCC application security status from tracker data, show all history with explanations
// @author       WeChat ID: RoaldAmundsen (https://security-checker.raindrop.eu.org for updates)
// @match        https://ircc-tracker-suivi.apps.cic.gc.ca/en/login*
// @match        https://ircc-tracker-suivi.apps.cic.gc.ca/en/overview*
// @match        https://ircc-tracker-suivi.apps.cic.gc.ca/en/applications/details*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// @inject-into  auto
// ==/UserScript==

(function() {
    'use strict';

    // Key meanings based on information collected from web research
    const KEY_CODE_MEANINGS = {
        'security':           { meaning: 'Background/Security process', sign: 'unknown' },
        'medical':            { meaning: 'Medical-related item', sign: 'neutral' },
        'imm1017':            { meaning: 'Medical Instructions (IMM 1017)', sign: 'neutral' },
        'word ltr 02':        { meaning: 'Letter generated/uploaded', sign: 'neutral' },
        'initial':            { meaning: 'Tracker initialized', sign: 'neutral' },
        '57':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '93':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '871': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '86':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '626': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '648': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '953': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '408': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '67':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '9':   { meaning: 'Internal IRCC code', sign: 'unknown' },
        '41':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '622': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '633': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '42':  { meaning: 'Internal IRCC code', sign: 'unknown' },
        '729': { meaning: 'Internal IRCC code', sign: 'unknown' },
        '8':   { meaning: 'Internal IRCC code', sign: 'unknown' },
        '637': { meaning: 'Internal IRCC code', sign: 'unknown' }
    };

    const SIGN_COLOR = {
        positive: '#28a745',
        neutral:  '#6c757d',
        unknown:  '#0d6efd',
        negative: '#dc3545'
    };

    // Capture tracker JSON from network
    (function captureFromNetwork(){
        if (window.__IRCC_NET_CAPTURED__) return;
        window.__IRCC_NET_CAPTURED__ = true;

        const saveIfTracker = (obj) => {
            if (hasHistoryArray(obj)) {
                window.__IRCC_TRACKER_DATA = obj;
                window.dispatchEvent(new CustomEvent('ircc-tracker-data', { detail: { source: 'network' }}));
            }
        };

        const _fetch = window.fetch;
        window.fetch = async function(...args){
            const resp = await _fetch.apply(this, args);
            try {
                const clone = resp.clone();
                const ct = clone.headers.get('content-type') || '';
                if (ct.includes('application/json')) {
                    const json = await clone.json();
                    saveIfTracker(json);
                }
            } catch {}
            return resp;
        };

        const _open = XMLHttpRequest.prototype.open;
        const _send = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(...a){ this.__ircc_url = a[1]; return _open.apply(this,a); };
        XMLHttpRequest.prototype.send = function(...a){
            this.addEventListener('load', function(){
                try {
                    const ct = this.getResponseHeader && this.getResponseHeader('content-type') || '';
                    if (ct.includes('application/json')) {
                        const json = JSON.parse(this.responseText);
                        saveIfTracker(json);
                    }
                } catch {}
            });
            return _send.apply(this,a);
        };
    })();

    function createUI() {
        if (document.getElementById('ircc-security-checker')) return;
        const wrap = document.createElement('div');
        wrap.id = 'ircc-security-checker';
        wrap.innerHTML = `
            <div style="
                position: fixed;
                top: 20px;
                right: 20px;
                z-index: 100000;
                background: #fff;
                border: 2px solid #007bff;
                border-radius: 8px;
                padding: 12px;
                width: 340px;
                box-shadow: 0 6px 18px rgba(0,0,0,0.2);
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, 'Noto Sans', 'Helvetica Neue', sans-serif;
            ">
                <div style="margin-bottom: 8px;">
                    <div style="font-weight: 700; color: #007bff; margin-bottom: 8px;">🔒 IRCC Security Checker</div>
                    <div style="font-size: 12px; color: #495057; margin-bottom: 10px;">
                        Captures tracker data on the overview or details page after login.
                    </div>
                    <button id="irccCheckBtn" style="display:block;width:100%;margin-bottom:8px;background:#007bff;color:#fff;border:none;border-radius:4px;padding:8px;cursor:pointer;">Check Security Status</button>
                    <button id="irccShowBtn" style="display:block;width:100%;margin-bottom:8px;background:#6c757d;color:#fff;border:none;border-radius:4px;padding:8px;cursor:pointer;">Show All Data</button>
                    <button id="irccCopyBtn" style="display:none;width:100%;margin-bottom:8px;background:#17a2b8;color:#fff;border:none;border-radius:4px;padding:8px;cursor:pointer;">Copy Data</button>
                    <button id="irccCloseBtn" style="display:block;width:100%;background:#dc3545;color:#fff;border:none;border-radius:4px;padding:8px;cursor:pointer;">Close</button>
                    <div id="irccStatusHint" style="font-size: 11px; color: #6c757d; margin-top: 6px;">Waiting for tracker data…</div>
                </div>
                <div id="irccResults" style="display:none;">
                    <div id="irccSecurity" style="margin-bottom:8px;"></div>
                    <pre id="irccData" style="display:none;margin:0;max-height:280px;overflow:auto;background:#f8f9fa;border:1px solid #e9ecef;padding:8px;border-radius:4px;font-size:12px;"></pre>
                </div>
            </div>
        `;
        document.body.appendChild(wrap);

        const refs = getUIRefs();
        refs.close.onclick = () => wrap.remove();
        refs.check.onclick = handleCheck;
        refs.show.onclick = handleShowAllToggle;
        refs.copy.onclick = handleCopy;

        const updateHint = () => {
            const ok = hasHistoryArray(window.__IRCC_TRACKER_DATA);
            refs.hint.textContent = ok ? 'Captured tracker data.' : 'Waiting for tracker data…';
        };
        updateHint();
        window.addEventListener('ircc-tracker-data', updateHint);
        document.addEventListener('readystatechange', updateHint);
    }

    function getUIRefs() {
        const box = document.getElementById('ircc-security-checker');
        return {
            box,
            check: box.querySelector('#irccCheckBtn'),
            show: box.querySelector('#irccShowBtn'),
            copy: box.querySelector('#irccCopyBtn'),
            close: box.querySelector('#irccCloseBtn'),
            hint: box.querySelector('#irccStatusHint'),
            results: box.querySelector('#irccResults'),
            security: box.querySelector('#irccSecurity'),
            data: box.querySelector('#irccData')
        };
    }

    function hasHistoryArray(obj) {
        try {
            return obj && obj.relations && Array.isArray(obj.relations) &&
                obj.relations[0] && Array.isArray(obj.relations[0].history);
        } catch { return false; }
    }
    function getTrackerData() { return hasHistoryArray(window.__IRCC_TRACKER_DATA) ? window.__IRCC_TRACKER_DATA : null; }
    function getHistory(obj)   { try { return obj?.relations?.[0]?.history || []; } catch { return []; } }
    function getApp(obj)       { try { return obj?.app || null; } catch { return null; } }

    function findLatestSecurityEntry(history) {
        const isSecurity = (k) => String(k || '').trim().toLowerCase() === 'security';
        const candidates = history
            .filter(h => isSecurity(h.key))
            .map(e => ({ e, t: parseDate(e.dateCreated) || parseDate(e.dateLoaded) || new Date(0) }))
            .sort((a,b) => b.t - a.t);
        return candidates[0]?.e || null;
    }

    function deriveSecurityStatusStrict(tracker) {
        const history = getHistory(tracker);
        const app = getApp(tracker);
        const sec = findLatestSecurityEntry(history);

        if (!sec) {
            return {
                state: 'no_security_key',
                date: parseDate(app?.lastUpdated) || latestDate(history),
                message: 'Congrats! There is no "Security" entry. Your file looks to be moving under the normal background check process.'
            };
        }

        const code = toInt(sec.actStatus);
        if (code === 33) {
            return {
                state: 'security_finished',
                date: parseDate(sec.dateCreated) || parseDate(sec.dateLoaded) || parseDate(app?.lastUpdated),
                message: 'Good news! A "Security" entry exists but is finished. Your application should now continue under the normal process.'
            };
        }
        if (code === 17) {
            return {
                state: 'security_in_progress',
                date: parseDate(sec.dateCreated) || parseDate(sec.dateLoaded) || parseDate(app?.lastUpdated),
                message: 'Heads up: A "Security" entry is present and in progress. This stage can be stressful and sometimes lengthy—stay strong. Hopefully it wraps up soon.'
            };
        }
        return {
            state: 'security_other',
            date: parseDate(sec.dateCreated) || parseDate(sec.dateLoaded) || parseDate(app?.lastUpdated),
            message: 'A "Security" entry is present. We can\'t map its status code precisely, but it likely indicates background processing. Stay positive—progress updates can take time.'
        };
    }

    function handleCheck() {
        const ui = getUIRefs();
        ui.results.style.display = 'block';
        const data = getTrackerData();
        if (!data) {
            ui.security.innerHTML = `
                <div style="border-left:4px solid #6c757d;padding-left:8px;">
                    <div style="font-weight:600;color:#6c757d;">Tracker data not found yet.</div>
                    <div style="color:#6c757d;">Ensure you're logged in on overview or details.</div>
                </div>`;
            return;
        }

        const res = deriveSecurityStatusStrict(data);
        const color = res.state === 'security_in_progress' ? SIGN_COLOR.negative
            : res.state === 'security_finished' ? SIGN_COLOR.positive
            : res.state === 'no_security_key' ? SIGN_COLOR.unknown
            : SIGN_COLOR.neutral;
        const label = res.state === 'security_in_progress' ? 'Security: In Progress'
            : res.state === 'security_finished' ? 'Security: Finished'
            : res.state === 'no_security_key' ? 'No "Security" Entry'
            : 'Security: Other/Unknown';
        const dateStr = res.date ? formatDate(res.date) : 'N/A';

        ui.security.innerHTML = `
            <div style="border-left:4px solid ${color};padding-left:8px;">
                <div style="margin-bottom:4px;"><strong>Status</strong>: <span style="color:${color};font-weight:700;">${label}</span></div>
                <div style="margin-bottom:4px;"><strong>Date</strong>: ${dateStr}</div>
                <div style="color:#495057;">${res.message}</div>
            </div>`;
    }

    function handleShowAllToggle() {
        const ui = getUIRefs();
        ui.results.style.display = 'block';

        if (ui.data.style.display !== 'none' && ui.data.textContent.trim().length > 0) {
            ui.data.style.display = 'none';
            ui.copy.style.display = 'none';
            return;
        }

        const data = getTrackerData();
        if (!data) {
            ui.data.textContent = 'Tracker data not found. Log in and reload the overview page.';
            ui.data.style.display = 'block';
            ui.copy.style.display = 'none';
            return;
        }

        const history = getHistory(data);
        const explained = history.map(h => explainHistoryEntry(h));
        ui.data.textContent = JSON.stringify({ legend: historyLegend(), items: explained }, null, 2);
        ui.data.style.display = 'block';
        ui.copy.style.display = 'block';
    }

    function handleCopy() {
        const ui = getUIRefs();
        const txt = ui.data.textContent || '';
        if (!txt) return;
        copy(txt);
        ui.copy.textContent = 'Copied!';
        setTimeout(() => ui.copy.textContent = 'Copy Data', 900);
    }

    function explainHistoryEntry(h) {
        const label = String(h.key ?? '').trim();
        const labelKey = label.toLowerCase();
        const meaningObj = KEY_CODE_MEANINGS[labelKey] || (isNumeric(label) ? KEY_CODE_MEANINGS[label] : null);

        const sign = meaningObj?.sign || 'unknown';
        const meaning = meaningObj?.meaning || (isNumeric(label) ? 'Internal IRCC code' : null);
        const signNote = sign === 'positive' ? 'good sign'
            : sign === 'neutral'  ? 'routine/neutral'
            : sign === 'negative' ? 'concerning'
            : 'not confirmed';

        return {
            key: label || null,
            key_meaning: meaning,
            key_sign: sign,
            key_sign_note: signNote,
            actStatus: toInt(h.actStatus),
            actStatus_note: actStatusNote(toInt(h.actStatus)),
            actType: toInt(h.actType),
            actType_note: actTypeHint(toInt(h.actType)),
            dateCreated: h.dateCreated ?? null,
            dateCreated_local: h.dateCreated ? formatDate(parseDate(h.dateCreated)) : null,
            dateLoaded: h.dateLoaded ?? null,
            dateLoaded_local: h.dateLoaded ? formatDate(parseDate(h.dateLoaded)) : null
        };
    }

    function historyLegend() {
        return {
            key: 'Event label or internal code.',
            key_meaning: 'Heuristic explanation or known meaning.',
            key_sign: 'positive/neutral/unknown/negative; indicates whether the event is a good sign.',
            actStatus: 'Numeric code if present; for "Security", 33=Finished, 17=In Progress.',
            actType: 'Type/category of the event (varies).',
            dateCreated: 'When IRCC recorded the event.',
            dateLoaded: 'When the tracker loaded/synced it.'
        };
    }

    function latestDate(list) {
        let best = null;
        for (const e of list || []) {
            const d = parseDate(e?.dateCreated) || parseDate(e?.dateLoaded);
            if (d && (!best || d > best)) best = d;
        }
        return best;
    }

    function isNumeric(s) { return /^\d+$/.test(String(s || '')); }

    function actStatusNote(code) {
        if (code == null || isNaN(code)) return null;
        if (code === 33) return 'Finished/Completed';
        if (code === 17) return 'In Progress';
        return 'Other/Unknown';
    }

    function actTypeHint(t) {
        if (t == null || isNaN(t)) return null;
        if (t === 3) return 'Observed in some medical-related entries';
        return null;
    }

    function parseDate(s) {
        if (!s) return null;
        const d = new Date(s);
        return isNaN(d.getTime()) ? null : d;
    }

    function toInt(v) {
        const n = parseInt(v, 10);
        return isNaN(n) ? null : n;
    }

    function formatDate(d) {
        try {
            return new Intl.DateTimeFormat(undefined, {
                year: 'numeric', month: 'short', day: '2-digit',
                hour: '2-digit', minute: '2-digit'
            }).format(d);
        } catch {
            return d?.toISOString?.() || '';
        }
    }

    function copy(text) {
        if (navigator.clipboard?.writeText) {
            navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
        } else {
            fallbackCopy(text);
        }
    }
    function fallbackCopy(text) {
        const ta = document.createElement('textarea');
        ta.value = text;
        ta.style.position = 'fixed';
        ta.style.opacity = '0';
        document.body.appendChild(ta);
        ta.select();
        try { document.execCommand('copy'); } catch {}
        document.body.removeChild(ta);
    }

    function init() { createUI(); }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init, { once: true });
    } else {
        init();
    }
})();