AI 资讯
Why I Built Unlockt: A Local-First Instagram Saved Archiver, Canvas Collage Studio & 9:16 Video Vault
Like many developers, designers, and digital marketers, my Instagram "Saved" collection had turned into a digital graveyard with over 5,000 bookmarked posts, reels, and carousels. The native Instagram web app offers virtually zero productivity tools: ❌ No full-text search across captions or hashtags ❌ No way to extract individual slides from carousel photo dumps ❌ No offline preservation (if a creator archives a post, it disappears forever) ❌ Existing web downloaders ask for account passwords, inject trackers, or bombard you with ads. So I spent the last few months developing Unlockt — a 100% free, MIT open-source, local-first Chromium extension and Node.js Express dashboard. --- ## 🏗️ Architecture & Engineering Highlights Here is how Unlockt is designed under the hood: ┌─────────────────────────────────┐ │ Chromium Extension (MV3) │ ──► Reads Instagram GraphQL via active session └────────────────┬────────────────┘ │ Local REST Sync ▼ ┌─────────────────────────────────┐ │ Express Backend (Port 3000) │ ──► SSRF-Hardened Proxy & HTTP 206 Video Streamer └────────────────┬────────────────┘ │ ┌────────┴────────┐ ▼ ▼ ┌──────────────┐ ┌───────────────────────────┐ │ data/saved. │ │ /thumbnails /videos │ │ json (DB) │ │ (Local High-DPI Storage) │ └──────────────┘ └───────────────────────────┘ 1. Zero-Password Session Scraping Rather than asking users for their credentials or running headless browser instances that trigger Meta account checkpoints, Unlockt operates as a Manifest V3 Chromium extension. It uses the cookies and CSRF tokens already present in your authenticated browser tab with randomized jitter delays (800ms - 2200ms) to respect rate limits. 2. 1-Click HTML5 Canvas Collage Studio One of my favorite features is the Carousel Studio . When you open a 10-slide photo dump, Unlockt extracts every slide and can render them onto an off-screen HTML5 <canvas> element to produce high-resolution moodboards ( 2x1 , 2x2 , 3x2 , 3x3 , and 5x2 ) with crisp 4px white margin div
开源项目
🔥 amElnagdy / delegate-skills - Delegate a coding task to a separate coding agent CLI, revie
GitHub热门项目 | Delegate a coding task to a separate coding agent CLI, review the diff, land the commit yourself — one per implementer. | Stars: 1,112 | 330 stars this week | 语言: JavaScript
开发者
shadcn Brings Conversational Primitives to shadcn/ui with New Chat Components
Shadcn, a design engineer at Vercel, has introduced new components for chat interfaces within the shadcn/ui project. This release includes components like MessageScroller and Message, focusing on conversation functionality. The approach emphasizes modular design, allowing developers to adapt elements without affecting underlying logic or styles. Support for headless components is also provided. By Daniel Curtis
开发者
analogous(-1): how a default hid a heap-exhaustion bug for fifteen years
This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry. ...
AI 资讯
Best Practices for Playwright Locators: Building Flake-Resistant Test Automation
Fragile element locators are one of the primary drivers of test flakiness in UI automation. Relying on auto-generated, deeply nested CSS selectors or long XPath expressions makes your test suite sensitive to minor layout changes, styling refactors, and DOM updates. Adopting a clear locator strategy simplifies maintenance and ensures tests remain reliable as applications evolve. Core Principles for Locator Selection Prioritize Intent-Revealing Attributes: Always prefer dedicated, stable testing attributes such as data-test, data-testid, or data-qa. Avoid Style-Driven Locators: Steer clear of brittle, structure-dependent CSS paths (e.g., div > div > span:nth-child(2)) and complex XPath queries unless absolutely necessary. Preferred Selector Patterns Buttons & Actions: button[data-test="login-submit"] Content & Inputs: [data-testid="product-name"] Practical Migration Tips Centralize Locators: Group and manage all selector definitions inside dedicated Page Object Model (POM) files rather than hard-coding strings within step definitions or tests. Collaborate for Testability: If a critical UI element lacks a distinct test attribute, submit a quick PR to your developer team to add a dedicated data-test attribute. Automate Audits: Implement a lightweight audit script in your workflow to scan and flag missing data-test attributes across key target pages before running full regressions.
AI 资讯
Rendering Custom Fonts to a 2048px PNG with Canvas
A browser preview can look correct while the downloaded image is wrong. The usual failure is timing: CSS eventually applies the custom font to the preview, but Canvas draws once. If the font is not ready at that exact moment, fillText() can silently use a fallback face. The user sees one design and downloads another. I ran into this while building GraffForge, a browser-based graffiti text tool. The free editor compares the same user-entered word across multiple bundled styles, then exports the selected result as a transparent 2048 × 2048 PNG. That gave the export path a clear contract: preserve the exact text; use the selected font; keep spacing, outline, shadow, and skew; fit inside a safe area; preserve real transparency; never upload the user's text or image. Here is the approach that made the output deterministic. 1. Treat export as a separate rendering target Do not enlarge the preview DOM and take a screenshot. Create a fresh Canvas with explicit bitmap dimensions: const EXPORT_SIZE = 2048 ; const canvas = document . createElement ( ' canvas ' ); canvas . width = EXPORT_SIZE ; canvas . height = EXPORT_SIZE ; const context = canvas . getContext ( ' 2d ' ); if ( ! context ) { throw new Error ( ' Canvas rendering is unavailable. ' ); } The width and height attributes define the actual PNG pixel dimensions. CSS sizing and devicePixelRatio are useful for an on-screen preview, but neither should determine the export contract. A fixed bitmap size also makes automated verification straightforward. 2. Load the font before measuring anything Canvas does not redraw automatically when a font finishes loading. Load the exact family, weight, size, and text before calling measureText() : await document . fonts . load ( `400 160px " ${ fontFamily } "` , text ); Passing the actual text is useful because the browser can confirm that the required glyphs are available. After this point, set the Canvas font explicitly: context . font = `400 ${ fontSize } px " ${ fontFamily } "` ;
AI 资讯
Playwright Automation Quick-Start Runbook: Setup, Execution, and Environment Config
Having a clear runbook speeds up onboarding for new team members and provides a standardized execution reference for CI/CD environments. Here is a quick-start automation runbook covering prerequisites, environment variables, execution commands, and output artifacts for a Playwright test suite. Prerequisites Node.js: 24.x (configured via package.json engines) Package Manager: npm Optional: Docker (for containerized pipeline runs) Quick Setup # 1. Install project dependencies cd <repo-root> npm ci # 2. Install Playwright browsers and dependencies npx playwright install --with-deps Execution Commands Full Test Suite: npm test (executes run-all-tests.js) API Suite Only: npm run test:api (executes run-api-tests.js) UI Suite Only: npm run test:ui (executes run-ui-tests.js) Generate Reports: npm run generate:reports Core Environment Variables Configure these keys inside your local .env file or CI secrets: API_BASE_URL — Base endpoint URL for API testing BASE_URL — Target website base URL for UI testing RETRY_COUNT — Maximum retry limit for flaky scenario reruns CUCUMBER_PARALLEL — Number of parallel workers for Cucumber execution Test Artifacts & Outputs Allure Execution Results: reports/allure-results Cucumber JSON Reports: reports/cucumber_report.json Failure Media (Videos/Screenshots): test-results/ (configured via config.js)
AI 资讯
How to Build a Playwright BDD Test Framework from Scratch: Step-by-Step Setup Guide
Setting up a fresh test automation framework can feel overwhelming without a clear blueprint. Having a structured setup process ensures that directory layouts, configuration files, and execution scripts are aligned right from day one. Here is a quick setup guide for initializing a hybrid BDD framework powered by Playwright, Cucumber, and JavaScript. Installation & Directory Setup Start by installing project dependencies and creating the core folder hierarchy for feature files, step definitions, page objects, and utilities: # Install dependencies npm install # Create required folder structure mkdir features features/API features/UI mkdir step-definitions step-definitions/API step-definitions/UI mkdir page-objects utils setup setup/fixtures mkdir test-data test-data/json test-data/excel mkdir reports logs test-results Key Framework Files Ensure your framework repository includes the following core files: Configuration: package.json, playwright.config.js, cucumber.config.js Page Models & Drivers: page-objects/PageManager.js, utils/ApiHelper.js Hooks & Fixtures: setup/hooks.js Step Definitions: step-definitions/API/PlaywrightAPISteps.js, step-definitions/API/JsonTestDataSteps.js Test Data Strategy & Execution Test Data Management: Primary test data is managed via JSON (test-data/json/testData.json, test-data/json/apiTestData.json), with optional Excel support for tabular data inputs. Environment Setup: Store key environment variables (e.g., BASE_URL, API_BASE_URL) inside your local .env configuration file. Execution Commands: # Run API test suite npm run test :api # Run UI test suite npm run test :ui # Run full execution suite npm run test
AI 资讯
Supabase in Vue Made Simple
Supabase has become one of the most popular choices for building modern web applications. It gives you: PostgreSQL database Authentication Realtime subscriptions Storage Edge Functions TypeScript support The official Supabase JavaScript client already makes it relatively easy to use these features from a Vue application. But integrating Supabase into a Vue application usually means creating a client and then making it available throughout your application. This is where the new @supabase-community/vue-supabase package comes in. The package provides a Vue-friendly integration for Supabase, allowing you to access your Supabase client through useSupabaseClient() while keeping the familiar Supabase API. You can check out the package on GitHub here: https://github.com/supabase-community/vue-supabase In this article, we'll explore: What @supabase-community/vue-supabase is How to install and configure it How to query your database How to use TypeScript with it How to handle authentication How to use Supabase Realtime How to structure Supabase logic using Vue composables What security considerations you need to remember Let's dive in. 🤔 What Is @supabase-community/vue-supabase ? @supabase-community/vue-supabase is a Vue integration for Supabase that provides a convenient way to access the Supabase client inside your Vue application. The main API you'll use is: import { useSupabaseClient } from ' @supabase-community/vue-supabase ' const supabase = useSupabaseClient () Once you have the client, you can use the standard Supabase API: const { data , error } = await supabase . from ( ' profiles ' ) . select ( ' * ' ) This is important because the package doesn't introduce a completely new way of working with Supabase. You still use the APIs you're familiar with: supabase . from () supabase . auth supabase . channel () supabase . storage The package mainly provides the Vue integration layer around them. 🟢 Installing and Configuring the Package The package can be installed with: n
AI 资讯
Warm Hearth — A Landing Page Built Around One Fire
This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing What I Built Warm Hearth — a landing page for a comfort food restaurant built around one idea: everything on the menu comes from the same wood-fired hearth in the back. Instead of treating "comfort food restaurant" as a generic brief, I anchored the whole page to that single hearth: An interactive hearth centerpiece. Right after the hero, there's a hand-drawn CSS/SVG fire pit you can click to "stoke." The flame flares, embers burst upward, and a small honest counter tracks how many times you've stoked it this visit — no fake global numbers, just a real, session-based response to your click. Four dishes, each with real cultural identity. Ramen, warm pies, a cheesy pasta bake, and gulab jamun — each with its own hand-drawn SVG illustration and a border motif pulled from its own cuisine (a jade-and-gold double line for the ramen, a scalloped pastry edge for the pies, an Italian tricolor accent for the pasta, gold paisley tones for the gulab jamun) rather than one generic card style stretched across all four. Living detail, not static photos. Steam rises off the ramen, pies, and pasta bake using the same wisp animation as the hero's hearth, so the whole page reads as one consistent "warmth" language. The gulab jamun gets a syrup shimmer and drip instead, since steam isn't the right detail for a syrup-soaked sweet. Price tags that hang like real kitchen tickets — pinned by a string, swaying gently, and giving a small "flicked" swing on hover instead of sitting flat on the card. Mira, an illustrated host in the corner who offers a rotating table tip when you click her — a small personal touch instead of a static "contact us" widget. Built for actual use, not just to look good in a screenshot: keyboard-focusable tab filters, a skip-to-content link, aria-live regions on the interactive parts, and full prefers-reduced-motion support that disables every animation without breaking the page. Dem
AI 资讯
Reading .xlsx in the browser without a spreadsheet library
I run a small site that converts bank CSV exports into the file format QuickBooks Desktop accepts. The whole thing runs client-side, and the privacy claim it makes is unusually literal: every page ships a Content Security Policy with connect-src 'none' , so the browser refuses to let the page make any network request at all. Open the Network tab while you convert a file and it stays empty. That's the feature, not a nice-to-have on top of it. When I added Excel support last week, the obvious choice was SheetJS. I decided against it, and the reason wasn't bundle size. The claim I want to be able to make is "your file never leaves the browser, and here is the policy that enforces it." Pulling in a large third-party parser turns that into "…and also trust this dependency," which is a materially weaker claim for a tool that handles people's bank statements. So I wanted to find out how much of the format I actually needed. Less than I expected. An .xlsx file is a ZIP archive containing XML: xl/workbook.xml lists the sheets, xl/worksheets/sheet1.xml holds the cells, xl/sharedStrings.xml is a deduplicated string pool that cells reference by index, and xl/styles.xml carries the number formats. Reading that needs three capabilities, and the browser already provides two of them. Unzipping means walking the ZIP central directory, which is about forty lines. Decompression is DecompressionStream('deflate-raw') , which is native. For the XML I hand-rolled a tag scanner rather than reaching for DOMParser , because my tests run in Node where DOMParser doesn't exist, and I would rather have one code path than two. Dates The part that took the most care was dates, because Excel doesn't store them as dates. A cell holding 5 January 2024 contains the number 45296 , and whether that number should be displayed as a date depends on the cell's number format — which lives in a different file inside the archive. So the parser has to read styles.xml , work out which style indexes correspond to
AI 资讯
I found code in my repo I'd never seen. All 82 tests passed. I quarantined it for three days anyway.
During a routine morning triage of my open-source project, git status showed a modified file I had no memory of touching: extension/background.js , last modified 24 hours earlier, sitting next to a fresh background.js.bak someone had thoughtfully left behind. Nobody broke in. I run several AI coding sessions in parallel against the same machine, and one of them — working on a completely different task, automating a GoHighLevel workflow — had hit a limitation in my browser automation tool, fixed the tool itself , verified the fix, and then moved on with its actual job. It never committed. It never told anyone. It just left better code in my working tree and walked away. The diff was good. That was the problem. The change itself was a real feature. My query_all tool (it queries DOM elements across a page) stopped at the main frame: if the elements you wanted lived inside a cross-origin iframe, you got back a clean, confident, empty array. The uncommitted diff added an execAcrossFrames() helper that runs the query in every frame and merges the results, plus x / y / frame fields on each returned element. I verified it the way you'd verify anything: syntax check passed, and the full test suite — all 82 tests — ran green with the change in place . So: useful feature, my own repository, every signal green. Everything about the situation said commit it . I didn't. I wrote it up in my project log, left the file untouched, and set an explicit deadline: if it's still sitting there uncommitted in three days, evaluate it properly — upstream it or revert it and file an issue. Not "leave it and see," which is how working trees rot. A quarantine with no release date is just a junk drawer. Why quarantine green code? Two reasons, and neither is paranoia. First: authorship isn't verification. The session that wrote this code had context I didn't have. Maybe it was mid-iteration and the diff was half of a plan. Maybe the .bak file meant it intended to roll back. Committing someone's wo
AI 资讯
Banx Walk Safe: same sidewalk, two heat loads
This is a submission for the DEV Weekend Challenge: Dog Days Edition . What I Built Same sidewalk. Two bodies. Two completely different heat loads. Banx is my French Bulldog. Born October 5, 2022. He weighs 35 pounds — seven above the 28-pound ceiling in the French Bull Dog Club of America conformation standard. I call him my XL. He is purebred and he has never had airway surgery. The face that makes him Banx is also the conformation that puts French Bulldogs at higher risk of obstructed breathing and heat-related illness. Dogs cool themselves mostly by panting. Flat-faced dogs can do it less efficiently, and how much varies a lot between individual dogs. So the same afternoon — same sun, same pavement, same humidity — is a walk for one dog and something else entirely for him. Nothing on the outside tells you that. Enter a location. It pulls temperature and humidity, computes a heat index, and shows the load on a flat-faced dog beside a longer-muzzle dog across the day. Then it helps me think through the question I actually have when he's standing at the door: how stressful do the conditions look right now, how does that change with activity, and when does the environment get more favorable? It does not medically answer that for him, and the section below says exactly why it can't. Demo Live: https://banx-walk-safe.vercel.app Geolocation or city search. Works if you deny location. No API key. Code Vanilla HTML / CSS / JS. No framework. Repo is the project folder on the machine that built it; the production artifact is the Vercel deploy above. Weather: Open-Meteo . Heat index: NOAA/NWS Rothfusz / Steadman family. Why I Built It When I first got him I didn't know how any of this worked. We started at Ledge Street Park in Nashua and took the trails toward Main Street. First ten minutes he's got everything — all over the place, into everything, full Banx. Then he changes. He stops being all over it and starts just observing. Walking straight forward, taking it in, calm.
AI 资讯
Balan Coffee & Roastery — A Slow-Drip Vietnamese Coffee Landing Page
This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing What I Built I created Balan Coffee & Roastery , a polished landing page for a fictional Vietnamese comfort café in Saigon. The concept is inspired by the quiet comfort of slow phin coffee, butter toast, and small sweet treats. Rather than treating coffee as a quick purchase, I wanted the site to feel like a calm daily ritual: slow, warm, familiar, and personal. Visitors can explore the menu, learn the café story, find visiting information, and interact with a small pixel-art coffee brewing experience. Highlights: Responsive editorial-style coffee shop landing page Vietnamese coffee-inspired menu, story, ritual, and visit sections Clear navigation and accessible interactive controls Consistent number and price typography throughout the site A lightweight interactive mini-game: Pixel Phin Brew Dose beans into the phin Grind the beans Bloom the coffee Let the phin drip Serve the finished cup Built without heavy UI, game, or animation libraries Demo Live demo: Balan Coffee & Roastery Source code: GitHub repository Journey I wanted to create something that felt more like a coffee ritual than a typical restaurant landing page. The visual direction uses warm cream tones, deep coffee browns, generous spacing, subtle texture, and an editorial layout inspired by a slow morning at a Saigon café. I paid attention to small details such as consistent tabular numerals for prices and opening hours, responsive layouts, visible interaction states, and reduced-motion support. The feature I enjoyed building most was Pixel Phin Brew . I wanted the interaction to be understandable instead of just decorative, so each button clearly explains the next brewing action. Every correct step updates the pixel scene, progress indicator, and feedback message until the final cup is served. The project was built with React, TypeScript, Vinext/Vite, and custom CSS. I kept the implementation lightweight and avoided add
AI 资讯
Setting Up Playwright & Cucumber UI Tests in Azure DevOps with LambdaTest
Here is a step-by-step guide to configuring your Playwright/Cucumber test suite to run on LambdaTest Cloud via Azure DevOps pipelines, returning test results directly to Azure. 1. Prerequisites A GitHub repository containing your Playwright, Cucumber, and JavaScript automation code. An active Azure DevOps account with a project created. A LambdaTest account (you will need your username and access key). 2. Connect GitHub to Azure DevOps In Azure DevOps, navigate to Pipelines > New Pipeline. Select GitHub as the source and authenticate your account. Choose your repository and target branch (e.g., main). 3. Create LambdaTest Credentials Variable Group Go to Pipelines > Library in Azure DevOps. Click + Variable group and name it LambdaTest-Credentials. Add the following key-value pairs: LAMBDATEST_USERNAME = your_lambdatest_username LAMBDATEST_ACCESS_KEY = your_lambdatest_access_key (toggle "Keep this value secret") Save the group. 4. Add/Update Your azure-pipelines.yml Place this configuration file in your repository root directory: trigger : - main pool : vmImage : ' windows-latest' variables : - group : LambdaTest-Credentials - name : BASE_URL value : ' https://your-app-url.com' - name : LT_BROWSER value : ' chrome' - name : ENABLE_LAMBDATEST value : ' true' stages : - stage : Test jobs : - job : UITestsLambdaTest displayName : ' UI Tests (LambdaTest Cloud)' steps : - task : NodeTool@0 inputs : versionSpec : ' 20.x' displayName : ' Install Node.js 20.x' - script : npm ci displayName : ' Install Dependencies' - script : npm run test:ui:smoke displayName : ' Run UI Smoke Tests on LambdaTest' env : ENABLE_LAMBDATEST : ' true' LT_USERNAME : $(LAMBDATEST_USERNAME) LT_ACCESS_KEY : $(LAMBDATEST_ACCESS_KEY) LT_BROWSER : $(LT_BROWSER) BASE_URL : $(BASE_URL) - task : PublishTestResults@2 condition : always() inputs : testResultsFormat : ' JUnit' testResultsFiles : ' reports/junit-report.xml' testRunTitle : ' UI Tests - LambdaTest Cloud' 5. Update Your Test Code Ensure your tes
AI 资讯
var in JavaScript
var is one of the ways to create a variable in JavaScript. A variable is a place to store a value, like a name or a number. var is mostly seen in old JavaScript code, written before 2015. Today most people use let and const instead, but it still helps to know var , especially when reading old code. Creating a Variable var name = " Abishek " ; var age = 22 ; console . log ( name ); console . log ( age ); Here, name stores "Abishek" and age stores 22 . We Can Change the Value var age = 22 ; age = 23 ; console . log ( age ); The output is 23 . The value inside age got updated. We Can Also Create it Again We can create the same variable a second time with var , and JavaScript does not give an error. var name = " Abishek " ; var name = " Abi " ; console . log ( name ); The output is Abi . It just overwrites the old value. It Works Across the Whole Function A block is a small part of code inside { } , like an if statement. var does not care about these small blocks, it only cares about the function. function test () { if ( true ) { var x = 10 ; } console . log ( x ); // works fine } test (); Even though x was created inside the if part, we can still use it outside the if , as long as we are inside the function. Hoisting console . log ( x ); var x = 10 ; You might expect an error here, but the output is undefined . This is because JavaScript moves the var declaration to the top before running the code. This is called hoisting. Why var Isn't Used Much Now Most people use let and const instead of var , because var can cause confusing bugs like accidental redeclaration and hoisting. let is used when the value can change, and const is used when it should not change. In Short var was the first way to create variables in JavaScript. It can be changed, redeclared, and it works across the whole function instead of one block. Once you understand var , let and const become easier to learn.
AI 资讯
The World Clock Time-Zone Landscape: what 162 places reveal about time zones
Time zones look like a tidy grid of whole hours. They aren't. I read the standard UTC offset of all 162 cities, countries and regions on our World Clock straight from the IANA database (via Intl ) — and the real shape is lumpy, with quarter-hour outliers and a near-even split over whether clocks move at all. The quirk, in one line: Kathmandu keeps its clocks 5 hours 45 minutes ahead of UTC — the only :45 offset on the board, and one of 11 places out of 162 that don't sit on a whole hour. Nearly half the rest never move their clocks at all. The clocks that don't sit on the hour Most of the world rounds to a whole hour from UTC. A handful don't: Offset Places UTC+3:30 Tehran (Iran) UTC+4:30 Kabul (Afghanistan) UTC+5:30 India — New Delhi, Mumbai, Kolkata, Bengaluru, Hyderabad UTC+5:45 Kathmandu (Nepal) UTC+9:30 Adelaide, Darwin (Australia) Half-hour and quarter-hour offsets are a reminder that a time zone is a political decision, not an astronomical one — which is exactly why date code should read the IANA database rather than dividing longitude by 15. Nearly half never change their clocks Daylight saving feels universal if you live in North America or Europe, but it isn't. Of the 162 places tracked, 87 (54%) shift their clocks and 75 (46%) never do . The whole of East Asia, the Gulf, most of Africa, India and much of South America keep one fixed offset year-round — Tokyo, Singapore, Dubai, Nairobi and New Delhi never spring forward. Where the clocks crowd together Offsets aren't evenly populated. Four of them carry nearly half the board: Offset Places Who's there UTC−5 25 US Eastern — New York, Toronto, Miami, Boston UTC+1 21 Central Europe — Paris, Berlin, Rome, Madrid UTC−6 14 US Central — Chicago, Dallas, Mexico City UTC+2 12 Eastern Europe & Africa — Athens, Cairo, Johannesburg The full set spans 22 hours , from Honolulu at UTC−10 to New Zealand and Fiji at UTC+12. Reproduce it Every number here is printed by one dependency-free Node script that reads each place's
开源项目
🔥 babalae / bettergi-scripts-list - BetterGI 的脚本仓库,内含BetterGI 的JS脚本、路径追踪、战斗策略、七圣召唤策略。
GitHub热门项目 | BetterGI 的脚本仓库,内含BetterGI 的JS脚本、路径追踪、战斗策略、七圣召唤策略。 | Stars: 515 | 2 stars today | 语言: JavaScript
开源项目
🔥 IRNova / Nova-Proxy - یک پنل گرافیکی کاربردی برای ارائه اشتراکهای Worker با پروکس
GitHub热门项目 | یک پنل گرافیکی کاربردی برای ارائه اشتراکهای Worker با پروکسیهای ، Trojan و Warp به همراه زنجیره پروکسی، ارائه دهنده تنظیمات کامل DNS، IP تمیز و روتینگ پیشرفته برای کاربران تمامی پلتفرمها با استفاده از هستههای Amnezia، Wireguard، Sing-box، Clash/Mihomo و Xray. | Stars: 3,039 | 24 stars today | 语言: JavaScript
AI 资讯
Build a POS receipt printer in Node.js
Disclosure: I build Receiptful, the printing API used in this tutorial. The Node and Express parts apply whatever you print with. You have orders coming into your point of sale, and you want each one to print on the thermal printer at the counter. This is a complete walkthrough of a small Node service that does exactly that. By the end you will have an endpoint you can POST an order to and watch paper come out. There is nothing to install next to the printer for this tutorial to work, and no ESC/POS to write by hand. You send HTML, Receiptful prints it. Before you start You need two things from the console : A paired printer, which gives you a printer ID . If you have not done this yet, the getting started guide walks through it in a couple of minutes. An API key (the rf_live_… value), created under API keys and shown only once. On the code side you need Node 18 or newer, so that fetch is available globally with no extra dependency. We will use TypeScript, but the same code works in plain JavaScript if you drop the types. Put your credentials in the environment rather than in the source: export RECEIPTFUL_API_KEY = "rf_live_3f9c…" export RECEIPTFUL_PRINTER_ID = "42" Step 1: model the order Start with the shape of an order. Yours will have more fields, but this is enough to print a useful receipt: interface LineItem { name : string ; quantity : number ; unitPrice : number ; // in cents, to avoid float rounding } interface Order { id : number ; items : LineItem []; placedAt : Date ; } Keeping money in cents and formatting only at the edges saves you from the classic floating point rounding bugs that show up as a receipt total that is one cent off. Step 2: render the order as HTML This is the part that decides how the receipt looks. Receiptful converts the HTML you send into ESC/POS for your specific printer, so you get to lay a receipt out with tags you already know instead of byte codes. function money ( cents : number ): string { return " $ " + ( cents / 100 ). toFi