diff --git a/.github/workflows/idea-market-share-qa.yml b/.github/workflows/idea-market-share-qa.yml new file mode 100644 index 0000000..47c0367 --- /dev/null +++ b/.github/workflows/idea-market-share-qa.yml @@ -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 diff --git a/share/idea-market/index.html b/share/idea-market/index.html new file mode 100644 index 0000000..474c8d2 --- /dev/null +++ b/share/idea-market/index.html @@ -0,0 +1,32 @@ + + + + + + + + +Open an Idea Market idea | SK APPS + + + + +
+

A friend shared an idea

+

Explore this idea in Idea Market. Paid or private details are never shown on this page.

+ + +Install Idea Market on Google Play +Explore SK APPS +After installing, return to the shared link to open the idea. +
+ + diff --git a/share/idea-market/referral.js b/share/idea-market/referral.js new file mode 100644 index 0000000..d2aa1fc --- /dev/null +++ b/share/idea-market/referral.js @@ -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 = '共有リンクを確認できません。もう一度共有してもらってください。'; + } +})(); diff --git a/tests/idea-market-share.test.mjs b/tests/idea-market-share.test.mjs new file mode 100644 index 0000000..beaa032 --- /dev/null +++ b/tests/idea-market-share.test.mjs @@ -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\(/); +});