AI 资讯
What a Browser Extension's Test Suite Cannot Reach
Longshot is a Firefox screenshot extension I wrote to replace FireShot: full page, visible area, drag region and element capture, an editor with eleven annotation tools, export to PNG, JPEG, WebP and PDF, and local OCR that produces a searchable text layer. It has no runtime dependencies. The code is not public, so this is a description rather than an invitation to read it. At one point it had 130 passing Node assertions across six suites, zero failing. Printing could not open a dialog at all. Not "printed the wrong thing". The print command hung indefinitely and no dialog ever appeared. The suites did not go amber, or flake, or report a warning. They reported 130 passed, 0 failed, which is what they had reported the day before and what they would have gone on reporting. Why nothing caught it printCanvas encoded each slice of the image to a blob URL and awaited img.decode() . That call does not resolve for an image inside a display:none subtree, and the print stylesheet creates exactly such a subtree by design, since the container has to be hidden on screen. So the await never returned, and the dialog never opened. Every part of that failure is a meeting point between my code and the browser: the decode promise's behaviour, the stylesheet's effect on the subtree, and the ordering between them. None of it is reachable by a function you can call from Node. The six suites test band arithmetic, canvas dimension limits, filename sanitising, the background module graph under stubbed extension APIs, PDF structure and scan geometry. All of that is worth testing and none of it goes near a real DOM. The second bug in the same batch has the same shape one level in. Choosing PDF broke "Open in editor", because deliver() handed the editor the PDF blob and createImageBitmap cannot decode one. That is not a browser boundary; it is one internal stage handing another something it cannot accept. Both stages were tested; the seam between them was not. That is the pattern worth naming.
AI 资讯
Full-Stack Architecture Patterns That Actually Survive Production
Every full-stack tutorial ends the same way: a working app, a happy demo, and zero mention of what happens six months later when your "simple" CRUD app has 40 endpoints, three types of caching, and a frontend team that's afraid to touch the API layer. This post isn't about picking a framework. It's about the architectural decisions that quietly determine whether your app is pleasant to work on in year two — or a slow-motion disaster. 1. Stop treating your API layer as an afterthought A huge number of full-stack apps start with the frontend calling the backend directly, endpoint by endpoint, with no shared contract. It works fine at 5 endpoints. At 50, nobody remembers which fields are optional, which ones changed last sprint, or why the mobile app is still sending the old shape. Two things fix this early: A single source of truth for your API contract. Whether that's OpenAPI, GraphQL SDL, or even just shared TypeScript types in a monorepo package, the goal is the same: one place where "what does this endpoint return" is answered definitively. Generated clients over hand-written fetch calls. If you're writing fetch('/api/users/' + id) by hand in more than one place, you've already created a maintenance liability. Tools like openapi-typescript-codegen or a tRPC setup remove an entire category of bugs. // Instead of this scattered everywhere: const res = await fetch ( `/api/users/ ${ id } ` ); const user = await res . json (); // type: any, hope for the best // This, generated from your contract: const user = await api . users . getById ( id ); // fully typed, autocomplete works 2. Decide where your business logic lives — before you have 30 files that disagree The classic failure mode: business logic scattered across route handlers, database triggers, frontend validation, and a couple of "utils" files nobody wants to open. Every rule ends up implemented two or three times, slightly differently. Pick one layer to own the rules. A common, boring, effective pattern: Contr
AI 资讯
Our regex found 199 records in a 1,723-record corpus and reported no errors
We maintain a corpus of 456 role-specific resume examples in TypeScript. Someone asked me what a good bullet point actually looks like, and rather than answer from taste I decided to measure the thing I already had. Fifteen minutes later we had a script, a set of numbers, and a conclusion. The conclusion was wrong, because the script had silently read about twelve percent of the data. This is a post about that failure mode, and then about the numbers I got once the script worked. The corpus Thirty-one TypeScript files, each exporting an array of role objects. One role looks roughly like this: { slug : ' cloud-architect ' , title : ' Cloud Architect Resume ' , category : ' Information Technology ' , sampleData : { summary : ' ... ' , experiences : [ { company : ' Amazon Web Services ' , position : ' Senior Cloud Architect ' , description : ' - Designed multi-region architecture... \n - Led migration of... ' , }, ], skills : [...], }, tips : [...], } The interesting field is description . It holds a newline-delimited list of bullets as a single string, so the whole corpus of bullets is sitting there in source, greppable, without a database or an export step. Version one const descs = [... text . matchAll ( /description: ' ((?:[^ ' \\] | \\ . ) * ) '/g )]. map ( m => m [ 1 ]); Nothing exotic. Match description: , then a single-quoted string, allowing escapes so an apostrophe inside the text does not terminate the match early. It found 199 description strings. I did not question that, because I had no prior for what the number should be. 199 sounded like a lot of text. We computed medians off it, looked at the opener distribution, and started writing. The number that saved me was on a different line of the same output: roles 456 . The slug count was fine. So 456 roles between them had 199 job descriptions, which would mean the overwhelming majority of roles had no work history at all. I knew that was false, because I had rendered these pages. Why it read twelve percent
AI 资讯
Closure in javascript
Closures in JavaScript Closures are one of the most important concepts in JavaScript. They can look confusing at first because they involve functions, lexical scope, and lexical environments together. But once we understand how these concepts are connected, closures become much easier to understand. A simple definition of closure is: A closure is a function that remembers and can access variables from its surrounding lexical environment even after the outer function has finished executing. The word "remembers" here doesn't mean that JavaScript literally copies the variables into the function. Instead, the function maintains a connection to the lexical environment in which it was created. Let's understand it with an example Consider the following code: function outer () { let name = " Abimanyu " function inner () { console . log ( name ) } return inner } let myFunction = outer () myFunction () When outer() is called, JavaScript creates a lexical environment for it. That environment contains the variable name : Outer Lexical Environment name → "Abimanyu" The inner() function is created inside outer() , so it has access to that surrounding environment. When outer() returns inner , the function is stored in myFunction . Now outer() has finished executing, but myFunction still refers to inner() . myFunction ↓ inner() ↓ Outer Lexical Environment ↓ name → "Abimanyu" When we call: myFunction () inner() needs the value of name . Since name is not inside its own environment, JavaScript looks through its surrounding environment and finds name in the environment created by outer() . This is the important part of a closure: the function retains access to the environment where it was created, even though the outer function has already finished executing. Why doesn't name disappear? This is where closures are often misunderstood. You might think that once outer() finishes, everything created inside it should disappear. But inner() still has a reference to the environment containin
AI 资讯
My restored Cypress session was lying to me
Author's Note / Disclosure: 100% human-authored content based on real production engineering work. No AI was involved in writing the article, technical analysis, or code. cy.session() is the single biggest speed win available to an authenticated Cypress suite. You log in once, Cypress snapshots cookies, localStorage and sessionStorage , and every later spec restores that snapshot instead of walking through an identity provider. The safety net is validate() . Cypress runs it after restoring a cached session; if it throws, fails an assertion, or yields false , Cypress throws the snapshot away and runs setup again. That is the whole contract: a bad session gets detected and replaced. Mine could not fail. For weeks. And it cost me days of chasing "flaky" specs that were nothing of the kind. The code that looked fine Cypress . Commands . add ( ' login ' , ( user : User ) => { cy . session ( user . username , () => { cy . visit ( ' / ' ) cy . origin ( idpOrigin , { args : user }, ({ username , password }) => { cy . get ( ' #username ' ). type ( username ) cy . get ( ' #password ' ). type ( password , { log : false }) cy . get ( ' button[type="submit"] ' ). click () }) cy . get ( ' #app-shell ' ). should ( ' be.visible ' ) }, { cacheAcrossSpecs : true , validate () { cy . request ( ' /connect/userinfo ' ). its ( ' status ' ). should ( ' eq ' , 200 ) }, }, ) }) Reasonable, right? /connect/userinfo is the OIDC user info endpoint. If the session is dead it should 401, validate() fails, and we log in again. Why it always passes Two independent bugs stack up here, and either one alone is enough to make the check worthless. The URL is relative. cy.request('/connect/userinfo') resolves against baseUrl , which is the application, not the identity provider. So the request never touches the IdP. The application is a single-page app. Its host serves index.html for any path it does not recognise, because that is what history-API routing requires. A request for /connect/userinfo gets b
开源项目
🔥 alyssaxuu / screenity - The free and privacy-friendly screen recorder with no limits
GitHub热门项目 | The free and privacy-friendly screen recorder with no limits 🎥 | Stars: 18,664 | 86 stars this week | 语言: JavaScript
开源项目
🔥 mekos2772 / ios-location-spoofer - Standalone iOS app to spoof GPS location without jailbreak.
GitHub热门项目 | Standalone iOS app to spoof GPS location without jailbreak. Includes Shadowrocket/Surge/Loon/QX/Stash module. | Stars: 3,856 | 20 stars today | 语言: JavaScript
AI 资讯
I built a 16-bit RPG inside Jira, and Forge took away my server
I could not make myself log time in Jira. Not because it is hard. Because nothing happens afterwards. You type a number into a box, the box says nothing back, and by Thursday the habit is gone again. Every tool I tried fixed this by adding another box. So I built the missing half instead. Feed The Troll gives everyone on a team a pixel-art troll that gains XP from the work they already do in Jira, and turns sprint results into a village the whole project shares. It is on the Atlassian Marketplace now. This post skips the game itself. It is about five problems that turned out to be hard in ways I did not expect, each one a consequence of building the thing on Atlassian Forge, alone. What Forge gives you, and what it takes back Forge runs your code on Atlassian's infrastructure. There is no server of mine anywhere in the picture. That is the line on the listing page, and it was the single fact that shaped every decision underneath it. You get a Node 22 runtime, Forge SQL (TiDB under the hood) for storage, and Custom UI modules that reach the backend through @forge/bridge . You give up a backend you control, a cache you can reach, and outbound HTTP to anything you did not declare. The one that keeps mattering: any way to open the database at three in the morning and fix a single row by hand. The whole app declares six scopes. None of them are write scopes: read:board-scope:jira-software read:issue-details:jira read:jira-work read:jira-user read:sprint:jira-software storage:app That last line is the entire persistence layer. Twenty-one tables live behind it now, but only ten shipped with v1.0: trolls, XP events, daily activity, kudos, quests, inventory, team quests, villages, raids, project settings. Every table added since arrived the only way the platform makes comfortable, as a new migration appended to the list, never an edit to one already deployed. migrationRunner . enqueue ( ' v001_create_trolls ' , CREATE_TROLLS_TABLE ) // ... . enqueue ( ' v012_create_product_m
AI 资讯
vlt 1.0 Ships as a Drop-in npm Replacement with Phased Installs, Graph Queries, and Malware-Blocking
vlt, created by the original npm team, has launched version 1.0 as a drop-in replacement for npm. It features phased installations to prevent automatic script execution, a queryable dependency graph with over 60 selectors, and hosted registries that block malicious packages. The tool aims to enhance security and streamline the JavaScript development process. By Daniel Curtis
AI 资讯
This is how I added an in-browser auto captions feature to my YouTube Shorts converter web application using Whisper AI and ffmpeg.wasm
A few weeks ago I launched Convert to Shorts — a free browser-based tool that converts horizontal videos to YouTube Shorts format (9:16) without uploading anything to a server. I wrote about the ffmpeg.wasm + Vite setup in a previous article. The most requested feature after launch was auto captions. Captions significantly boost Shorts engagement since most people watch without sound, and manually typing captions is tedious. The challenge: how do you add free auto captions to a privacy-first tool that never uploads your video to a server? The answer: run Whisper AI in the browser. The stack - Transformers.js ( @xenova/transformers ) — Hugging Face's JavaScript port of the Transformers library, runs ONNX models in the browser via WebAssembly Whisper tiny — OpenAI's speech recognition model, 75MB, surprisingly accurate for clear speech Web Audio API — for extracting and resampling audio from the video file ffmpeg.wasm — for burning captions into the video ASS subtitles — the subtitle format libass (inside ffmpeg.wasm) understands. Step 1: Audio extraction Whisper expects mono 16kHz audio as a Float32Array. The Web Audio API handles this cleanly: async function extractAudio ( file : File , trimStart : number , trimEnd : number ): Promise < Float32Array > { const arrayBuffer = await file . arrayBuffer (); const audioContext = new AudioContext ({ sampleRate : 16000 }); const audioBuffer = await audioContext . decodeAudioData ( arrayBuffer ); const sampleRate = audioContext . sampleRate ; const startSample = Math . floor ( trimStart * sampleRate ); const endSample = Math . floor ( trimEnd * sampleRate ); // Mix down to mono, slice to trim range const channelData = audioBuffer . getChannelData ( 0 ); const trimmed = channelData . slice ( startSample , endSample ); await audioContext . close (); return trimmed ; } Creating the AudioContext at 16kHz means the browser automatically resamples from whatever the source rate is (usually 44.1kHz or 48kHz). No manual resampling nee
AI 资讯
Why I Prefer TypeScript Over JavaScript for Larger Projects
JavaScript is flexible, fast to start with, and supported everywhere on the web. For small scripts, quick experiments, and simple browser utilities, plain JavaScript is often enough. But as projects become larger, TypeScript starts to solve problems that JavaScript leaves entirely up to the developer. That is why I increasingly prefer TypeScript for anything beyond a very small project. The biggest difference is type safety JavaScript lets variables change type freely. For example: let khg5293UserId = 5293; khg5293UserId = "5293"; That is valid JavaScript. Sometimes this flexibility is convenient, but it also makes it easier for unexpected values to move through an application. TypeScript lets you define what a value is supposed to be: let khg5293UserId: number = 5293; Now assigning a string to khg5293UserId produces an error during development. That means certain mistakes are caught before the code ever runs. For small khg5293 experiments, this may not matter much. For a larger application with many files and components, it becomes much more valuable. Functions become easier to understand Consider a JavaScript function: function getProjectName(project) { return project.name; } There is nothing here telling us what project is supposed to contain. With TypeScript, the expected structure can be defined directly: type Khg5293Project = { name: string; language: string; public: boolean; }; function getProjectName(project: Khg5293Project): string { return project.name; } Now the function documents itself. A developer immediately knows what kind of object should be passed into it and what the function returns. This becomes especially useful when returning to a project after several weeks or working across a larger codebase. Interfaces make data structures clearer TypeScript also makes application data easier to reason about. For example: interface Khg5293Profile { username: string; projectCount: number; active: boolean; } const khg5293Profile: Khg5293Profile = { username:
AI 资讯
What’s the Fastest React Data Grid? Let’s Find Out (Benchmarks)
Web development has changed a lot. We have LLMs, AI-assisted coding, etc. But some things remain...
AI 资讯
Bulk URL Checker – Batch HTTP Status & Redirect Tracking for 100 URLs, SSRF-Protected
## Why I built this Checking URLs one at a time during a site migration or relaunch is tedious, and the tools that do it in bulk for free — Ahrefs, SEMrush, Screaming Frog — gate that behind a paid plan. So I built Bulk URL Checker for ForgePlug : a free batch URL checker that handles up to 100 URLs per run, no account required. What it does Check status codes, full redirect chains, and response latency for up to 100 URLs at once Three ways to feed it URLs: paste directly, upload a CSV (auto-detects the URL column), or parse a sitemap Follows up to 20 redirect hops, recording the status code and Location header at each step Streams results in real time as each URL finishes, instead of making you wait for the whole batch Export as a formatted text report or properly-escaped CSV Built with SSRF protection from the ground up Since it fetches arbitrary URLs server-side, every redirect destination is validated against private IP ranges (10.x.x.x, 192.168.x.x, 169.254.169.254) before it's followed — so it can't be tricked into hitting internal infrastructure. No URLs are stored; everything lives only for the active session. Details Runs server-side (Node.js) with a concurrency pool of 10 simultaneous requests. Free tier caps at 100 URLs per run — a commercial plan is planned for unlimited batches, scheduled re-checks, and branded reporting. Try it: https://www.forgeplug.com/tools/bulk-url-checker Would love feedback, especially from anyone running site migrations or link audits.
AI 资讯
I Replaced a $40/mo PDF API with 200 Lines of Web Worker Code — Here's the Offline Invoice Tool I Built
The bill that started this I was paying $40/month for a PDF generation API to power a tiny internal invoicing tool for a client project. Forty bucks a month to convert some JSON into a PDF. That's it. That's the whole service. I finally sat down on a Saturday to see if I could kill that subscription. Three weekends later, not only did I kill it — the replacement is faster than the API ever was, because there's no network round-trip at all. This post is the log of how it went, in the order I actually hit the problems, not the order that makes me look competent. Attempt #1: jsPDF on the main thread (it worked, until it didn't) First pass was the obvious one — jsPDF running directly in the click handler: function generateInvoice ( data ) { const doc = new jsPDF (); doc . text ( data . clientName , 20 , 20 ); data . lineItems . forEach (( item , i ) => { doc . text ( ` ${ item . description } — $ ${ item . amount } ` , 20 , 40 + i * 10 ); }); doc . save ( ' invoice.pdf ' ); } Fine for a 3-line invoice. Once I tested with a 40-line-item invoice (a real client sent me one to test against), the tab froze for almost two full seconds. Not crashed — frozen. Scroll didn't work, buttons didn't respond, and on a mid-range Android phone it was closer to five seconds. The main thread doing synchronous PDF math while also being responsible for painting the UI is exactly the kind of thing that looks fine in a demo and falls apart the moment a real user pastes in real data. Attempt #2: move it to a Web Worker Web Workers get talked about like they're this exotic tool for WASM and video processing. They're also just... a really good fit for "expensive synchronous work that a user is waiting on." I'd never reached for one before this project, mostly out of habit. The tricky part isn't the worker itself, it's that jsPDF assumes it has access to document and window in a couple of code paths (font metrics, mostly), which don't exist inside a worker. I ended up switching to pdfkit compiled
AI 资讯
From Contract Boundary to Error Boundary: Structuring API Error Handling in a TypeScript Frontend
In a previous post , I covered why TypeScript types alone can't protect you from a backend that returns something you didn't expect, and how to build a small apiRequest boundary that validates both the outgoing request and the incoming response against Zod-style schemas before your application ever touches the data. That post answered one question: Is this data actually shaped the way I think it is? It left another question open: When the answer is no, or when the request fails for a completely different reason (like a timeout or a dropped connection), what does the rest of the app do with that failure? In practice, "the rest of the app" usually does something different depending on who's writing it: One component checks error.response?.status directly. Another checks error.code === "ECONNABORTED" . A form manually digs through the error to find field-level messages. A toast just displays whatever string happens to be on error.message . The app works, but every layer speaks a different error dialect. This post is Part 2: it takes the validation boundary from Part 1 and builds the missing piece on top of it, a single, normalized ApiError shape that every layer of the app can speak, plus the logging, messaging, and form-mapping that make it actually usable. Quick Recap: The Validation Boundary From Part 1, the apiRequest wrapper validates request payloads and response bodies against schemas, and throws one of two typed errors when something doesn't match the contract: export class ApiRequestValidationError extends Error { constructor ( public readonly url : string , public override readonly cause : unknown ) { super ( `API request input does not match the contract for ${ url } .` ); this . name = " ApiRequestValidationError " ; } } export class ApiResponseValidationError extends Error { constructor ( public readonly url : string , public override readonly cause : unknown ) { super ( `API response does not match the contract for ${ url } .` ); this . name = " ApiRespon
AI 资讯
Implementing AI Streaming Responses with JSON Lines Chunked Communication Instead of SSE
Background When streaming AI chat responses, Server-Sent Events (SSE) are commonly used. They are also adopted by APIs from OpenAI and Anthropic, as well as by MCP server responses. In fact, I implemented several AI chat projects that modified responses from AI platforms while streaming them to the browser. In doing so, I encountered an issue where SSE did not work because of certain intermediary proxies and load balancers, such as AWS App Runner. After taking a closer look at the SSE specification, I no longer felt that using SSE was right when the purpose was not actually event notification. The API's block data itself is JSON. This is also the same format as the structured logging sent to services such as CloudWatch Logs today (I had already been working on structuring application logs as JSON). Moving from SSE to JSON Lines Chunked Communication What I came up with was a combination of Transfer-Encoding: chunked and Content-Type: application/jsonl (which is not defined by the IAEA). With this approach, even if a proxy or load balancer buffers the response and returns it as a single body rather than chunks, only streaming is lost; the final complete data remains unchanged. Because it is JSON Lines (NDJSON), all you need to do is split on line feeds (LF) and JSON-parse each line. It is also easy to inspect in browser developer tools. However, implementing this from scratch every time is a bit of work, so I implemented and published jsonl-webstream , an npm library of stream utilities for browsers and servers (Node.js). The library has zero dependencies . tilfin / jsonl-webstream Lightweight library for JSON Lines web stream between browsers and Node.js environments jsonl-webstream Lightweight library for JSON Lines web stream between browsers and Node.js environments Overview This library provides utilities for processing JSON Lines formatted data through the Web Streams API It enables efficient streaming of JSON Lines data with minimal memory overhead across brow
AI 资讯
I built 59 free browser-based dev tools in vanilla JS — here's what I learned
I've been quietly building Antigravity Tools — a collection of 59 free, browser-based developer utilities — and today I'm sharing everything I built and learned. Why vanilla JS? No React, no build step. The main constraint I set for myself: zero dependencies, zero server, zero telemetry . When you paste your JWT token into jwt.io, it goes to their server. When you use an online regex tester, your test strings are logged. I built Antigravity Tools so every operation runs inside your browser, using native APIs. No Node.js backend No npm packages No webpack/vite/parcel No Google Analytics No cookies Everything runs on Web Crypto API , Canvas API , Web Audio API , and IndexedDB — all native to modern browsers. The 8 tool categories 🔐 Security & Auth Tools JWT Inspector — decode JWT header, payload, and check expiry locally RSA & ECC Key Generator — generate 2048-bit key pairs via SubtleCrypto Hash & Password Generator — SHA-256/SHA-512 via Web Crypto PII Masker — strip emails, credit cards, SSNs, IPs from text Universal Encoder/Decoder — Base64, URL, Hex, HTML entities, Unicode 🤖 AI & Prompting Tools AI Token Counter — estimate cost across GPT-4o, Claude 3.5, Gemini 2.0, DeepSeek R1 System Prompt Builder — structure agent instructions with XML tags and tool definitions AI Text Humanizer — rephrase robotic AI output into natural writing Prompt Cost Trimmer — compress prompts by 30–50% to reduce API costs ⚡ Dev & Code Tools JSON Workbench — beautify, validate, convert to TypeScript, Python, Go types cURL Converter — cURL → JS fetch, Python requests, Go, PHP Regex Tester — real-time match highlighting with capture group display Cron Builder — visual cron expression editor with plain-English output Git Command Helper — build undo/squash/cherry-pick commands visually Try it 👉 https://antigravitytools.app
开发者
I Built a Version Bump Tool in Rust That Is 10,000x Faster Than Its Python Counterparts.
Hello, fellow version-bumping enthusiasts, sleep-deprived Rustaceans, and accidental software...
AI 资讯
How to convert a folder of PNGs to one PDF without uploading the files
A simple browser-local PNG-to-PDF workflow For this kind of job, the useful workflow is straightforward: Select the PNG, JPG, or JPEG files. Put the pages in the order they should appear. Choose a page size and margins if the document needs them. Export one PDF. The important detail is where the conversion happens. A browser-local PNG-to-PDF tool processes the images in the browser instead of uploading them to a conversion server. That makes it easier to keep control of source files while still producing one shareable PDF. When this is useful This workflow is handy for: combining screenshots into a bug report or handoff document; turning scanned pages into one file for email or printing; arranging portfolio images or design exports in a deliberate order; and collecting receipts or reference images without making a separate document first. Before exporting, check the page order and decide whether each page should match the image, A4, or US Letter. A preview is useful here: it catches a stray portrait page, an oversized margin, or a screenshot in the wrong position before the PDF is created. The tool I use for this I maintain PNG Binder , a free PNG-to-PDF converter for this specific workflow. It accepts up to 50 PNG, JPG, or JPEG images, lets you arrange them, and creates one PDF locally in the browser. It does not require an account, and the images are not sent to a conversion server. It creates an image-based PDF, so it does not perform OCR or rebuild text and tables. If that is the kind of result you need, try it and let me know whether page ordering, page settings, or browser compatibility could be improved. Disclosure: I am the maker and operator of PNG Binder.
AI 资讯
Validate Card Brands in Node.js with Luhn and credit-card-brand-detector
When a checkout form receives a card number, the first useful question is often not whether the payment will be approved. It is whether the input is structurally plausible and which network rules should be shown to the user. The open-source credit-card-brand-detector package provides that small client-side or server-side building block. It detects 11 brands, removes spaces and hyphens, and applies a Luhn checksum. It has zero runtime dependencies and exposes CommonJS functions for validation and brand detection. This tutorial builds a minimal Node.js check, verifies the result with known test numbers, and explains what this kind of validation cannot tell you. TL;DR Install version 1.0.1 , call validateCreditCard when you need both a boolean result and a brand, and call detectBrand when you only need the network name. The package does not contact a payment processor, authorize a transaction, tokenize data, or prove that a card exists. Prerequisites You need: Node.js 12 or newer. The package declares >=12.0.0 in its metadata. npm. A terminal and a small JavaScript file. The package is released under the MIT license . The examples below target the published npm package version 1.0.1 , which is also the version I installed for this walkthrough. Install the package Create a directory for the example and install the pinned version: mkdir card-check-example cd card-check-example npm init -y npm install credit-card-brand-detector@1.0.1 Pinning the version makes the example reproducible. If you use a different version later, check its README and package metadata before copying the behavior into a production application. Build the smallest useful check Create check-card.js : const { validateCreditCard , detectBrand , getBrand , } = require ( ' credit-card-brand-detector ' ); const formattedVisa = ' 4532 0151-1283-0366 ' ; const mastercard = ' 5555555555554444 ' ; console . log ( validateCreditCard ( formattedVisa )); console . log ( detectBrand ( mastercard )); console . log