์ฑ
์: ํ๊ฒ ํ์ด์ง์ ์ฃผ์
๋์ด ์๋ฒ ๋ช
๋ น์ ๋ฐ์ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์์ค ์ ์กํ๋ค.
๋ชจ๋: src/arachne/client/{bookmarklet.js, executor.js, install.html} + ํ๊ฒ ๋ฑ๋ก(settings.TARGETS)
โ ๏ธ v1(ํ ๊ตฌํ, ์๋) =payload.scripteval. v2 ๋ชฉํ = ์ปค๋งจ๋ ์ธํฐํ๋ฆฌํฐ(specs/16) โ ํด๋ผ๋ TS ์ธํฐํ๋ฆฌํฐ(ES2017), ์๋ฒ๊ฐ ํ์ ์ปค๋งจ๋๋ก ํต์ผ ์ ์ด. ๋ถ๋งํฌ๋ฆฟ์ ๊ธฐ๋ณธ์ ์ผ๋ก v1ยทv2 ๋์ผ(์์ ๋ก๋,<script src>๋ง) โ ๋จ v2์D-csp-script-src๋์์ฉ ๋ ๋ฒ์งธ ํํ(์ธ๋ผ์ธ, ๋ก๋ ์ ์ฒด๋ฅผ ๋ณธ๋ฌธ์ ์ง์ ์๋ฒ ๋ฉ โ ๋ ์ด์ "์์"๊ฐ ์๋)๊ฐ ์ถ๊ฐ๋๋ค(docs/00_ARCHITECTURE.mdD-csp-script-src,specs/15ยง4).
JSON(ํ๊ฒฝ๋ณ์ TARGETS ๋๋ .env). 1์ฐจ ๋ฌดUI โ ์ ์ ๋ฑ๋ก:
{ "example.com": { "version": 3, "script": "return {title: document.title}", "actions": [{"type":"scroll","args":{"to":"bottom"}}] } }- ์คํ๊ฒ ์์(m.news.nate.com ์ต์ ๋ด์ค)๋
tests/e2e์ ๋ผ์ด๋ธ ํ ์คํธ(-m live) ์ฐธ์กฐ.
javascript:(function(){var s=document.createElement('script');
s.src='https://<server>/agent/loader.js?op=op_a1b2c3...';document.body.appendChild(s);})();- ์ค์น๋ณธ๋ณ๋ก op ํ ํฐ์ด ๋ฐํ ๋ถ๋งํฌ๋ฆฟ(์๋ฒ ๋ฐ๊ธ,
specs/15ยง1). ์ด๋ ๋๋ฉ์ธ์ ์คํํด๋ ๊ฐ์ op โ ๋๋ฉ์ธ ๋๋ ํตํฉ. - loader.js ๋์(
specs/15ยง4): client_id ํ๋ณด(localStorage ๋๋คยทGA์, ์ง๋ฌธ ๋ฏธ์ฌ์ฉ) โPOST /agent/register(op+client) โGET /agent/payloadโ actions/script โ collect. ๋ชจ๋ collect ๋ดํฌ์ op_idยทclient_idยทtask_id ๋๋ด(๋งค๊ฐ).
window._aaq = window._aaq || []; // Matomo _paq ํจํด: ๋ก๋ ์ ๋ฒํผ๋ง(์ ์ค 0)
// 1) actions ์ํ โ flush #1
for (const a of payload.actions) await runAction(a);
_aaq.push(['collect', {stage:'action', data: collectedSoFar}]);
// 2) script ์คํ โ flush #2
const result = await new Function('return (async()=>{'+payload.script+'})()')();
_aaq.push(['collect', {stage:'script', data: result}]);- ์คํ ์์ ๋ถ๋ณ: action โ collect โ script โ collect (
00_ARCHITECTURE ยง4.3). - runAction ํ์ดํธ๋ฆฌ์คํธ:
scrollยทclickยทwaitยทswipe(m.nate.com ๋ญํน ๋ณธ๋ฌธ ์ค์์ดํ,docs/80GT-real์์ ์๊ตฌ).swipe={args:{to,times}} โ ํฐ์น ์ค์์ดํ(ํฝ์ค์ฒ์์ scroll๋ก ๋๋ฑ ์ฒ๋ฆฌ). ๊ทธ ์ธ ๋์์ 2์ฐจ ํ์ฅ.
- ๋ช
๋ น ์์ :
<script src="/agent/payload.js?...">โ_aaq.push(['payload',{...}]). - ๊ฒฐ๊ณผ ์ ์ก:
fetch PUT /api/v1/collect; ์ฐจ๋จ ์ ํด๋ฐฑnew Image().src="/api/v1/collect.json?envelope=<urlenc>". - ํ๊ณ(์ด์): GET ๋น์ฝ URL ๊ธธ์ด โ ๋์ฉ๋์ ์ฒญํฌ/์์ถ(๊ฒ์ฆ ์ด๋ ค์ฐ๋ฉด BLOCKER ๊ธฐ๋ก ํ ์งํ).
v2 loader.v2.js(ts/loader.ts ๋จ์ผ ์์ค)๋ ์ ๋ขฐ ๊ฒฝ๊ณ(์ปจํธ๋กค ์๋ฒ ์๋ต)๋ฅผ ํ๋๋ํ๋ค.
scriptopt-in(D-script-optin):new Functioneval์ ๊ธฐ๋ณธ ์ฐจ๋จ, ๋ก๋src ?allowScript=1์์๋ง.beacon์ค๋ฆฌ์ง ์ ํ(D-beacon-origin): ์ก์ถ ๋์์SERVER_ORIGIN๋ง(์์ exfil ์ฐจ๋จ).- ๋์ crypto(D-client-crypto): client_idยทtask_idยทJSONP ์ฝ๋ฐฑ๋ช
crypto.getRandomValues์ฐ์ . - ์ค๋ณต ์ฃผ์
๊ฐ๋: ์๊ฐ
commands.js๋ก๋ยทํด๋ง ๋ถ์์ฉ์ 1ํ๋ง(__arachneV2Loaded). op๋ณด๊ฐ ์๋ํ์ด์ฆ(D-op-sanitize): v1/agent/loader.js?op=๋ ์๋ฒ๊ฐop๋ฅผ ํ์ดํธ๋ฆฌ์คํธ ์ ๊ทํ ํ ๋ณธ๋ฌธ ์ฝ์ (specs/15) โ ๋ฐ์ฌํ ์ธ์ ์ ์ฐจ๋จ.- iframe ํ์ด์ง ์ ์ด(D-frame, v1.1.0):
frame_extract/frame_tour๋ ๊ฐ์ ์ถ์ฒ ์์ธ๋ง iframe ๋ก๋(cross-origin ํด๋ผ ์ฆ์ ๊ฑฐ๋ถ + ์๋ฒ 422).extractItems๋ฅผ iframecontentDocumentroot๋ก ์ฌ์ฌ์ฉ. ์ต์์ ๋ฌธ์ ๋ถ๋ณ โ ๋ก๋/์ธ์ ๋น๋จ์ (specs/22).
- ์ต์ ์ ์ : ๋ถ๋งํฌ๋ฆฟ ๋๋๊ทธ ์๋ด ํ ํ์ด์ง. โ ๋์์ธ ์์คํ ยทํ๋ฆฌ๋ฏธ์ UI ๊ธ์ง(๊ณจ๋ํ๋ ์ดํ ).
- โ ๋ถ๋งํฌ๋ฆฟ์ ์์ง ๋ก์ง ํ๋์ฝ๋ฉ(์๋ฒ ๋์ ๋ฐํ ์์น).
- โ runAction ํ์ดํธ๋ฆฌ์คํธ ๋ฐ ์์ ๋์.
- โ install.html ๊ณผ์ ๋์์ธ.
- GT-client: executor 2๋จ๊ณ flush ์์ยท
_aaq๋ฒํผ๋ง(jsdom ๋๋ E2E). ์ ์ฒด ํฉ๋ฅ๋docs/80GT-E2E.
ํด๋ผ์ด์ธํธ E2E๋ Phase 2(Playwright). 1์ฐจ๋ ์๋ฒ์ธก ์๋(GT-seed)๋ก ํฉ๋ฅ ์ฆ๋ช .