Back to Napcraft
SOURCE / PINNED RELEASE

Made of little things.

Napcraft

Release
ee47ac6a25df…
Author-recorded commit
410dea87e109…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce

Archive hash verified: 9e6e0cabb18d0d46…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/consent-recovery.mjs
import { mkdir, writeFile } from 'node:fs/promises';
import { blockPoint } from './direct-building.mjs';
import { fitFrame } from './browser-support.mjs';
const reload = async p => {
  await p.page.reload(); await p.page.locator('#stage iframe').waitFor();
  p.frame = await (await p.page.locator('#stage iframe').elementHandle()).contentFrame();
  await p.frame.locator('#app[data-ready=true]').waitFor();
};
const synced = (p,n) => p.frame.waitForFunction(n=>document.querySelector('canvas').dataset.confirmedEdits===String(n)&&document.querySelector('#app').dataset.pending==='0',n,{timeout:30000});
const join = async (p,code) => {
  await p.frame.locator('#join').click(); await p.frame.locator('#code').fill(code);
  await p.frame.locator('#confirmJoin').click();
  try {await p.frame.locator('#share').waitFor({timeout:30000});}
  catch(error){console.log('Join diagnostic',await p.frame.locator('body').innerText(),(await p.page.locator('body').innerText()).slice(-2000));throw error;}
};
export default async ({players,connectIdentity,approveBackendAccount,check}) => {
  const [a,b]=players;
  const approvalWait=Number(process.env.NAPCRAFT_APPROVAL_WAIT_MS ?? 20000);
  const signingWait=Number(process.env.NAPCRAFT_SIGN_WAIT_MS ?? 25000);
  await mkdir('.napplet-space/promotion',{recursive:true});
  await connectIdentity(a);
  await a.page.evaluate((signingWait)=>{
    const original=window.nostr.signEvent.bind(window.nostr);
    window.signingProbe={started:0,finished:0};
    window.nostr.signEvent=async event=>{
      window.signingProbe.started=Date.now();
      await new Promise(r=>setTimeout(r,signingWait));
      const result=await original(event);window.signingProbe.finished=Date.now();return result;
    };
  },signingWait);
  await a.frame.evaluate(()=>{
    const original=window.napplet.cvm.registry.call.bind(window.napplet.cvm.registry);
    window.creationCalls=[];let lose=true;
    window.napplet.cvm.registry.call=async(family,tool,args,options)=>{
      const result=await original(family,tool,args,options);
      if(args.operation==='createWorld') {
        window.creationCalls.push({args:structuredClone(args),options,result});
        if(lose&&!result.isError) {lose=false;throw Error('Test lost acknowledgement after world creation');}
      }
      return result;
    };
  });
  await a.frame.locator('#create').click(); await a.frame.locator('#name').fill('Patient builders');
  await a.frame.locator('#confirmCreate').click();
  await a.page.locator('#prompt[open] #confirm').waitFor();
  const start=Date.now();
  // Real elapsed user think-time at the actual host consent prompt, followed by
  // a slow NIP-07 approval. Both exceed the old 15-second UI deadline.
  await a.page.waitForTimeout(approvalWait);
  check(`host consent remains open for ${approvalWait} ms`,await a.frame.locator('#confirmCreate').isDisabled() && (await a.frame.locator('#error').innerText())==='');
  await approveBackendAccount(a,'worlds');
  await a.frame.locator('#retrySave').waitFor({timeout:90000});
  const signing=await a.page.evaluate(()=>window.signingProbe);
  check(`extension signing was allowed ${signingWait} ms`,signing.finished-signing.started>=signingWait);
  const first=await a.frame.evaluate(()=>window.creationCalls[0]);
  check('account-bound invocation uses two-minute UI timeout',first.options.timeoutMs===120000);
  check('world committed despite delayed approval and signing',!first.result.isError&&Boolean(first.result.structuredContent.instance));
  const pending=await a.frame.evaluate(async()=>JSON.parse(await window.napplet.storage.getItem('napcraft-pending-v1')));
  check('lost creation reply retains original intent',JSON.stringify(pending)===JSON.stringify(first.args));
  check('recovery explains safe retry instead of another create',(await a.frame.locator('#panel').innerText()).includes('exact same saved request'));
  await a.frame.locator('#retrySave').click();await a.frame.locator('#share').waitFor({timeout:30000});
  const calls=await a.frame.evaluate(()=>window.creationCalls);
  check('retry returns the same world without a duplicate request',calls.length===2&&JSON.stringify(calls[0].args)===JSON.stringify(calls[1].args)&&calls[0].result.structuredContent.instance===calls[1].result.structuredContent.instance);
  await a.frame.locator('#share').click();const code=await a.frame.locator('#shareCode').inputValue();await a.frame.locator('#close').click();
  await join(b,code);
  await Promise.all(players.map(p=>p.frame.waitForFunction(()=>document.querySelector('canvas').dataset.peerCount==='1')));
  check('independent second browser joins and sees the first',true);
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,2,8)});
  await Promise.all(players.map(p=>synced(p,1)));
  await b.frame.locator('canvas').click({position:await blockPoint(b.frame,4,8)});
  await Promise.all(players.map(p=>synced(p,2)));
  check('both players edits reach each other',true);
  await Promise.all(players.map(reload));
  for(const p of players) {await join(p,code);await synced(p,2);}
  check('saved world and edits survive both browser reloads',true);

  // An old failed creation from before the host fix. Never send the expired
  // request, change its expiry, or quietly mint another world.
  const legacy={...first.args,expiresAt:Math.floor(Date.now()/1000)-86400};
  await b.frame.evaluate(value=>window.napplet.storage.setItem('napcraft-pending-v1',value),JSON.stringify(legacy));
  await reload(b);
  await b.frame.evaluate(()=>{
    const original=window.napplet.cvm.registry.call.bind(window.napplet.cvm.registry);window.newCreations=0;
    window.napplet.cvm.registry.call=(f,t,a,o)=>{if(a.operation==='createWorld')window.newCreations++;return original(f,t,a,o);};
  });
  await b.frame.locator('#recover').click();
  check('expired creation has an explanation instead of a retry loop',await b.frame.locator('#retrySave').count()===0&&(await b.frame.locator('#panel').innerText()).includes('might already exist'));
  await fitFrame(b.page,390,844);
  await b.page.locator('#stage iframe').screenshot({path:'.napplet-space/promotion/recovery-phone.png'});
  await b.frame.locator('#keepSave').click();
  const kept=await b.frame.evaluate(async()=>JSON.parse(await window.napplet.storage.getItem('napcraft-kept-saves-v1')));
  check('setting aside preserves the exact expired request durably',JSON.stringify(kept[0].intent)===JSON.stringify(legacy));
  await b.frame.locator('#practice').click();await b.frame.locator('#worldMenu').click();
  check('kept recovery record does not prevent practice',await b.frame.locator('#kept').isVisible());
  await join(b,code);await synced(b,2);
  check('can join saved worlds while retaining an unresolved creation',true);
  await b.frame.locator('#worldMenu').click();await b.frame.locator('#create').click();
  await b.frame.locator('#differentWorld').click();await b.frame.locator('#confirmCreate').click();
  check('a separate world requires explicit acknowledgement',await b.frame.locator('#separateWorld').isVisible()&&(await b.frame.locator('#error').innerText()).includes('Confirm')&&await b.frame.evaluate(()=>window.newCreations)===0);
  await reload(b);await b.frame.locator('#kept').click();await b.frame.locator('#records button').first().click();
  const restored=JSON.parse(await b.frame.locator('#recoveryDetails').inputValue());
  check('recovery record survives reload without altered payload',JSON.stringify(restored.intent)===JSON.stringify(legacy));
  await b.frame.evaluate(()=>{
    const original=window.napplet.cvm.registry.call.bind(window.napplet.cvm.registry);
    window.recoveryReads=[];let conflicts=2;
    window.napplet.cvm.registry.call=(f,t,a,o)=>{
      if(a.operation==='readWorld'){
        window.recoveryReads.push(a.requestId);
        if(conflicts-->0)return Promise.resolve({isError:true,structuredContent:{ok:false,error:{code:'CONFLICT',message:'Test concurrent query'}}});
      }
      return original(f,t,a,o);
    };
  });
  await b.frame.locator('#recoveredCode').fill(code);await b.frame.locator('#checkCode').click();
  try { await b.frame.locator('#useRecovered').waitFor(); }
  catch(error) {console.log('Recovery diagnostic',await b.frame.locator('#panel').innerText());throw error;}
  check('recovered code asks the player to recognize the world', (await b.frame.locator('#panel').innerText()).includes('Patient builders'));
  check('recovery retries query conflicts with fresh read IDs',await b.frame.evaluate(()=>window.recoveryReads.length===3&&new Set(window.recoveryReads).size===3));
  await b.frame.locator('#useRecovered').click();await synced(b,2);
  const linked=await b.frame.evaluate(async()=>JSON.parse(await window.napplet.storage.getItem('napcraft-kept-saves-v1')));
  check('recognizing the saved world retains original request and links its code',linked[0].code===code&&JSON.stringify(linked[0].intent)===JSON.stringify(legacy));
  await writeFile('.napplet-space/consent-recovery.json',JSON.stringify({passed:true,hostApprovalWaitMs:approvalWait,signingWaitMs:signing.finished-signing.started,elapsedCreationMs:Date.now()-start,instance:first.result.structuredContent.instance,edits:2},null,2));
};