Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/idea-market-share-qa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Idea Market Share Landing QA
on:
pull_request:
paths:
- share/idea-market/**
- tests/idea-market-share.test.mjs
- .github/workflows/idea-market-share-qa.yml
push:
branches: [main]
paths:
- share/idea-market/**
- tests/idea-market-share.test.mjs
- .github/workflows/idea-market-share-qa.yml
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
share/idea-market
tests/idea-market-share.test.mjs
sparse-checkout-cone-mode: true
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: node --test tests/idea-market-share.test.mjs
32 changes: 32 additions & 0 deletions share/idea-market/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none';">
<title>Open an Idea Market idea | SK APPS</title>
<style>
:root{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color-scheme:light}
*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:#f4f5ff;color:#252144}
main{width:min(100% - 32px,460px);padding:32px 24px;background:white;border-radius:20px;box-shadow:0 12px 38px #15112a16;text-align:center}
h1{font-size:1.65rem;margin:0 0 12px}p{line-height:1.5;color:#595475}
a.action{display:block;margin:14px 0;padding:14px 18px;border-radius:12px;text-decoration:none;font-weight:700;background:#6536c9;color:white}
a.secondary{background:#f2edff;color:#5133ac}
small{display:block;margin-top:16px;color:#656078}#error{color:#a12c47}
</style>
<script src="referral.js" defer></script>
</head>
<body>
<main>
<h1 id="title">A friend shared an idea</h1>
<p id="intro">Explore this idea in Idea Market. Paid or private details are never shown on this page.</p>
<p id="error" role="status" hidden>We couldn't read this invitation. Please ask your friend to share it again.</p>
<a id="open-app" class="action" href="#" hidden>Open this idea in the app</a>
<a class="action secondary" href="https://play.google.com/store/apps/details?id=jp.appdevelopsk.ideamarket">Install Idea Market on Google Play</a>
<a class="action secondary" href="https://appdevelopsk.com/apps">Explore SK APPS</a>
<small>After installing, return to the shared link to open the idea.</small>
</main>
</body>
</html>
21 changes: 21 additions & 0 deletions share/idea-market/referral.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';
// This static landing does NOT request idea details and does not track tokens.
// Only harmless alphanumeric IDs are used to build an app-specific URI.
(() => {
const id = new URLSearchParams(location.search).get('id') ?? '';
const validId = /^[a-zA-Z0-9_-]{1,96}$/.test(id);
const open = document.getElementById('open-app');
const error = document.getElementById('error');
if (validId) {
open.href = 'ideamarket://open/ideas/' + encodeURIComponent(id);
open.hidden = false;
} else {
error.hidden = false;
}
if (navigator.language?.toLowerCase().startsWith('ja')) {
document.getElementById('title').textContent = 'アイデアが共有されました';
document.getElementById('intro').textContent = 'Idea Marketアプリで共有されたアイデアを開けます。購入前の非公開情報はこのページに表示しません。';
open.textContent = 'アプリでアイデアを開く';
error.textContent = '共有リンクを確認できません。もう一度共有してもらってください。';
}
})();
17 changes: 17 additions & 0 deletions tests/idea-market-share.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
const html=readFileSync(new URL('../share/idea-market/index.html',import.meta.url),'utf8');
const js=readFileSync(new URL('../share/idea-market/referral.js',import.meta.url),'utf8');
test('share page gives real Play acquisition fallback and an explicit app open',()=>{
assert.match(html,/play\.google\.com\/store\/apps\/details\?id=jp\.appdevelopsk\.ideamarket/);
assert.match(html,/id="open-app"/);
assert.match(html,/script src="referral\.js"/);
assert.match(html,/noindex,nofollow/);
});
test('identifiers cannot inject scripts or arbitrary external redirects',()=>{
assert.match(js,/\[a-zA-Z0-9_-\]\{1,96\}/);
assert.match(js,/encodeURIComponent\(id\)/);
assert.match(js,/ideamarket:\/\/open\/ideas\//);
assert.doesNotMatch(js,/innerHTML|window\.location\s*=|fetch\(/);
});
Loading