慣性聚合 関心のあるブログ、ニュース、テクノロジーを効率的に追跡
原文を読む 慣性聚合で開く

おすすめ購読元

G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
V
Visual Studio Blog
Martin Fowler
Martin Fowler
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 叶小钗
I
InfoQ
B
Blog RSS Feed
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
Blog — PlanetScale
Blog — PlanetScale
IT之家
IT之家
P
Proofpoint News Feed
WordPress大学
WordPress大学
小众软件
小众软件
B
Blog
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
量子位
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
190 Countries, Zero API Calls: Shipping Static Data in a ...
SHOTA · 2026-05-23 · via DEV Community

SHOTA

Most Chrome extensions that need data fall into one of two patterns: they call an external API, or they store a small amount of user-specific data locally. EntryCheck does neither. It bundles a static dataset of visa requirements for 190+ passport/destination combinations directly into the extension and resolves every lookup client-side with zero network requests.

This tradeoff — large bundle, instant lookups, no API dependency — turns out to be the right call for travel data. Here's why, and how it works.

Why Static Over API

Visa requirements don't change often. A country might update its visa-on-arrival list two or three times a year. An API would add latency, require authentication, and create a failure mode (network unavailable, API down, rate-limited) in a context where the user is typically trying to quickly check something before booking a flight.

More practically: there's no reliable free public API for visa requirements. The data sources are government websites and reference databases. Scraping or licensing these for a real-time API isn't worth it for a tool whose value is speed and simplicity.

A local JSON file loaded at extension startup sidesteps all of this.

Data Structure

The core dataset is a JSON object keyed by two-letter ISO passport code, then by two-letter destination code:

type VisaStatus =
  | 'visa_free'
  | 'visa_on_arrival'
  | 'e_visa'
  | 'visa_required'
  | 'not_admitted';

interface EntryRequirement {
  status: VisaStatus;
  maxStay?: number;        // days, undefined if no limit
  notes?: string;
}

type VisaMatrix = Record<string, Record<string, EntryRequirement>>;

Enter fullscreen mode Exit fullscreen mode

A lookup is just two array accesses:

function lookup(matrix: VisaMatrix, passport: string, destination: string): EntryRequirement | null {
  return matrix[passport]?.[destination] ?? null;
}

Enter fullscreen mode Exit fullscreen mode

The matrix itself compresses well: visa_free and visa_required cover the majority of combinations, so the JSON has a lot of repeated structure. Gzipped, the full dataset is under 30KB.

Bundling with WXT

The matrix lives in public/visa-matrix.json. WXT (the extension framework) copies the public/ directory to the output root verbatim. The background service worker loads it once on install and caches the result:

let cachedMatrix: VisaMatrix | null = null;

async function getMatrix(): Promise<VisaMatrix> {
  if (cachedMatrix) return cachedMatrix;
  const url = chrome.runtime.getURL('visa-matrix.json');
  const resp = await fetch(url);
  cachedMatrix = await resp.json();
  return cachedMatrix;
}

Enter fullscreen mode Exit fullscreen mode

chrome.runtime.getURL converts the relative path to the extension's internal chrome-extension:// URL. This is the standard pattern for accessing bundled assets from a service worker — it works in MV3 without any special permissions.

Content Script Injection on Google Flights

The lookup popup works fine on its own, but the more useful feature is automatic injection on Google Flights. When a user searches for a flight and the page shows a destination, EntryCheck's content script detects the destination, looks up the requirements for the user's saved passport, and injects a badge next to the search results.

The content script reads the current destination from the URL parameters and page DOM, calls the background for a lookup via chrome.runtime.sendMessage, and renders a small badge component inline.

chrome.runtime.sendMessage(
  { type: 'VISA_LOOKUP', passport: savedPassport, destination: detected },
  (response: EntryRequirement | null) => {
    if (response) renderBadge(response);
  }
);

Enter fullscreen mode Exit fullscreen mode

The background receives this, calls getMatrix(), and returns the result. Because the matrix is in memory after the first load, the response is synchronous from the content script's perspective.

The Maintenance Problem

Static data has one obvious downside: it goes stale. My current approach is to update the JSON file with each extension version bump and push it as a normal CWS update. This is manual but tractable — visa requirements change infrequently enough that quarterly checks cover 95% of changes.

For something that updates more frequently (exchange rates, business hours), this model breaks down and an API makes more sense. Visa requirements are the rare case where static actually wins.


🔗 EntryCheck on Chrome Web Store: Install