openvibers/openvibe.coupons Commits
Coupons: a page for the OpenVibe Companion extension, and the helper's privacy text made true for 0.3.0 (#66)
Changes
@@@ -169,6 +169,7 @@ The code is in `server/domain/hosts.js` and `server/domain/psl.js`. The tests ar| `POST /c/:id/report` | Worked or didn't work, with an optional reason. Needs sign-in and a form token. || `POST /m/:slug/watch` | Watch or stop watching a shop. || `/submit` | Submit a code: shop, code, what it gives, details, evidence link, expiry (empty means unknown) and whether the evidence page states it, restrictions, and how to apply. |++| `/companion` | The OpenVibe Companion browser extension (OpenVibers/OpenVibe.Extensions `browser/`): what it does across Coupons, Food, Rent and Watch, what it sees and sends, and how to install it from its release zip. Linked from the home and the navbar. || `/connect-extension` | Create, list and revoke browser-helper tokens. || `/watching` | Your watched shops. Private. || `/staff` | Add shops, approve or reject pending shops and codes, take down, expire or re-enable a code, and see the import holds. |@@@ -63,6 +63,7 @@ function createDiscoveryRoutes({ config, store, merchants, coupons, publication{ title: 'Start here', links: [{ title: 'All shops', url: abs('/'), note: 'with recently added codes' },{ title: 'How codes are labelled', url: abs('/about'), note: 'statuses, the confidence formula, expiry, privacy' },++ { title: 'The OpenVibe Companion', url: abs('/companion'), note: 'the open-source browser extension: codes for the shop you are on, tried when you ask; never rewrites referral links' },{ title: 'Sitemap', url: abs('/sitemap.xml') },] },{ title: 'Feeds', links: [{ title: 'RSS', url: abs('/feed.xml') }, { title: 'Atom', url: abs('/atom.xml') }, { title: 'JSON Feed', url: abs('/feed.json') }] },@@@ -113,7 +113,7 @@ function createPublicRoutes(ctx) {eyebrow: 'OpenVibe.Coupons',title: 'Coupon codes,', accent: 'honestly labelled',lede: 'Codes for online shops with their restrictions, their expiry (or "unknown") and what people reported when they tried them. A code is only ever called working because people said it worked recently.',-- actions: [{ label: 'Find a shop', href: '#q', primary: true }, { label: 'Submit a code', href: '/submit' }],++ actions: [{ label: 'Find a shop', href: '#q', primary: true }, { label: 'Get the browser extension', href: '/companion' }, { label: 'Submit a code', href: '/submit' }],}) + showcase.features({title: 'What every code tells you',items: [@@@ -121,6 +121,7 @@ function createPublicRoutes(ctx) {{ icon: 'ov:docs', title: 'Restrictions stated', text: 'Minimum spend, new customers only, the products it applies to: written down, not hidden.' },{ icon: 'ov:history', title: 'Honest expiry', text: 'An expiry date when there is one, and "unknown" when there is not.' },{ icon: 'ov:community', title: 'Reports from people', text: 'Worked or did not work, recently: the status comes from what people said when they tried it.' },++ { icon: 'ov:plug', title: 'Tried for you, no referral games', text: 'The OpenVibe Companion extension tries the codes in the shop\'s coupon box when you ask, and never rewrites referral links.', href: '/companion' },],});}@@@ -144,6 +145,34 @@ function createPublicRoutes(ctx) {}, { cacheable: true });});++ // The OpenVibe Companion browser extension (OpenVibers/OpenVibe.Extensions): what it does and sees, how to install it.++ function companionShowcase() {++ return showcase.hero({++ eyebrow: 'Browser extension · free and open source',++ title: 'The OpenVibe', accent: 'Companion',++ lede: 'For Chrome, Edge, Brave and Firefox. It finds coupon codes for the shop you are on and tries them for you when you ask. It never rewrites links or adds referral tags, so the creator or site that sent you keeps its credit.',++ actions: [{ label: 'Get the Companion', href: pages.COMPANION_RELEASE, primary: true }, { label: 'How to install it', href: '#install' }],++ }) + showcase.features({++ title: 'What it does',++ items: [++ { icon: 'ov:coupons', title: 'Codes for this shop', text: 'On a shop\'s site it lists the codes OpenVibe.Coupons knows for it, with restrictions, expiry and what people reported, best first.' },++ { icon: 'ov:check', title: 'Try these codes on this page', text: 'At checkout, one button types the codes into the shop\'s own coupon box, presses Apply and reads the total before and after. Codes reported not working are skipped; at most ten are tried.' },++ { icon: 'ov:food', title: 'Compare delivery apps', text: 'On DoorDash, Uber Eats, Grubhub, Seamless, Caviar or Instacart it opens the same restaurant on every app through OpenVibe.Food.', href: 'https://openvibe.food/delivery' },++ { icon: 'ov:rent', title: 'Keep a rental on your board', text: 'On Craigslist, Facebook Marketplace, Zillow and other listing sites, one click keeps the listing on your OpenVibe.Rent board.', href: 'https://openvibe.rent/board' },++ { icon: 'ov:watch', title: 'Watch a page', text: 'Opens a watch on OpenVibe.Watch for the page, so you hear when a price, a stock line or a listing changes.', href: 'https://openvibe.watch' },++ { icon: 'ov:shield', title: 'No referral games', text: 'It never replaces affiliate tags, never adds its own, never opens tabs behind your back and never earns from your shopping.' },++ ],++ });++ }++ router.get('/companion', (req, res) => {++ send(req, res, 200, {++ title: 'The OpenVibe Companion browser extension', decision: pageDecision('/companion'), canonical: `${config.baseUrl}/companion`,++ description: 'A free, open-source extension for Chrome, Edge, Brave and Firefox: coupon codes for the shop you are on, tried when you ask, delivery apps compared and rentals kept on your board. It never rewrites referral links.',++ styles: [showcase.STYLESHEET],++ body: companionShowcase() + pages.companion(),++ }, { cacheable: true });++ });++router.get('/about', (req, res) => {send(req, res, 200, { title: 'How codes are labelled', decision: pageDecision('/about'), body: pages.about({ confidence }) }, { cacheable: true });});@@@ -22,7 +22,7 @@ const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');const NAV = [{ label: 'Merchants', href: '/' },{ label: 'Submit a code', href: '/submit' },-- { label: 'Browser helper', href: '/connect-extension' },++ { label: 'Browser extension', href: '/companion' },];const hashes = new Map();@@@ -62,10 +62,10 @@ function renderPage(o) {};// This site's own account links live in the shared navbar's account menu (the page's account// bar below is only for visitors without JavaScript).-- if (signedIn) nav.menu = { before: [{ label: 'Watching', href: '/watching', icon: 'fa-bell' }, { label: 'Browser helper', href: '/connect-extension', icon: 'fa-puzzle-piece' }, ...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : [])] };++ if (signedIn) nav.menu = { before: [{ label: 'Watching', href: '/watching', icon: 'fa-bell' }, { label: 'Connected browsers', href: '/connect-extension', icon: 'fa-puzzle-piece' }, ...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : [])] };const footer = { service: 'coupons', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };const account = signedIn-- ? `<a href="/watching">Watching</a> · <a href="/connect-extension">Browser helper</a>${viewer.staff ? ' · <a href="/staff">Staff</a>' : ''} · <a href="/auth/logout?next=${loginNext}">Sign out</a>`++ ? `<a href="/watching">Watching</a> · <a href="/connect-extension">Connected browsers</a>${viewer.staff ? ' · <a href="/staff">Staff</a>' : ''} · <a href="/auth/logout?next=${loginNext}">Sign out</a>`: `<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a>`;return layout.renderDocument({site: 'coupons',@@@ -186,8 +186,8 @@ function about({ confidence }) {<p>An expiry is shown only when someone stated it, with who stated it. A date without a time means the end of that day in UTC, because the merchant's time zone is not known. When nobody stated an expiry it is “unknown”.</p><h2 id="privacy">Privacy</h2><p>Codes and their aggregates are public. Who submitted or reported a code is never shown, never returned by the API and never sent in events. Reports are stored under a keyed hash of the person, not their account id.</p>--<h2 id="helper">The browser helper</h2>--<p>The OpenVibe browser helper reads only the hostname of the tab you are on, and only when you open it. It asks this site's public API which shop that is and lists its codes. It never reads the page, never sends the page's address beyond the hostname and holds no OpenVibe sign-in: to report from it you <a href="/connect-extension">connect it</a> with a token that can only look up and report, and that you can revoke at any time.</p>`;++<h2 id="helper">The OpenVibe Companion</h2>++<p>The <a href="/companion">OpenVibe Companion</a> browser extension sends this site only the <strong>hostname</strong> of the tab you are on, and only when you open it, to list that shop's codes. When you press “Try these codes on this page” it reads the page in your own browser (the coupon box, its Apply button and the order total) and nothing from the page leaves your browser. It never rewrites links or adds referral tags, and it holds no OpenVibe sign-in: to report from it you <a href="/connect-extension">connect it</a> with a token that can only look up and report, and that you can revoke at any time.</p>`;}function submitForm({ values = {}, errs = [], csrf, viewer, merchantName = null }) {@@@ -233,11 +233,11 @@ function submitted({ coupon, merchant, duplicate }) {}function connect({ viewer, installs, csrf, created, errs = [] }) {-- const intro = h`<h1>Connect the browser helper</h1>--<p class="lede">The OpenVibe browser helper shows codes for the shop you are on. It works without connecting; connecting lets it report whether a code worked, as you.</p>++ const intro = h`<h1>Connect the OpenVibe Companion</h1>++<p class="lede">The <a href="/companion">OpenVibe Companion</a> shows and tries codes for the shop you are on. It works without connecting; connecting lets it report whether a code worked, as you, so the next person sees it.</p><ul>--<li>It reads only the <strong>hostname</strong> of the current tab, and only when you open it.</li>--<li>It never reads page content, never sends full addresses and has no access to your OpenVibe account: the token below can only look up codes and (if you allow it) report on them.</li>++<li>It sends this site only the <strong>hostname</strong> of the current tab, and only when you open it.</li>++<li>When you ask it to try codes, it reads the checkout page in your browser; nothing from the page is sent anywhere. It never sends full addresses and has no access to your OpenVibe account: the token below can only look up codes and (if you allow it) report on them.</li><li>Revoking a token here takes effect on its next request.</li></ul>`;if (!viewer || viewer.kind !== 'user') return h`${intro}<p><a href="/auth/login?next=%2Fconnect-extension">Sign in with OpenVibe</a> to create a token.</p>`;@@@ -258,6 +258,34 @@ ${installs.length ? h`<table class="installs"><thead><tr><th>Name</th><th>Token<<td>${i.active ? h`<form method="post" action="/connect-extension/${i.id}/revoke">${hidden(csrf)}<button type="submit">Revoke</button></form>` : (i.revoked_at ? 'revoked' : 'expired')}</td></tr>`)}</tbody></table>` : h`<p class="empty">None yet.</p>`}`;}++const COMPANION_RELEASE = 'https://github.com/OpenVibers/OpenVibe.Extensions/releases/latest';++const COMPANION_SOURCE = 'https://github.com/OpenVibers/OpenVibe.Extensions/tree/main/browser';++++/**++ * /companion below its showcase (server/http/public.js draws the hero and the feature cards): what the OpenVibe++ * Companion (OpenVibers/OpenVibe.Extensions `browser/`) sees and sends, how to install it, connecting it.++ */++function companion() {++ return h`<h2 id="privacy">What it can see, and what it sends</h2>++<ul>++<li><strong>Permissions:</strong> the active tab (only after you click it), running its own code there when you press a button, and its own storage. It has no access to other tabs, your history or cookies, and no background process.</li>++<li><strong>Sent to OpenVibe.Coupons:</strong> the tab's hostname when you open it (for example <code>shop.example</code>), and a worked or did not work report when you have connected it and say so.</li>++<li><strong>Read from the page:</strong> the tab's address and title when you open it; the checkout page's coupon box, Apply button and total only when you press “Try these codes on this page”. Nothing read from the page leaves your browser.</li>++<li><strong>Stored:</strong> only the optional connection token, on that device.</li>++</ul>++<h2 id="install">Install it</h2>++<p>It is not in the browser stores yet, so it installs from its release file:</p>++<ol>++<li>Download the zip from the <a href="${COMPANION_RELEASE}">latest release</a> and unzip it.</li>++<li><strong>Chrome, Edge or Brave:</strong> open <code>chrome://extensions</code>, turn on Developer mode, choose “Load unpacked” and pick the unzipped folder.</li>++<li><strong>Firefox:</strong> open <code>about:debugging</code>, choose “This Firefox”, then “Load Temporary Add-on”, and pick the <code>manifest.json</code> in the folder.</li>++<li>Pin it to the toolbar, open a shop's site and click it.</li>++</ol>++<h2>Connect it (optional)</h2>++<p>Without connecting, it looks up and tries codes. <a href="/connect-extension">Connect it</a> to report whether a code worked, so the labels stay honest for everyone. The token can only look up and report, and you can revoke it at any time.</p>++<p class="meta">Source code and the full privacy table: <a href="${COMPANION_SOURCE}">OpenVibers/OpenVibe.Extensions</a>. How codes are labelled: <a href="/about">the method</a>.</p>`;++}++function watching({ merchants }) {return h`<h1>Shops you watch</h1><p class="meta">Private to you. When a new code for one of these shops is listed, you get a notification in your OpenVibe inbox (unless you muted service notifications there).</p>@@@ -296,4 +324,4 @@ function message({ heading, text, action }) {return h`<h1>${heading}</h1><p>${text}</p>${action ? h`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;}--module.exports = { home, merchant, couponPage, couponCard, about, submitForm, submitted, connect, watching, staff, message, STATUS_LABEL, SITE_SUMMARY };++module.exports = { home, merchant, couponPage, couponCard, about, submitForm, submitted, connect, companion, COMPANION_RELEASE, watching, staff, message, STATUS_LABEL, SITE_SUMMARY };@@@ -157,7 +157,17 @@ const { boot, check, done } = require('./helpers/boot');assert.match(s.text, /<meta name="robots" content="noindex, nofollow">/, 'search results are not indexed');const about = await t.get('/about');assert.match(about.text, /confidence = \(1 \+ e \+ W\) \/ \(2 \+ e \+ W \+ F\)/);-- assert.match(about.text, /reads only the hostname/);++ assert.match(about.text, /sends this site only the <strong>hostname<\/strong>/);++ assert.match(about.text, /never rewrites links or adds referral tags/);++ assert.ok(home.text.includes('href="/companion"'), 'the home links the Companion');++ const cp = await t.get('/companion');++ assert.strictEqual(cp.status, 200);++ assert.ok(cp.text.includes('class="sc-hero') && /<h1[^>]*>The OpenVibe[\s\S]{0,80}Companion/.test(cp.text), 'the page opens with its showcase');++ assert.strictEqual((cp.text.match(/<h1[\s>]/g) || []).length, 1, 'one h1');++ assert.ok(cp.text.includes('https://github.com/OpenVibers/OpenVibe.Extensions/releases/latest') && cp.text.includes('chrome://extensions') && cp.text.includes('about:debugging'), 'how to install it');++ assert.ok(cp.text.includes('https://openvibe.rent/board') && cp.text.includes('https://openvibe.food/delivery'), 'what it does across sites');++ assert.match(cp.text, /<link rel="canonical" href="[^"]*\/companion">/);++ assert.doesNotMatch(cp.text, /noindex/, 'indexable');});await check('every page carries the boost marker and script; the navbar signs in back to the current page', async () => {
@@ -169,6 +169,7 @@ The code is in `server/domain/hosts.js` and `server/domain/psl.js`. The tests ar
@@ -169,6 +169,7 @@ The code is in `server/domain/hosts.js` and `server/domain/psl.js`. The tests ar
| `POST /c/:id/report` | Worked or didn't work, with an optional reason. Needs sign-in and a form token. |
| `POST /c/:id/report` | Worked or didn't work, with an optional reason. Needs sign-in and a form token. |
| `POST /m/:slug/watch` | Watch or stop watching a shop. |
| `POST /m/:slug/watch` | Watch or stop watching a shop. |
| `/submit` | Submit a code: shop, code, what it gives, details, evidence link, expiry (empty means unknown) and whether the evidence page states it, restrictions, and how to apply. |
| `/submit` | Submit a code: shop, code, what it gives, details, evidence link, expiry (empty means unknown) and whether the evidence page states it, restrictions, and how to apply. |
+| `/companion` | The OpenVibe Companion browser extension (OpenVibers/OpenVibe.Extensions `browser/`): what it does across Coupons, Food, Rent and Watch, what it sees and sends, and how to install it from its release zip. Linked from the home and the navbar. |
| `/connect-extension` | Create, list and revoke browser-helper tokens. |
| `/connect-extension` | Create, list and revoke browser-helper tokens. |
| `/watching` | Your watched shops. Private. |
| `/watching` | Your watched shops. Private. |
| `/staff` | Add shops, approve or reject pending shops and codes, take down, expire or re-enable a code, and see the import holds. |
| `/staff` | Add shops, approve or reject pending shops and codes, take down, expire or re-enable a code, and see the import holds. |
@@ -63,6 +63,7 @@ function createDiscoveryRoutes({ config, store, merchants, coupons, publication
@@ -63,6 +63,7 @@ function createDiscoveryRoutes({ config, store, merchants, coupons, publication
{ title: 'Start here', links: [
{ title: 'Start here', links: [
{ title: 'All shops', url: abs('/'), note: 'with recently added codes' },
{ title: 'All shops', url: abs('/'), note: 'with recently added codes' },
{ title: 'How codes are labelled', url: abs('/about'), note: 'statuses, the confidence formula, expiry, privacy' },
{ title: 'How codes are labelled', url: abs('/about'), note: 'statuses, the confidence formula, expiry, privacy' },
+ { title: 'The OpenVibe Companion', url: abs('/companion'), note: 'the open-source browser extension: codes for the shop you are on, tried when you ask; never rewrites referral links' },
{ title: 'Sitemap', url: abs('/sitemap.xml') },
{ title: 'Sitemap', url: abs('/sitemap.xml') },
] },
] },
{ title: 'Feeds', links: [{ title: 'RSS', url: abs('/feed.xml') }, { title: 'Atom', url: abs('/atom.xml') }, { title: 'JSON Feed', url: abs('/feed.json') }] },
{ title: 'Feeds', links: [{ title: 'RSS', url: abs('/feed.xml') }, { title: 'Atom', url: abs('/atom.xml') }, { title: 'JSON Feed', url: abs('/feed.json') }] },
@@ -113,7 +113,7 @@ function createPublicRoutes(ctx) {
@@ -113,7 +113,7 @@ function createPublicRoutes(ctx) {
eyebrow: 'OpenVibe.Coupons',
eyebrow: 'OpenVibe.Coupons',
title: 'Coupon codes,', accent: 'honestly labelled',
title: 'Coupon codes,', accent: 'honestly labelled',
lede: 'Codes for online shops with their restrictions, their expiry (or "unknown") and what people reported when they tried them. A code is only ever called working because people said it worked recently.',
lede: 'Codes for online shops with their restrictions, their expiry (or "unknown") and what people reported when they tried them. A code is only ever called working because people said it worked recently.',
- actions: [{ label: 'Find a shop', href: '#q', primary: true }, { label: 'Submit a code', href: '/submit' }],
+ actions: [{ label: 'Find a shop', href: '#q', primary: true }, { label: 'Get the browser extension', href: '/companion' }, { label: 'Submit a code', href: '/submit' }],
}) + showcase.features({
}) + showcase.features({
title: 'What every code tells you',
title: 'What every code tells you',
items: [
items: [
@@ -121,6 +121,7 @@ function createPublicRoutes(ctx) {
@@ -121,6 +121,7 @@ function createPublicRoutes(ctx) {
{ icon: 'ov:docs', title: 'Restrictions stated', text: 'Minimum spend, new customers only, the products it applies to: written down, not hidden.' },
{ icon: 'ov:docs', title: 'Restrictions stated', text: 'Minimum spend, new customers only, the products it applies to: written down, not hidden.' },
{ icon: 'ov:history', title: 'Honest expiry', text: 'An expiry date when there is one, and "unknown" when there is not.' },
{ icon: 'ov:history', title: 'Honest expiry', text: 'An expiry date when there is one, and "unknown" when there is not.' },
{ icon: 'ov:community', title: 'Reports from people', text: 'Worked or did not work, recently: the status comes from what people said when they tried it.' },
{ icon: 'ov:community', title: 'Reports from people', text: 'Worked or did not work, recently: the status comes from what people said when they tried it.' },
+ { icon: 'ov:plug', title: 'Tried for you, no referral games', text: 'The OpenVibe Companion extension tries the codes in the shop\'s coupon box when you ask, and never rewrites referral links.', href: '/companion' },
],
],
});
});
}
}
@@ -144,6 +145,34 @@ function createPublicRoutes(ctx) {
@@ -144,6 +145,34 @@ function createPublicRoutes(ctx) {
}, { cacheable: true });
}, { cacheable: true });
});
});
+ // The OpenVibe Companion browser extension (OpenVibers/OpenVibe.Extensions): what it does and sees, how to install it.
+ function companionShowcase() {
+ return showcase.hero({
+ eyebrow: 'Browser extension · free and open source',
+ title: 'The OpenVibe', accent: 'Companion',
+ lede: 'For Chrome, Edge, Brave and Firefox. It finds coupon codes for the shop you are on and tries them for you when you ask. It never rewrites links or adds referral tags, so the creator or site that sent you keeps its credit.',
+ actions: [{ label: 'Get the Companion', href: pages.COMPANION_RELEASE, primary: true }, { label: 'How to install it', href: '#install' }],
+ }) + showcase.features({
+ title: 'What it does',
+ items: [
+ { icon: 'ov:coupons', title: 'Codes for this shop', text: 'On a shop\'s site it lists the codes OpenVibe.Coupons knows for it, with restrictions, expiry and what people reported, best first.' },
+ { icon: 'ov:check', title: 'Try these codes on this page', text: 'At checkout, one button types the codes into the shop\'s own coupon box, presses Apply and reads the total before and after. Codes reported not working are skipped; at most ten are tried.' },
+ { icon: 'ov:food', title: 'Compare delivery apps', text: 'On DoorDash, Uber Eats, Grubhub, Seamless, Caviar or Instacart it opens the same restaurant on every app through OpenVibe.Food.', href: 'https://openvibe.food/delivery' },
+ { icon: 'ov:rent', title: 'Keep a rental on your board', text: 'On Craigslist, Facebook Marketplace, Zillow and other listing sites, one click keeps the listing on your OpenVibe.Rent board.', href: 'https://openvibe.rent/board' },
+ { icon: 'ov:watch', title: 'Watch a page', text: 'Opens a watch on OpenVibe.Watch for the page, so you hear when a price, a stock line or a listing changes.', href: 'https://openvibe.watch' },
+ { icon: 'ov:shield', title: 'No referral games', text: 'It never replaces affiliate tags, never adds its own, never opens tabs behind your back and never earns from your shopping.' },
+ ],
+ });
+ }
+ router.get('/companion', (req, res) => {
+ send(req, res, 200, {
+ title: 'The OpenVibe Companion browser extension', decision: pageDecision('/companion'), canonical: `${config.baseUrl}/companion`,
+ description: 'A free, open-source extension for Chrome, Edge, Brave and Firefox: coupon codes for the shop you are on, tried when you ask, delivery apps compared and rentals kept on your board. It never rewrites referral links.',
+ styles: [showcase.STYLESHEET],
+ body: companionShowcase() + pages.companion(),
+ }, { cacheable: true });
+ });
+
router.get('/about', (req, res) => {
router.get('/about', (req, res) => {
send(req, res, 200, { title: 'How codes are labelled', decision: pageDecision('/about'), body: pages.about({ confidence }) }, { cacheable: true });
send(req, res, 200, { title: 'How codes are labelled', decision: pageDecision('/about'), body: pages.about({ confidence }) }, { cacheable: true });
});
});
@@ -22,7 +22,7 @@ const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
@@ -22,7 +22,7 @@ const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
const NAV = [
const NAV = [
{ label: 'Merchants', href: '/' },
{ label: 'Merchants', href: '/' },
{ label: 'Submit a code', href: '/submit' },
{ label: 'Submit a code', href: '/submit' },
- { label: 'Browser helper', href: '/connect-extension' },
+ { label: 'Browser extension', href: '/companion' },
];
];
const hashes = new Map();
const hashes = new Map();
@@ -62,10 +62,10 @@ function renderPage(o) {
@@ -62,10 +62,10 @@ function renderPage(o) {
};
};
// This site's own account links live in the shared navbar's account menu (the page's account
// This site's own account links live in the shared navbar's account menu (the page's account
// bar below is only for visitors without JavaScript).
// bar below is only for visitors without JavaScript).
- if (signedIn) nav.menu = { before: [{ label: 'Watching', href: '/watching', icon: 'fa-bell' }, { label: 'Browser helper', href: '/connect-extension', icon: 'fa-puzzle-piece' }, ...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : [])] };
+ if (signedIn) nav.menu = { before: [{ label: 'Watching', href: '/watching', icon: 'fa-bell' }, { label: 'Connected browsers', href: '/connect-extension', icon: 'fa-puzzle-piece' }, ...(viewer.staff ? [{ label: 'Staff', href: '/staff', icon: 'fa-shield' }] : [])] };
const footer = { service: 'coupons', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const footer = { service: 'coupons', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const account = signedIn
const account = signedIn
- ? `<a href="/watching">Watching</a> · <a href="/connect-extension">Browser helper</a>${viewer.staff ? ' · <a href="/staff">Staff</a>' : ''} · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
+ ? `<a href="/watching">Watching</a> · <a href="/connect-extension">Connected browsers</a>${viewer.staff ? ' · <a href="/staff">Staff</a>' : ''} · <a href="/auth/logout?next=${loginNext}">Sign out</a>`
: `<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a>`;
: `<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a>`;
return layout.renderDocument({
return layout.renderDocument({
site: 'coupons',
site: 'coupons',
@@ -186,8 +186,8 @@ function about({ confidence }) {
@@ -186,8 +186,8 @@ function about({ confidence }) {
<p>An expiry is shown only when someone stated it, with who stated it. A date without a time means the end of that day in UTC, because the merchant's time zone is not known. When nobody stated an expiry it is “unknown”.</p>
<p>An expiry is shown only when someone stated it, with who stated it. A date without a time means the end of that day in UTC, because the merchant's time zone is not known. When nobody stated an expiry it is “unknown”.</p>
<h2 id="privacy">Privacy</h2>
<h2 id="privacy">Privacy</h2>
<p>Codes and their aggregates are public. Who submitted or reported a code is never shown, never returned by the API and never sent in events. Reports are stored under a keyed hash of the person, not their account id.</p>
<p>Codes and their aggregates are public. Who submitted or reported a code is never shown, never returned by the API and never sent in events. Reports are stored under a keyed hash of the person, not their account id.</p>
-<h2 id="helper">The browser helper</h2>
-<p>The OpenVibe browser helper reads only the hostname of the tab you are on, and only when you open it. It asks this site's public API which shop that is and lists its codes. It never reads the page, never sends the page's address beyond the hostname and holds no OpenVibe sign-in: to report from it you <a href="/connect-extension">connect it</a> with a token that can only look up and report, and that you can revoke at any time.</p>`;
+<h2 id="helper">The OpenVibe Companion</h2>
+<p>The <a href="/companion">OpenVibe Companion</a> browser extension sends this site only the <strong>hostname</strong> of the tab you are on, and only when you open it, to list that shop's codes. When you press “Try these codes on this page” it reads the page in your own browser (the coupon box, its Apply button and the order total) and nothing from the page leaves your browser. It never rewrites links or adds referral tags, and it holds no OpenVibe sign-in: to report from it you <a href="/connect-extension">connect it</a> with a token that can only look up and report, and that you can revoke at any time.</p>`;
}
}
function submitForm({ values = {}, errs = [], csrf, viewer, merchantName = null }) {
function submitForm({ values = {}, errs = [], csrf, viewer, merchantName = null }) {
@@ -233,11 +233,11 @@ function submitted({ coupon, merchant, duplicate }) {
@@ -233,11 +233,11 @@ function submitted({ coupon, merchant, duplicate }) {
}
}
function connect({ viewer, installs, csrf, created, errs = [] }) {
function connect({ viewer, installs, csrf, created, errs = [] }) {
- const intro = h`<h1>Connect the browser helper</h1>
-<p class="lede">The OpenVibe browser helper shows codes for the shop you are on. It works without connecting; connecting lets it report whether a code worked, as you.</p>
+ const intro = h`<h1>Connect the OpenVibe Companion</h1>
+<p class="lede">The <a href="/companion">OpenVibe Companion</a> shows and tries codes for the shop you are on. It works without connecting; connecting lets it report whether a code worked, as you, so the next person sees it.</p>
<ul>
<ul>
-<li>It reads only the <strong>hostname</strong> of the current tab, and only when you open it.</li>
-<li>It never reads page content, never sends full addresses and has no access to your OpenVibe account: the token below can only look up codes and (if you allow it) report on them.</li>
+<li>It sends this site only the <strong>hostname</strong> of the current tab, and only when you open it.</li>
+<li>When you ask it to try codes, it reads the checkout page in your browser; nothing from the page is sent anywhere. It never sends full addresses and has no access to your OpenVibe account: the token below can only look up codes and (if you allow it) report on them.</li>
<li>Revoking a token here takes effect on its next request.</li>
<li>Revoking a token here takes effect on its next request.</li>
</ul>`;
</ul>`;
if (!viewer || viewer.kind !== 'user') return h`${intro}<p><a href="/auth/login?next=%2Fconnect-extension">Sign in with OpenVibe</a> to create a token.</p>`;
if (!viewer || viewer.kind !== 'user') return h`${intro}<p><a href="/auth/login?next=%2Fconnect-extension">Sign in with OpenVibe</a> to create a token.</p>`;
@@ -258,6 +258,34 @@ ${installs.length ? h`<table class="installs"><thead><tr><th>Name</th><th>Token<
@@ -258,6 +258,34 @@ ${installs.length ? h`<table class="installs"><thead><tr><th>Name</th><th>Token<
<td>${i.active ? h`<form method="post" action="/connect-extension/${i.id}/revoke">${hidden(csrf)}<button type="submit">Revoke</button></form>` : (i.revoked_at ? 'revoked' : 'expired')}</td></tr>`)}</tbody></table>` : h`<p class="empty">None yet.</p>`}`;
<td>${i.active ? h`<form method="post" action="/connect-extension/${i.id}/revoke">${hidden(csrf)}<button type="submit">Revoke</button></form>` : (i.revoked_at ? 'revoked' : 'expired')}</td></tr>`)}</tbody></table>` : h`<p class="empty">None yet.</p>`}`;
}
}
+const COMPANION_RELEASE = 'https://github.com/OpenVibers/OpenVibe.Extensions/releases/latest';
+const COMPANION_SOURCE = 'https://github.com/OpenVibers/OpenVibe.Extensions/tree/main/browser';
+
+/**
+ * /companion below its showcase (server/http/public.js draws the hero and the feature cards): what the OpenVibe
+ * Companion (OpenVibers/OpenVibe.Extensions `browser/`) sees and sends, how to install it, connecting it.
+ */
+function companion() {
+ return h`<h2 id="privacy">What it can see, and what it sends</h2>
+<ul>
+<li><strong>Permissions:</strong> the active tab (only after you click it), running its own code there when you press a button, and its own storage. It has no access to other tabs, your history or cookies, and no background process.</li>
+<li><strong>Sent to OpenVibe.Coupons:</strong> the tab's hostname when you open it (for example <code>shop.example</code>), and a worked or did not work report when you have connected it and say so.</li>
+<li><strong>Read from the page:</strong> the tab's address and title when you open it; the checkout page's coupon box, Apply button and total only when you press “Try these codes on this page”. Nothing read from the page leaves your browser.</li>
+<li><strong>Stored:</strong> only the optional connection token, on that device.</li>
+</ul>
+<h2 id="install">Install it</h2>
+<p>It is not in the browser stores yet, so it installs from its release file:</p>
+<ol>
+<li>Download the zip from the <a href="${COMPANION_RELEASE}">latest release</a> and unzip it.</li>
+<li><strong>Chrome, Edge or Brave:</strong> open <code>chrome://extensions</code>, turn on Developer mode, choose “Load unpacked” and pick the unzipped folder.</li>
+<li><strong>Firefox:</strong> open <code>about:debugging</code>, choose “This Firefox”, then “Load Temporary Add-on”, and pick the <code>manifest.json</code> in the folder.</li>
+<li>Pin it to the toolbar, open a shop's site and click it.</li>
+</ol>
+<h2>Connect it (optional)</h2>
+<p>Without connecting, it looks up and tries codes. <a href="/connect-extension">Connect it</a> to report whether a code worked, so the labels stay honest for everyone. The token can only look up and report, and you can revoke it at any time.</p>
+<p class="meta">Source code and the full privacy table: <a href="${COMPANION_SOURCE}">OpenVibers/OpenVibe.Extensions</a>. How codes are labelled: <a href="/about">the method</a>.</p>`;
+}
+
function watching({ merchants }) {
function watching({ merchants }) {
return h`<h1>Shops you watch</h1>
return h`<h1>Shops you watch</h1>
<p class="meta">Private to you. When a new code for one of these shops is listed, you get a notification in your OpenVibe inbox (unless you muted service notifications there).</p>
<p class="meta">Private to you. When a new code for one of these shops is listed, you get a notification in your OpenVibe inbox (unless you muted service notifications there).</p>
@@ -296,4 +324,4 @@ function message({ heading, text, action }) {
@@ -296,4 +324,4 @@ function message({ heading, text, action }) {
return h`<h1>${heading}</h1><p>${text}</p>${action ? h`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;
return h`<h1>${heading}</h1><p>${text}</p>${action ? h`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;
}
}
-module.exports = { home, merchant, couponPage, couponCard, about, submitForm, submitted, connect, watching, staff, message, STATUS_LABEL, SITE_SUMMARY };
+module.exports = { home, merchant, couponPage, couponCard, about, submitForm, submitted, connect, companion, COMPANION_RELEASE, watching, staff, message, STATUS_LABEL, SITE_SUMMARY };
@@ -157,7 +157,17 @@ const { boot, check, done } = require('./helpers/boot');
@@ -157,7 +157,17 @@ const { boot, check, done } = require('./helpers/boot');
assert.match(s.text, /<meta name="robots" content="noindex, nofollow">/, 'search results are not indexed');
assert.match(s.text, /<meta name="robots" content="noindex, nofollow">/, 'search results are not indexed');
const about = await t.get('/about');
const about = await t.get('/about');
assert.match(about.text, /confidence = \(1 \+ e \+ W\) \/ \(2 \+ e \+ W \+ F\)/);
assert.match(about.text, /confidence = \(1 \+ e \+ W\) \/ \(2 \+ e \+ W \+ F\)/);
- assert.match(about.text, /reads only the hostname/);
+ assert.match(about.text, /sends this site only the <strong>hostname<\/strong>/);
+ assert.match(about.text, /never rewrites links or adds referral tags/);
+ assert.ok(home.text.includes('href="/companion"'), 'the home links the Companion');
+ const cp = await t.get('/companion');
+ assert.strictEqual(cp.status, 200);
+ assert.ok(cp.text.includes('class="sc-hero') && /<h1[^>]*>The OpenVibe[\s\S]{0,80}Companion/.test(cp.text), 'the page opens with its showcase');
+ assert.strictEqual((cp.text.match(/<h1[\s>]/g) || []).length, 1, 'one h1');
+ assert.ok(cp.text.includes('https://github.com/OpenVibers/OpenVibe.Extensions/releases/latest') && cp.text.includes('chrome://extensions') && cp.text.includes('about:debugging'), 'how to install it');
+ assert.ok(cp.text.includes('https://openvibe.rent/board') && cp.text.includes('https://openvibe.food/delivery'), 'what it does across sites');
+ assert.match(cp.text, /<link rel="canonical" href="[^"]*\/companion">/);
+ assert.doesNotMatch(cp.text, /noindex/, 'indexable');
});
});
await check('every page carries the boost marker and script; the navbar signs in back to the current page', async () => {
await check('every page carries the boost marker and script; the navbar signs in back to the current page', async () => {