@01.works/visual-review 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{r as e}from"./install-Crgjt0uw.js";import{i as t,r as n,t as r}from"./reviewer-auth-presentation-CAm-YbaR.js";function i(e){let t=[`01works-reviewer-device-proof-v1`,e.purpose,e.deviceSessionId,e.challengeId,e.challenge,String(e.counter),String(e.expiresAt),e.invitationId,e.origin,e.pathname];return new TextEncoder().encode(t.map(e=>`${e.length}:${e}`).join(``))}const a=`reviewer-device-credentials-v1`;function o(){try{return globalThis.indexedDB?new l(globalThis.indexedDB):null}catch{return null}}async function s(e=globalThis.crypto){if(!e?.subtle||typeof e.randomUUID!=`function`)throw Error(`Visual Review device authentication requires Web Crypto`);let t=await e.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},!1,[`sign`,`verify`]);if(!(t instanceof Object)||!(`privateKey`in t)||!(`publicKey`in t)||t.privateKey.extractable)throw Error(`Visual Review could not create a non-extractable device key`);let n=await e.subtle.exportKey(`jwk`,t.publicKey);if(n.kty!==`EC`||n.crv!==`P-256`||typeof n.x!=`string`||typeof n.y!=`string`)throw Error(`Visual Review received an invalid device public key`);return{deviceSessionId:e.randomUUID(),privateKey:t.privateKey,publicKey:{kty:`EC`,crv:`P-256`,x:n.x,y:n.y}}}async function c(e,t,n=globalThis.crypto){if(e.extractable||e.type!==`private`||e.algorithm.name!==`ECDSA`||!e.usages.includes(`sign`))throw Error(`Visual Review device key is invalid`);let r=await n.subtle.sign({name:`ECDSA`,hash:`SHA-256`},e,i(t));return p(new Uint8Array(r))}var l=class{factory;constructor(e){this.factory=e}async get(e){let t=await this.open();try{return u(await d(t.transaction(a,`readonly`).objectStore(a).get(e)))}finally{t.close()}}async set(e,t){let n=await this.open();try{await f(n.transaction(a,`readwrite`),n=>{n.put(t,e)})}finally{n.close()}}async delete(e){let t=await this.open();try{await f(t.transaction(a,`readwrite`),t=>{t.delete(e)})}finally{t.close()}}open(){return new Promise((e,t)=>{let n=this.factory.open(`01works-visual-review`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(a)||n.result.createObjectStore(a)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error??Error(`IndexedDB open failed`)),n.onblocked=()=>t(Error(`IndexedDB upgrade blocked`))})}};function u(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;let t=e;return typeof t.deviceSessionId!=`string`||!Number.isSafeInteger(t.expiresAt)||(t.expiresAt??0)<=Date.now()||t.invitationId!==void 0&&typeof t.invitationId!=`string`||!t.privateKey||t.privateKey.type!==`private`||t.privateKey.extractable||t.privateKey.algorithm.name!==`ECDSA`||!t.privateKey.usages.includes(`sign`)?null:t}function d(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??Error(`IndexedDB request failed`))})}function f(e,t){return new Promise((n,r)=>{e.oncomplete=()=>n(),e.onerror=()=>r(e.error??Error(`IndexedDB transaction failed`)),e.onabort=()=>r(e.error??Error(`IndexedDB transaction aborted`)),t(e.objectStore(a))})}function p(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replace(/=+$/u,``)}var m=class extends Error{code;status;constructor(e,t,n){super(t),this.code=e,this.status=n,this.name=`HostedBootstrapError`}};function h(e={}){return g(e)}function g(t={},n={}){let r=e(t.serviceUrl??`https://review.01.works`),i=t.fetch??globalThis.fetch,a=n.now??Date.now,s=new Map,c=n.reviewerDeviceStore===void 0?o():n.reviewerDeviceStore,l=n.crypto??globalThis.crypto,u=n.reviewerSessionStorage===void 0?V(H(),U()):V(n.reviewerSessionStorage);if(typeof i!=`function`)throw Error(`Visual Review bootstrap requires the Fetch API`);return{async resolve(e){let t=M(e),o=await F(r,i,t,e.signal),d=a(),f={serviceUrl:r,...n.instantAuthDatabaseFactory===void 0?{}:{databaseFactory:n.instantAuthDatabaseFactory},redeemEmailInvitation:async e=>(await w(r,i,e)).refreshToken,acceptInvitation:async e=>{let t=await i(`${r}/v1/invitations/accept`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${e.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:e.invitationId,...e.displayName===void 0?{}:{displayName:e.displayName}}),cache:`no-store`,credentials:`omit`,signal:e.signal}),n=await Y(t);if(!t.ok)throw new m(X(n,`error`)??`INVITATION_ACCEPT_FAILED`,X(n,`message`)??`리뷰 초대 수락에 실패했습니다.`,t.status)},resolveReviewPage:e=>P(r,i,e),...e.collaboration?.cursors===!0||e.collaboration?.chat===!0?{resolveCollaborationRoom:e=>N(r,i,e)}:{}},p=o.apiVersion===2?await _({config:o,convexRuntimeFactory:n.convexRuntimeFactory??(await import(`./convex-runtime-8kViWXIL.js`)).createConvexReviewRuntime,fetcher:i,memorySessions:s,reviewerDeviceStore:c,cryptoProvider:l,request:e,legacySessionStorages:u,serviceUrl:r}):{...await(n.instantRuntimeFactory??(await import(`./instant-runtime-CTh_DTHR.js`)).createInstantReviewRuntime)(o.instantAppId,f).resolve(e),sessionScope:`browser`,sessionExpiresAt:o.invitationExpiresAt},h=p.resolvePage;if(!h)return p;let g=async t=>{await F(r,i,{invitationId:e.invitationId,origin:t.location.origin,pathname:t.location.pathname},t.signal),d=a()};return{...p,resolvePage:async e=>(a()-d>=3e5&&await g(e),h(e))}}}}async function _(e){let t=e.config.accessScope?.kind===`project`?e.config.accessScope:null,n=B(e.serviceUrl,e.request.location.origin,t?.locator??e.config.sessionLocator??e.request.invitationId),i=B(e.serviceUrl,e.request.location.origin,e.request.invitationId);q(e.legacySessionStorages,n),i!==n&&q(e.legacySessionStorages,i);let a;if(t)a=await W(e.reviewerDeviceStore,e.memorySessions,n),a&&!a.invitationId&&(await K(e.reviewerDeviceStore,e.memorySessions,n),a=null),a||=await v(e,t.locator,await s(e.cryptoProvider));else if(e.request.emailToken){await r(e.request.authHost,e.request.signal,e.request.styleNonce);let t=await s(e.cryptoProvider);try{a=await C(e.serviceUrl,e.fetcher,{invitationId:e.request.invitationId,emailToken:e.request.emailToken,origin:e.request.location.origin,pathname:e.request.location.pathname,signal:e.request.signal,device:t})}catch(n){if(!(n instanceof m)||n.code!==`INVITATION_TOKEN_USED`)throw n;a=await y(e,t)}}else a=await W(e.reviewerDeviceStore,e.memorySessions,n),!a&&i!==n&&(a=await W(e.reviewerDeviceStore,e.memorySessions,i),a&&await K(e.reviewerDeviceStore,e.memorySessions,i));a||=await y(e,await s(e.cryptoProvider));let o=t?a.invitationId:e.request.invitationId;if(!o)throw new m(`INVALID_PROJECT_ACCESS_RESPONSE`,`프로젝트 리뷰 인증 응답이 올바르지 않습니다.`,502);a={...a,invitationId:o},await G(e.reviewerDeviceStore,e.memorySessions,n,a);let{expiresAt:c}=a,l={...e.request,invitationId:o,emailToken:void 0};return{...await e.convexRuntimeFactory(e.config.convexUrl,{fetchAccessToken:t=>b(e.serviceUrl,e.fetcher,a,t,e.cryptoProvider,()=>K(e.reviewerDeviceStore,e.memorySessions,n))}).resolve(l),sessionScope:`browser`,sessionExpiresAt:c??e.config.invitationExpiresAt,clearLocalSession:()=>{K(e.reviewerDeviceStore,e.memorySessions,n);let t=new AbortController,r=setTimeout(()=>t.abort(),5e3);S(e.serviceUrl,e.fetcher,a,{invitationId:o,origin:e.request.location.origin,pathname:e.request.location.pathname,forceRefreshToken:!1,signal:t.signal},e.cryptoProvider).catch(()=>void 0).finally(()=>clearTimeout(r))}}}async function v(e,t,r){let i={reviewLocator:t,pathname:e.request.location.pathname,signal:e.request.signal};return n(e.request.authHost,e.request.signal,{requestCode:t=>E(e.serviceUrl,e.fetcher,{...i,email:t}),verifyCode:(t,n,a,o)=>D(e.serviceUrl,e.fetcher,{...i,challengeId:t,attemptId:n,email:a,code:o,device:r})},e.request.styleNonce)}async function y(e,n){let r={invitationId:e.request.invitationId,pathname:e.request.location.pathname,signal:e.request.signal};return t(e.request.authHost,e.request.signal,{requestCode:()=>T(e.serviceUrl,e.fetcher,r),verifyCode:(t,i)=>O(e.serviceUrl,e.fetcher,{...r,challengeId:t,code:i,device:n})},e.request.styleNonce)}async function b(e,t,n,r,i,a){let o;try{o=await x(e,t,n.deviceSessionId,r)}catch(e){throw e instanceof m&&[401,403,409,410].includes(e.status)&&await a(),e}let s=await c(n.privateKey,{purpose:`token`,deviceSessionId:n.deviceSessionId,...o,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname},i),l=await t(`${e}/v1/convex/token`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,deviceSessionId:n.deviceSessionId,proof:{deviceSessionId:n.deviceSessionId,...o,signature:s}}),cache:`no-store`,credentials:`omit`,signal:r.signal}),u=await Y(l),d=X(u,`token`);if(!l.ok||!d||d.length>16e3)throw[401,403,409,410].includes(l.status)&&await a(),new m(X(u,`error`)??`CONVEX_AUTH_FAILED`,X(u,`message`)??`리뷰 인증을 갱신하지 못했습니다.`,l.status);return d}async function x(e,t,n,r,i=`token`){let a=await t(`${e}/v1/convex/token/challenge`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({deviceSessionId:n,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,purpose:i}),cache:`no-store`,credentials:`omit`,signal:r.signal}),o=await Y(a),s=X(o,`challengeId`),c=X(o,`challenge`),l=Z(o,`counter`),u=z(Z(o,`expiresAt`));if(!a.ok||!s||!c||!Number.isSafeInteger(l)||l<1||u==null)throw new m(X(o,`error`)??`DEVICE_CHALLENGE_FAILED`,X(o,`message`)??`리뷰 기기 인증을 시작하지 못했습니다.`,a.status);return{challengeId:s,challenge:c,counter:l,expiresAt:u}}async function S(e,t,n,r,i){let a=await x(e,t,n.deviceSessionId,r,`revoke`),o=await c(n.privateKey,{purpose:`revoke`,deviceSessionId:n.deviceSessionId,...a,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname},i);if(!(await t(`${e}/v1/convex/session/revoke`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({deviceSessionId:n.deviceSessionId,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,proof:{deviceSessionId:n.deviceSessionId,...a,signature:o}}),cache:`no-store`,credentials:`omit`,keepalive:!0,signal:r.signal})).ok)throw Error(`Reviewer device revocation failed`)}async function C(e,t,n){let r=await t(`${e}/v1/invitations/redeem-email`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,emailToken:n.emailToken,pathname:n.pathname,deviceSessionId:n.device.deviceSessionId,devicePublicKey:n.device.publicKey}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await Y(r);if(!r.ok)throw new m(X(i,`error`)??`INVITATION_REDEEM_FAILED`,X(i,`message`)??`리뷰 초대를 시작하지 못했습니다.`,r.status);let a=X(i,`deviceSessionId`);if(!a||a!==n.device.deviceSessionId)throw new m(`INVALID_INVITATION_REDEEM_RESPONSE`,`잘못된 초대 인증 응답입니다.`,502);let o=z(Z(i,`expiresAt`));if(o===null)throw new m(`INVALID_INVITATION_REDEEM_RESPONSE`,`잘못된 초대 인증 응답입니다.`,502);if(o===void 0)throw new m(`INVALID_INVITATION_REDEEM_RESPONSE`,`device session 만료 시각이 필요합니다.`,502);return{deviceSessionId:a,privateKey:n.device.privateKey,expiresAt:o}}async function w(e,t,n){let r=await t(`${e}/v1/invitations/redeem-email`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,emailToken:n.emailToken,pathname:n.pathname}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await Y(r),a=X(i,`refreshToken`);if(!r.ok||!a||a.length>4096)throw new m(X(i,`error`)??`INVITATION_REDEEM_FAILED`,X(i,`message`)??`리뷰 초대를 시작하지 못했습니다.`,r.status);return{refreshToken:a}}async function T(e,t,n){let r=await k(e,t,`start`,n),i=X(r,`challengeId`),a=X(r,`emailHint`);if(!i||!a)throw new m(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);return{challengeId:i,emailHint:a}}async function E(e,t,n){let r=await A(e,t,`start`,n),i=X(r,`challengeId`),a=X(r,`attemptId`),o=X(r,`emailHint`);if(!i||!a||!o)throw new m(`INVALID_PROJECT_ACCESS_RESPONSE`,`프로젝트 리뷰 인증 응답이 올바르지 않습니다.`,502);return{challengeId:i,attemptId:a,emailHint:o}}async function D(e,t,n){let r=await A(e,t,`verify`,n),i=X(r,`deviceSessionId`),a=X(r,`invitationId`),o=z(Z(r,`expiresAt`));if(i!==n.device.deviceSessionId||!a||o==null)throw new m(`INVALID_PROJECT_ACCESS_RESPONSE`,`프로젝트 리뷰 인증 응답이 올바르지 않습니다.`,502);return{deviceSessionId:i,privateKey:n.device.privateKey,invitationId:a,expiresAt:o}}async function O(e,t,n){let r=await k(e,t,`verify`,n),i=X(r,`deviceSessionId`);if(!i||i!==n.device.deviceSessionId)throw new m(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);let a=z(Z(r,`expiresAt`));if(a===null)throw new m(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);if(a===void 0)throw new m(`INVALID_RECOVERY_RESPONSE`,`device session 만료 시각이 필요합니다.`,502);return{deviceSessionId:i,privateKey:n.device.privateKey,expiresAt:a}}async function k(e,t,n,r){let{signal:i,...a}=r,o=a;if(`device`in a&&j(a.device)){let{device:e,...t}=a;o={...t,deviceSessionId:e.deviceSessionId,devicePublicKey:e.publicKey}}let s=await t(`${e}/v1/invitations/recovery/${n}`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify(o),cache:`no-store`,credentials:`omit`,signal:i}),c=await Y(s);if(!s.ok)throw new m(X(c,`error`)??`RECOVERY_FAILED`,X(c,`message`)??`리뷰 접속을 복구하지 못했습니다.`,s.status);if(!c||typeof c!=`object`||Array.isArray(c))throw new m(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);return c}async function A(e,t,n,r){let{signal:i,...a}=r,o=a;if(`device`in a&&j(a.device)){let{device:e,...t}=a;o={...t,deviceSessionId:e.deviceSessionId,devicePublicKey:e.publicKey}}let s=await t(`${e}/v1/reviewer/access/${n}`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify(o),cache:`no-store`,credentials:`omit`,signal:i}),c=await Y(s);if(!s.ok)throw new m(X(c,`error`)??`PROJECT_ACCESS_FAILED`,X(c,`message`)??`프로젝트 리뷰에 로그인하지 못했습니다.`,s.status);if(!c||typeof c!=`object`||Array.isArray(c))throw new m(`INVALID_PROJECT_ACCESS_RESPONSE`,`프로젝트 리뷰 인증 응답이 올바르지 않습니다.`,502);return c}function j(e){return!!(e&&typeof e==`object`&&`deviceSessionId`in e&&`privateKey`in e&&`publicKey`in e)}function M(e){return{invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname}}async function N(e,t,n){let r=await t(`${e}/v1/collaboration/room`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${n.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,pageId:n.pageId,scopeId:n.scopeId}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await Y(r);if(!r.ok)throw new m(X(i,`error`)??`COLLABORATION_ROOM_FAILED`,X(i,`message`)??`실시간 협업 연결에 실패했습니다.`,r.status);if(!i||typeof i!=`object`||Array.isArray(i))throw new m(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);let a=i,o=X(a,`id`);if(!o||!/^vr_[A-Za-z0-9_-]{43}$/u.test(o)||!Number.isSafeInteger(a.expiresAt))throw new m(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);return{id:o,expiresAt:a.expiresAt}}async function P(e,t,n){let r=await t(`${e}/v1/pages/resolve`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${n.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,origin:n.origin,pathname:n.pathname}),cache:`no-store`,credentials:`omit`,signal:n.signal});if(r.ok)return;let i=await Y(r);throw new m(X(i,`error`)??`PAGE_RESOLVE_FAILED`,X(i,`message`)??`리뷰 페이지를 준비하지 못했습니다.`,r.status)}async function F(e,t,n,r){let i=await t(`${e}/v1/bootstrap`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify(n),cache:`no-store`,credentials:`omit`,signal:r}),a=await Y(i);if(!i.ok)throw new m(X(a,`error`)??`BOOTSTRAP_FAILED`,X(a,`message`)??`Visual Review bootstrap에 실패했습니다.`,i.status);return I(a)}function I(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);let t=e;if(typeof t.apiVersion==`number`&&t.apiVersion!==1&&t.apiVersion!==2)throw new m(`UNSUPPORTED_SERVICE_VERSION`,`이 Visual Review 위젯 버전은 더 이상 지원되지 않습니다. 사이트의 @01.works/visual-review를 업데이트해 주세요.`,502);if(t.apiVersion===2){let e=J(t.convexUrl),n=z(t.invitationExpiresAt),r=L(t.accessScope),i=R(t.sessionLocator);if(t.provider!==`convex`||!e||n===null)throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{apiVersion:2,provider:`convex`,convexUrl:e,...n===void 0?{}:{invitationExpiresAt:n},...r===void 0?{}:{accessScope:r},...i===void 0?{}:{sessionLocator:i}}}let n=X(t,`instantAppId`),r=z(t.invitationExpiresAt),i=L(t.accessScope),a=R(t.sessionLocator);if(t.apiVersion!==1||!n||!Q(n)||r===null)throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{apiVersion:1,instantAppId:n,...r===void 0?{}:{invitationExpiresAt:r},...i===void 0?{}:{accessScope:i},...a===void 0?{}:{sessionLocator:a}}}function L(e){if(e===void 0)return;if(!e||typeof e!=`object`||Array.isArray(e))throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);let t=e,n=X(t,`locator`);if(t.kind!==`project`||!n||n.length>200)throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{kind:`project`,locator:n}}function R(e){if(e!==void 0){if(typeof e!=`string`||!e.trim()||e.length>200)throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return e.trim()}}function z(e){if(e!==void 0)return Number.isSafeInteger(e)&&e>0?e:null}function B(e,t,n){return`01works.visual-review.convex-session.v1:${JSON.stringify([e,t,n])}`}function V(...e){return e.filter(e=>e!==null)}function H(){try{return globalThis.localStorage??null}catch{return null}}function U(){try{return globalThis.sessionStorage??null}catch{return null}}async function W(e,t,n){try{let t=await e?.get(n);if(t)return t}catch{}let r=t.get(n)??null;return r&&r.expiresAt<=Date.now()?(t.delete(n),null):r}async function G(e,t,n,r){t.set(n,r);try{await e?.set(n,r)}catch{}}async function K(e,t,n){t.delete(n);try{await e?.delete(n)}catch{}}function q(e,t){for(let n of e)try{n.removeItem(t)}catch{}}function J(e){if(typeof e!=`string`||!e.trim())return null;try{let t=new URL(e.trim());return t.origin!==e.trim()||t.protocol!==`https:`||!t.hostname.endsWith(`.convex.cloud`)?null:t.origin}catch{return null}}async function Y(e){try{return await e.json()}catch{throw new m(`INVALID_BOOTSTRAP_RESPONSE`,`JSON bootstrap 응답이 필요합니다.`,502)}}function X(e,t){let n=Z(e,t);return typeof n==`string`&&n.trim()?n.trim():null}function Z(e,t){if(!(!e||typeof e!=`object`||Array.isArray(e)))return e[t]}function Q(e){return/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}export{h as createHostedReviewRuntime};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./install-DaKugI9S.js";export{e as installVisualReview};
1
+ import{t as e}from"./install-Crgjt0uw.js";export{e as installVisualReview};
@@ -1,2 +1,2 @@
1
1
  const e=`https://review.01.works`;function t(e){let t=new URL(e),n=t.hostname===`localhost`||t.hostname===`127.0.0.1`||t.hostname.endsWith(`.test`)||t.hostname.endsWith(`.local`);if(t.protocol!==`https:`&&!(n&&t.protocol===`http:`))throw Error(`Visual Review serviceUrl must use HTTPS outside local development`);if(t.username||t.password||t.search||t.hash)throw Error(`Visual Review serviceUrl must be a plain origin or base path`);return t.toString().replace(/\/+$/,``)}const n=()=>{let e=Promise.resolve(null);return{ready:e,refresh:()=>e,dispose(){}}},r=new Map,i=6e4,a=1e4,o=new WeakMap;function s(e={}){if(e.enabled===!1||typeof window>`u`||typeof document>`u`)return n();let r=!1,i=0,a=null,o=null,s=null,_=null,v=!1,y=!1,b=null,x=null,E=null,k=t(e.serviceUrl??`https://review.01.works`),j=e.activationParam??`visual-review`,M=ue(window),N=de(window),F=w(window.location);if(!(ie(k,j,F,N,M,e.runtime!==void 0)??T(D(k,F.origin)))){let t={reason:`missing-activation`,origin:F.origin};return P(e.build).mode===`development`&&c(document,t.origin),l(e.onInactive,t),n()}let I=new WeakMap,L=new Map,R=pe(e.styleNonce,document),z=S(window.location,j,e.pageIdentity),B=te(window,t=>{let n=S(t,j,e.pageIdentity);if(n===z)return;let r=A(j,t.href);r&&(y=!1,L.set(t.origin,r)),z=n,V(t)}),ce=t=>{if(!(r||!_)){if(t===`membership-revoked`){let e=w(window.location),t=L.get(e.origin);t&&(O(k,e.origin,t),L.delete(e.origin),q(k,e.origin,N,M,t));return}z=S(window.location,j,e.pageIdentity),W()}};return{ready:W(),refresh(){return r?Promise.resolve(null):(z=S(window.location,j,e.pageIdentity),V(w(window.location),!0))},dispose(){r||(r=!0,i+=1,U(),_?.remove(),_=null,E=null,x?.remove(),x=null,L.clear(),b?.dispose(),b=null,B.unsubscribe())}};function V(e,t=!1){return!_||!E||E.origin!==e.origin||A(j,e.href)?W():H(_,E,e,t)}function H(t,n,r,c){let l=++i;a!==o&&a?.abort();let d=new AbortController;a=d;let f=u(e.build,r);t.beginPageTransition();let p=(async()=>{try{let i=await oe(n.resolvePage({location:r,...e.pageIdentity===`url`?{routeKey:C(r)}:{},signal:d.signal,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:f}}),d.signal);return Q(l,d)?(i.commit?.(),t.setPage({pageId:i.pageId,collaboration:{roomId:i.collaborationRoomId,scopeId:f}},{reload:c}),t.isConnected||document.body?.append(t),t):$(l)}catch{return Q(l,d)?W():$(l)}})();return s=p,p}function U(){a?.abort(),o?.abort(),a=null,o=null}function W(){if(y&&!A(j,w(window.location).href))return Promise.resolve(null);let t=++i;U(),b?.suspendSession(),_?.remove(),_=null,E=null,x?.remove();let n=new AbortController,r=document.createElement(`div`);r.dataset.agencyReviewRoot=``,a=n,o=n,x=r;let c=G(t,n,r);return s=c,c.then(r=>{r&&Q(t,n)&&_===r&&e.onReady?.(r)}),c}async function G(t,n,i){try{let e=document.body??await se(document,n.signal);if(!Q(t,n))return $(t);e.append(i);let r=await J(t,n,i);return Q(t,n)?r:(i.remove(),$(t))}catch(a){if(!Q(t,n))return i.remove(),$(t);if(m(a,`AUTH_CANCELLED`))return i.remove(),x===i&&(x=null),null;let o=I.get(i),s=o?.[3];if(X(a,i),h(s)&&m(a,`INVITATION_EMAIL_REQUIRED`)&&!v){let e=o?.[1]??w(window.location).origin,t=o?.[2];return t&&(L.delete(e),q(k,e,N,M,t)),i.remove(),x===i&&(x=null),null}let c=fe(a,o?.[2]),u=o?.[1]??w(window.location).origin,d=m(a,`ORIGIN_NOT_ALLOWED`);if(d&&l(e.onInactive,{reason:`origin-not-allowed`,origin:u}),!h(s)||v||!g(a)){let{originNotAllowedMessage:o,renderBootstrapError:s}=await import(`./bootstrap-error-B30Je5Pg.js`);if(!Q(t,n))return i.remove(),$(t);let l=ee(a)?void 0:()=>{r||x!==i||(z=S(window.location,j,e.pageIdentity),W())};s(i,d?o(u):c.message,R,l)}else i.remove(),x===i&&(x=null);return e.onError?.(c),window.dispatchEvent(new CustomEvent(`visual-review:error`,{detail:c})),null}}async function J(t,n,r){let i=w(window.location),a=ie(k,j,i,N,M,e.runtime!==void 0),o=ne(L.get(i.origin)),s=T(D(k,i.origin)),c=a?.source===`url`?a:s??o??a;if(!c)return I.delete(r),r.remove(),x===r&&(x=null),null;let l=c.invitationId;I.set(r,[k,i.origin,l,c.source,c.emailToken]),c.source===`url`?(q(k,i.origin,N,M),L.set(i.origin,l),re(k,i.origin,l,c.emailToken),B.suppress(()=>ae(j))):c.source===`handoff`&&L.set(i.origin,l),z=S(window.location,j,e.pageIdentity);let m=w(window.location),h=await oe((e.runtime??await d(k)).resolve({invitationId:l,emailToken:c.emailToken,location:m,...e.pageIdentity===`url`?{routeKey:C(m)}:{},signal:n.signal,authHost:r,styleNonce:R,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:u(e.build,m)}}),n.signal);if(!Q(t,n))return null;O(k,i.origin,l),le(k,l,i,N,M,h.sessionScope??`browser`,h.sessionExpiresAt),c.source===`url`&&K(M,Z(k,i.origin)),L.set(i.origin,l);let g=e.sourceContextResolver??await p(e.build,n.signal);if(!Q(t,n))return null;let{createActiveReviewSessionLifecycle:ee,createReviewNavigationHandler:te,mountReviewWidget:y}=await f(n.signal);if(!Q(t,n))return null;b??=ee({window,document,storage:M,onExit:Y,onReauthenticationRequired:ce});let A=b.bindSession(l,Z(k,i.origin),h.clearLocalSession);return Q(t,n)?(_=y({...h,collaboration:{cursors:e.collaboration?.cursors===!0&&!!(h.collaborationRepository&&h.collaborationRoomId),chat:e.collaboration?.chat===!0&&!!h.collaborationRepository,repository:h.collaborationRepository,roomId:h.collaborationRoomId,scopeId:u(e.build,i)},developerTools:e.developerTools?.codeContextCopy===!0||e.developerTools?.reactGrab===!0?{codeContextCopy:!0}:void 0,offset:e.offset,onExitReview:A,onNavigateToFeedback:te(window,e.navigateToFeedback),sourceContextResolver:g,styleNonce:R,position:e.position}),b.attachWidget(_),E=h.resolvePage?{origin:i.origin,resolvePage:e=>h.resolvePage(e)}:null,r.remove(),x===r&&(x=null),v=!0,window.dispatchEvent(new CustomEvent(`visual-review:ready`,{detail:_})),_):null}function Y(e){let t=w(window.location).origin;y=!0,O(k,t,e),L.delete(t),q(k,t,N,M,e),i+=1,U(),_?.remove(),_=null,E=null,x?.remove(),x=null}function X(e,t){if(!g(e))return;let n=I.get(t);if(!n)return;let[r,i,a]=n;O(r,i,a),L.get(i)===a&&L.delete(i),q(r,i,N,M,a)}function Q(e,t){return!r&&i===e&&a===t&&!t.signal.aborted}function $(e){return!r&&i!==e?s:null}}function c(e,t){let n=o.get(e);n||(n=new Set,o.set(e,n)),!n.has(t)&&(n.add(t),console.info([`[VisualReview] Inactive: no invitation or resume marker was found.`,`Open this page using a Visual Review invitation URL.`,`Current origin: ${t}`].join(`
2
- `)))}function l(e,t){if(e)try{e(t)}catch(e){console.error(`[VisualReview] onInactive callback failed.`,e)}}function u(e,t){return JSON.stringify([P(e).buildId,t.pathname,t.search])}async function d(e){let{createHostedReviewRuntime:t}=await import(`./hosted-runtime-Sar8A9oH.js`);return t({serviceUrl:e})}async function f(e){return N(import(`./active-review-mount-BW2oQDQ2.js`),e,a,`리뷰 위젯을 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`)}async function p(e,t){let n=P(e),{createEmbeddedSourceContextResolver:r}=await N(import(`./embedded-source-context-BTB3pFqv.js`),t,a,`리뷰 소스 정보를 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`);return r({build:n})}function m(e,t){return e instanceof Error&&`code`in e&&e.code===t}function h(e){return e===`tab-storage`||e===`browser-storage`||e===`legacy-storage`}function g(e){let t=_(e);return typeof t==`string`&&/^INVITATION_(?:EXPIRED|NOT_FOUND|REVOKED)$/.test(t)}function ee(e){return g(e)||_(e)===`UNSUPPORTED_SERVICE_VERSION`}function _(e){return e instanceof Error&&`code`in e?e.code:void 0}const v=new WeakMap;function te(e,t){let n=v.get(e);if(!n){let t=e.history,r=t.pushState,i=t.replaceState,a=new Set;n={history:t,originalPushStateDescriptor:Object.getOwnPropertyDescriptor(t,`pushState`),originalReplaceStateDescriptor:Object.getOwnPropertyDescriptor(t,`replaceState`),patchedPushState:function(t,n,i){r.call(this,t,n,i),y(e)},patchedReplaceState:function(t,n,r){i.call(this,t,n,r),y(e)},popstateListener:()=>y(e),subscribers:a,suppressionDepth:0},v.set(e,n),b(t,`pushState`,n.patchedPushState),b(t,`replaceState`,n.patchedReplaceState),e.addEventListener(`popstate`,n.popstateListener)}n.subscribers.add(t);let r=!0;return{suppress(e){n.suppressionDepth+=1;try{return e()}finally{--n.suppressionDepth}},unsubscribe(){r&&(r=!1,n.subscribers.delete(t),!(n.subscribers.size>0)&&(e.removeEventListener(`popstate`,n.popstateListener),x(n.history,`pushState`,n.patchedPushState,n.originalPushStateDescriptor),x(n.history,`replaceState`,n.patchedReplaceState,n.originalReplaceStateDescriptor),v.delete(e)))}}}function y(e){let t=v.get(e);if(!t||t.suppressionDepth>0)return;let n=w(e.location);[...t.subscribers].forEach(e=>e(n))}function b(e,t,n){Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:!0,value:n})}function x(e,t,n,r){if(Object.getOwnPropertyDescriptor(e,t)?.value===n){if(r){Object.defineProperty(e,t,r);return}delete e[t]}}function S(e,t,n){return`${e.origin}\n${n===`url`?C(e):e.pathname}\n${A(t,e.href)??``}`}function C(e){let t=new URL(e.href);return`${t.pathname}${t.search}${t.hash}`}function w(e){return new URL(e.href)}function ne(e){return e?{invitationId:e,source:`memory`}:null}function T(e){return e?{invitationId:e.invitationId,emailToken:e.emailToken,source:`handoff`}:null}function E(e,t){return JSON.stringify([e,t])}function D(e,t,n=Date.now()){let i=E(e,t),a=r.get(i);if(a){if(a.expiresAt>n)return a;clearTimeout(a.timeout),r.delete(i)}}function re(e,t,n,a,o=Date.now()){k(o);let s=E(e,t),c=r.get(s);for(c&&(clearTimeout(c.timeout),r.delete(s));r.size>=32;){let e=r.keys().next().value;if(!e)break;let t=r.get(e);t&&clearTimeout(t.timeout),r.delete(e)}let l=o+i,u=setTimeout(()=>{r.get(s)?.expiresAt===l&&r.delete(s)},i);r.set(s,{invitationId:n,emailToken:a,expiresAt:l,timeout:u})}function O(e,t,n){let i=E(e,t),a=r.get(i);a?.invitationId===n&&(clearTimeout(a.timeout),r.delete(i))}function k(e){for(let[t,n]of r)n.expiresAt>e||(clearTimeout(n.timeout),r.delete(t))}function ie(e,t,n,r,i,a){let o=A(t,n.href);if(o)return{invitationId:o,emailToken:j(n.href)??void 0,source:`url`};let s=W(i,Z(e,n.origin)),c=W(r,Y(e,n.origin));if(c&&c!==s)return{invitationId:c,source:`tab-storage`};let l=U(i,X(e,n.origin));if(l&&l.invitationId!==s)return{invitationId:l.invitationId,source:`browser-storage`};let u=W(i,J(e,n.origin));return u&&u!==s?{invitationId:u,source:`legacy-storage`}:a&&Q(n.hostname)?{invitationId:`local-preview`,source:`local-preview`}:null}function A(e,t){let n=new URL(t),r=M(n.hash);return(r?.get(e)??r?.get(`visual-review`)??r?.get(`agency-review`)??r?.get(`agency-review-invite`)??n.searchParams.get(e)??n.searchParams.get(`agency-review`)??n.searchParams.get(`agency-review-invite`))?.trim()||null}function j(e){let t=(M(new URL(e).hash)?.get(`visual-review-token`))?.trim();return t&&/^[A-Za-z0-9_-]{43}$/u.test(t)?t:null}function M(e){let t=e.startsWith(`#`)?e.slice(1):e,n=t.startsWith(`?`)?t.slice(1):t;return n.includes(`=`)?new URLSearchParams(n):null}function ae(e){let t=new URL(window.location.href),n=new Set([e,`visual-review`,`agency-review`,`agency-review-invite`,`visual-review-token`]),r=!1;n.forEach(e=>{t.searchParams.has(e)&&(t.searchParams.delete(e),r=!0)});let i=M(t.hash);if(i){n.forEach(e=>{i.has(e)&&(i.delete(e),r=!0)});let e=i.toString();t.hash=e?`#${e}`:``}r&&window.history.replaceState(window.history.state,``,`${t.pathname}${t.search}${t.hash}`)}function oe(e,t){return new Promise((n,r)=>{let i=()=>r(new DOMException(`Aborted`,`AbortError`));if(t.aborted){i();return}t.addEventListener(`abort`,i,{once:!0}),e.then(e=>{t.removeEventListener(`abort`,i),n(e)},e=>{t.removeEventListener(`abort`,i),r(e)})})}function N(e,t,n,r){return new Promise((i,a)=>{let o=!1,s=e=>{o||(o=!0,clearTimeout(l),t.removeEventListener(`abort`,c),e())},c=()=>{s(()=>a(new DOMException(`Aborted`,`AbortError`)))},l=setTimeout(()=>{s(()=>a(Error(r)))},n);if(t.aborted){c();return}t.addEventListener(`abort`,c,{once:!0}),e.then(e=>s(()=>i(e)),e=>s(()=>a(e)))})}function se(e,t){return e.body?Promise.resolve(e.body):t.aborted?Promise.reject(new DOMException(`Aborted`,`AbortError`)):new Promise((n,r)=>{let i=!1,a=new MutationObserver(()=>c()),o=()=>{e.removeEventListener(`DOMContentLoaded`,c),t.removeEventListener(`abort`,l),a.disconnect()},s=e=>{i||(i=!0,o(),e())},c=()=>{let t=e.body;t&&s(()=>n(t))},l=()=>{s(()=>r(new DOMException(`Aborted`,`AbortError`)))};e.addEventListener(`DOMContentLoaded`,c),t.addEventListener(`abort`,l,{once:!0}),a.observe(e,{childList:!0,subtree:!0}),c()})}function P(e){let t=document.querySelector(`meta[name="visual-review-build"]`),n=document.querySelector(`meta[name="visual-review-commit"]`),r=H(L()),i=H(R());return{buildId:V(e?.buildId)??ce(t)??r??`unversioned`,gitCommit:z(e,n,i),generatedAt:e?.generatedAt,mode:e?.mode??I(F())??(Q(window.location.hostname)?`development`:`production`)}}function F(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_MODE}catch{return}}function I(e){return e===`development`||e===`preview`||e===`production`?e:void 0}function L(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_ID}catch{return}}function R(){try{return process.env.VISUAL_REVIEW_PUBLIC_GIT_COMMIT}catch{return}}function z(e,t,n){return e?.gitCommit===null?null:typeof e?.gitCommit==`string`?B(e.gitCommit):B(t?.content)??B(n)}function B(e){if(typeof e!=`string`)return null;let t=e.trim().toLowerCase();return/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(t)?t:null}function ce(e){return H(e?.content)}function V(e){let t=typeof e==`string`?e.trim():``;return t.length>0?t:null}function H(e){return typeof e==`string`&&e.length>0?e:null}function le(e,t,n,r,i,a,o){if(a===`tab`){try{r?.setItem(Y(e,n.origin),t)}catch{}K(i,X(e,n.origin))}else{let a=Number.isSafeInteger(o)&&o>Date.now()?o:Date.now()+1440*6e4;try{i?.setItem(X(e,n.origin),JSON.stringify({version:2,invitationId:t,expiresAt:a}))}catch{}K(r,Y(e,n.origin))}G(e,n.origin,i,t)}function U(e,t){let n=W(e,t);if(!n)return null;let r=()=>{try{e?.removeItem?.(t)}catch{}};try{let e=JSON.parse(n),t=e.expiresAt;return e.version!==2||typeof e.invitationId!=`string`||!e.invitationId.trim()||!Number.isSafeInteger(t)||t<=Date.now()?(r(),null):{version:2,invitationId:e.invitationId,expiresAt:t}}catch{return r(),null}}function W(e,t){try{return e?.getItem(t)??null}catch{return null}}function ue(e){try{return e.localStorage}catch{return null}}function de(e){try{return e.sessionStorage}catch{return null}}function G(e,t,n,r){try{if(!n)return;let i=J(e,t),a=n.getItem(i);if(a===null||r!==void 0&&a!==r)return;n.removeItem(i)}catch{}}function K(e,t){try{e?.removeItem(t)}catch{}}function q(e,t,n,r,i){G(e,t,r,i);let a=Y(e,t),o=X(e,t);if(i===void 0){K(n,a),K(r,o);return}W(n,a)===i&&K(n,a),U(r,o)?.invitationId===i&&K(r,o)}function J(e,t){return`visual-review:${e}:${t}:invitation`}function Y(e,t){return`visual-review:${e}:${t}:invitation:tab:v2`}function X(e,t){return`visual-review:${e}:${t}:invitation:browser:v2`}function Z(e,t){return`visual-review:${e}:${t}:exit:v1`}function Q(e){return e===`localhost`||e===`127.0.0.1`||e.endsWith(`.test`)||e.endsWith(`.local`)}function fe(e,t){let n=e instanceof Error?e.message:String(e),r=Error($(n,t));if(e&&typeof e==`object`){let n=`code`in e?e.code:void 0,i=`status`in e?e.status:void 0;typeof n==`string`&&Object.defineProperty(r,"code",{configurable:!0,enumerable:!0,value:$(n,t)}),typeof i==`number`&&Number.isFinite(i)&&Object.defineProperty(r,"status",{configurable:!0,enumerable:!0,value:i})}return r}function $(e,t){if(!t)return e;let n=new Set([t,encodeURIComponent(t)]),r=e;for(let e of n)e&&(r=r.split(e).join(`[redacted]`));return r}function pe(e,t){if(e!==void 0)return he(e);let n=t.currentScript,r=n?me(n):void 0;if(r)return r;try{for(let e of t.querySelectorAll(`script[nonce],style[nonce]`)){let t=me(e);if(t)return t}}catch{}}function me(e){let t=`nonce`in e?e.nonce:void 0;return typeof t==`string`?he(t):void 0}function he(e){return e.trim()||void 0}export{e as n,t as r,s as t};
2
+ `)))}function l(e,t){if(e)try{e(t)}catch(e){console.error(`[VisualReview] onInactive callback failed.`,e)}}function u(e,t){return JSON.stringify([P(e).buildId,t.pathname,t.search])}async function d(e){let{createHostedReviewRuntime:t}=await import(`./hosted-runtime-CP0aew_W.js`);return t({serviceUrl:e})}async function f(e){return N(import(`./active-review-mount-BW2oQDQ2.js`),e,a,`리뷰 위젯을 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`)}async function p(e,t){let n=P(e),{createEmbeddedSourceContextResolver:r}=await N(import(`./embedded-source-context-BTB3pFqv.js`),t,a,`리뷰 소스 정보를 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`);return r({build:n})}function m(e,t){return e instanceof Error&&`code`in e&&e.code===t}function h(e){return e===`tab-storage`||e===`browser-storage`||e===`legacy-storage`}function g(e){let t=_(e);return typeof t==`string`&&/^INVITATION_(?:EXPIRED|NOT_FOUND|REVOKED)$/.test(t)}function ee(e){return g(e)||_(e)===`UNSUPPORTED_SERVICE_VERSION`}function _(e){return e instanceof Error&&`code`in e?e.code:void 0}const v=new WeakMap;function te(e,t){let n=v.get(e);if(!n){let t=e.history,r=t.pushState,i=t.replaceState,a=new Set;n={history:t,originalPushStateDescriptor:Object.getOwnPropertyDescriptor(t,`pushState`),originalReplaceStateDescriptor:Object.getOwnPropertyDescriptor(t,`replaceState`),patchedPushState:function(t,n,i){r.call(this,t,n,i),y(e)},patchedReplaceState:function(t,n,r){i.call(this,t,n,r),y(e)},popstateListener:()=>y(e),subscribers:a,suppressionDepth:0},v.set(e,n),b(t,`pushState`,n.patchedPushState),b(t,`replaceState`,n.patchedReplaceState),e.addEventListener(`popstate`,n.popstateListener)}n.subscribers.add(t);let r=!0;return{suppress(e){n.suppressionDepth+=1;try{return e()}finally{--n.suppressionDepth}},unsubscribe(){r&&(r=!1,n.subscribers.delete(t),!(n.subscribers.size>0)&&(e.removeEventListener(`popstate`,n.popstateListener),x(n.history,`pushState`,n.patchedPushState,n.originalPushStateDescriptor),x(n.history,`replaceState`,n.patchedReplaceState,n.originalReplaceStateDescriptor),v.delete(e)))}}}function y(e){let t=v.get(e);if(!t||t.suppressionDepth>0)return;let n=w(e.location);[...t.subscribers].forEach(e=>e(n))}function b(e,t,n){Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:!0,value:n})}function x(e,t,n,r){if(Object.getOwnPropertyDescriptor(e,t)?.value===n){if(r){Object.defineProperty(e,t,r);return}delete e[t]}}function S(e,t,n){return`${e.origin}\n${n===`url`?C(e):e.pathname}\n${A(t,e.href)??``}`}function C(e){let t=new URL(e.href);return`${t.pathname}${t.search}${t.hash}`}function w(e){return new URL(e.href)}function ne(e){return e?{invitationId:e,source:`memory`}:null}function T(e){return e?{invitationId:e.invitationId,emailToken:e.emailToken,source:`handoff`}:null}function E(e,t){return JSON.stringify([e,t])}function D(e,t,n=Date.now()){let i=E(e,t),a=r.get(i);if(a){if(a.expiresAt>n)return a;clearTimeout(a.timeout),r.delete(i)}}function re(e,t,n,a,o=Date.now()){k(o);let s=E(e,t),c=r.get(s);for(c&&(clearTimeout(c.timeout),r.delete(s));r.size>=32;){let e=r.keys().next().value;if(!e)break;let t=r.get(e);t&&clearTimeout(t.timeout),r.delete(e)}let l=o+i,u=setTimeout(()=>{r.get(s)?.expiresAt===l&&r.delete(s)},i);r.set(s,{invitationId:n,emailToken:a,expiresAt:l,timeout:u})}function O(e,t,n){let i=E(e,t),a=r.get(i);a?.invitationId===n&&(clearTimeout(a.timeout),r.delete(i))}function k(e){for(let[t,n]of r)n.expiresAt>e||(clearTimeout(n.timeout),r.delete(t))}function ie(e,t,n,r,i,a){let o=A(t,n.href);if(o)return{invitationId:o,emailToken:j(n.href)??void 0,source:`url`};let s=W(i,Z(e,n.origin)),c=W(r,Y(e,n.origin));if(c&&c!==s)return{invitationId:c,source:`tab-storage`};let l=U(i,X(e,n.origin));if(l&&l.invitationId!==s)return{invitationId:l.invitationId,source:`browser-storage`};let u=W(i,J(e,n.origin));return u&&u!==s?{invitationId:u,source:`legacy-storage`}:a&&Q(n.hostname)?{invitationId:`local-preview`,source:`local-preview`}:null}function A(e,t){let n=new URL(t),r=M(n.hash);return(r?.get(e)??r?.get(`visual-review`)??r?.get(`agency-review`)??r?.get(`agency-review-invite`)??n.searchParams.get(e)??n.searchParams.get(`agency-review`)??n.searchParams.get(`agency-review-invite`))?.trim()||null}function j(e){let t=(M(new URL(e).hash)?.get(`visual-review-token`))?.trim();return t&&/^[A-Za-z0-9_-]{43}$/u.test(t)?t:null}function M(e){let t=e.startsWith(`#`)?e.slice(1):e,n=t.startsWith(`?`)?t.slice(1):t;return n.includes(`=`)?new URLSearchParams(n):null}function ae(e){let t=new URL(window.location.href),n=new Set([e,`visual-review`,`agency-review`,`agency-review-invite`,`visual-review-token`]),r=!1;n.forEach(e=>{t.searchParams.has(e)&&(t.searchParams.delete(e),r=!0)});let i=M(t.hash);if(i){n.forEach(e=>{i.has(e)&&(i.delete(e),r=!0)});let e=i.toString();t.hash=e?`#${e}`:``}r&&window.history.replaceState(window.history.state,``,`${t.pathname}${t.search}${t.hash}`)}function oe(e,t){return new Promise((n,r)=>{let i=()=>r(new DOMException(`Aborted`,`AbortError`));if(t.aborted){i();return}t.addEventListener(`abort`,i,{once:!0}),e.then(e=>{t.removeEventListener(`abort`,i),n(e)},e=>{t.removeEventListener(`abort`,i),r(e)})})}function N(e,t,n,r){return new Promise((i,a)=>{let o=!1,s=e=>{o||(o=!0,clearTimeout(l),t.removeEventListener(`abort`,c),e())},c=()=>{s(()=>a(new DOMException(`Aborted`,`AbortError`)))},l=setTimeout(()=>{s(()=>a(Error(r)))},n);if(t.aborted){c();return}t.addEventListener(`abort`,c,{once:!0}),e.then(e=>s(()=>i(e)),e=>s(()=>a(e)))})}function se(e,t){return e.body?Promise.resolve(e.body):t.aborted?Promise.reject(new DOMException(`Aborted`,`AbortError`)):new Promise((n,r)=>{let i=!1,a=new MutationObserver(()=>c()),o=()=>{e.removeEventListener(`DOMContentLoaded`,c),t.removeEventListener(`abort`,l),a.disconnect()},s=e=>{i||(i=!0,o(),e())},c=()=>{let t=e.body;t&&s(()=>n(t))},l=()=>{s(()=>r(new DOMException(`Aborted`,`AbortError`)))};e.addEventListener(`DOMContentLoaded`,c),t.addEventListener(`abort`,l,{once:!0}),a.observe(e,{childList:!0,subtree:!0}),c()})}function P(e){let t=document.querySelector(`meta[name="visual-review-build"]`),n=document.querySelector(`meta[name="visual-review-commit"]`),r=H(L()),i=H(R());return{buildId:V(e?.buildId)??ce(t)??r??`unversioned`,gitCommit:z(e,n,i),generatedAt:e?.generatedAt,mode:e?.mode??I(F())??(Q(window.location.hostname)?`development`:`production`)}}function F(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_MODE}catch{return}}function I(e){return e===`development`||e===`preview`||e===`production`?e:void 0}function L(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_ID}catch{return}}function R(){try{return process.env.VISUAL_REVIEW_PUBLIC_GIT_COMMIT}catch{return}}function z(e,t,n){return e?.gitCommit===null?null:typeof e?.gitCommit==`string`?B(e.gitCommit):B(t?.content)??B(n)}function B(e){if(typeof e!=`string`)return null;let t=e.trim().toLowerCase();return/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(t)?t:null}function ce(e){return H(e?.content)}function V(e){let t=typeof e==`string`?e.trim():``;return t.length>0?t:null}function H(e){return typeof e==`string`&&e.length>0?e:null}function le(e,t,n,r,i,a,o){if(a===`tab`){try{r?.setItem(Y(e,n.origin),t)}catch{}K(i,X(e,n.origin))}else{let a=Number.isSafeInteger(o)&&o>Date.now()?o:Date.now()+1440*6e4;try{i?.setItem(X(e,n.origin),JSON.stringify({version:2,invitationId:t,expiresAt:a}))}catch{}K(r,Y(e,n.origin))}G(e,n.origin,i,t)}function U(e,t){let n=W(e,t);if(!n)return null;let r=()=>{try{e?.removeItem?.(t)}catch{}};try{let e=JSON.parse(n),t=e.expiresAt;return e.version!==2||typeof e.invitationId!=`string`||!e.invitationId.trim()||!Number.isSafeInteger(t)||t<=Date.now()?(r(),null):{version:2,invitationId:e.invitationId,expiresAt:t}}catch{return r(),null}}function W(e,t){try{return e?.getItem(t)??null}catch{return null}}function ue(e){try{return e.localStorage}catch{return null}}function de(e){try{return e.sessionStorage}catch{return null}}function G(e,t,n,r){try{if(!n)return;let i=J(e,t),a=n.getItem(i);if(a===null||r!==void 0&&a!==r)return;n.removeItem(i)}catch{}}function K(e,t){try{e?.removeItem(t)}catch{}}function q(e,t,n,r,i){G(e,t,r,i);let a=Y(e,t),o=X(e,t);if(i===void 0){K(n,a),K(r,o);return}W(n,a)===i&&K(n,a),U(r,o)?.invitationId===i&&K(r,o)}function J(e,t){return`visual-review:${e}:${t}:invitation`}function Y(e,t){return`visual-review:${e}:${t}:invitation:tab:v2`}function X(e,t){return`visual-review:${e}:${t}:invitation:browser:v2`}function Z(e,t){return`visual-review:${e}:${t}:exit:v1`}function Q(e){return e===`localhost`||e===`127.0.0.1`||e.endsWith(`.test`)||e.endsWith(`.local`)}function fe(e,t){let n=e instanceof Error?e.message:String(e),r=Error($(n,t));if(e&&typeof e==`object`){let n=`code`in e?e.code:void 0,i=`status`in e?e.status:void 0;typeof n==`string`&&Object.defineProperty(r,"code",{configurable:!0,enumerable:!0,value:$(n,t)}),typeof i==`number`&&Number.isFinite(i)&&Object.defineProperty(r,"status",{configurable:!0,enumerable:!0,value:i})}return r}function $(e,t){if(!t)return e;let n=new Set([t,encodeURIComponent(t)]),r=e;for(let e of n)e&&(r=r.split(e).join(`[redacted]`));return r}function pe(e,t){if(e!==void 0)return he(e);let n=t.currentScript,r=n?me(n):void 0;if(r)return r;try{for(let e of t.querySelectorAll(`script[nonce],style[nonce]`)){let t=me(e);if(t)return t}}catch{}}function me(e){let t=`nonce`in e?e.nonce:void 0;return typeof t==`string`?he(t):void 0}function he(e){return e.trim()||void 0}export{e as n,t as r,s as t};
@@ -1 +1 @@
1
- import{a as e,c as t,i as n,o as r,r as i,s as a,t as o}from"./src-CjYM9---.js";import{i as s,n as c,t as l}from"./reviewer-auth-presentation-DusgaOej.js";import{i as u,init as d}from"@instantdb/core";new Uint8Array([107,167,184,17,157,173,17,209,128,180,0,192,79,212,48,200]);const f=u.schema({entities:{$files:u.entity({path:u.string().unique().indexed(),url:u.string()}),$users:u.entity({email:u.string().unique().indexed()}),projects:u.entity({name:u.string(),slug:u.string().unique().indexed(),allowedOrigin:u.string().optional(),originDisconnectedAt:u.number().optional(),originModelVersion:u.number().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),projectOrigins:u.entity({projectId:u.string().indexed(),originKey:u.string().unique().indexed(),origin:u.string().indexed(),label:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPages:u.entity({projectId:u.string().indexed(),autoRegistrationSlot:u.string().unique().indexed().optional(),name:u.string(),pathname:u.string(),stagingOrigin:u.string(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPageBuilds:u.entity({projectId:u.string().indexed(),pageId:u.string().unique().indexed(),buildId:u.string(),gitCommit:u.string().optional(),generatedAt:u.number().optional(),mode:u.string(),updatedAt:u.number().indexed()}),reviewers:u.entity({authUserId:u.string().indexed(),projectId:u.string().indexed(),email:u.string().indexed(),displayName:u.string(),role:u.string(),createdAt:u.number()}),reviewerIdentityClaims:u.entity({reviewerId:u.string().unique().indexed(),projectId:u.string().indexed(),authUserId:u.string().indexed(),email:u.string(),displayName:u.string(),role:u.string(),createdAt:u.number()}),invitations:u.entity({projectId:u.string().indexed(),reviewerId:u.string().indexed().optional(),email:u.string().indexed(),status:u.string().indexed(),claimRevision:u.number(),claimedByAuthUserId:u.string().indexed().optional(),expiresAt:u.number().indexed(),createdAt:u.number(),acceptedAt:u.number().optional(),revokedAt:u.number().optional(),emailTokenHash:u.string().optional(),emailSentAt:u.number().optional(),reviewUrl:u.string().optional()}),comments:u.entity({pageId:u.string().indexed(),pageNumber:u.number().optional(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),anchorSelector:u.string().optional(),anchorOffsetX:u.number().optional(),anchorOffsetY:u.number().optional(),anchorViewportX:u.number().optional(),anchorViewportY:u.number().optional(),targetKind:u.string().optional(),targetPageX:u.number().optional(),targetPageY:u.number().optional(),targetWidth:u.number().optional(),targetHeight:u.number().optional(),targetViewportX:u.number().optional(),targetViewportY:u.number().optional(),targetViewportWidth:u.number().optional(),targetViewportHeight:u.number().optional(),targetPageOrigin:u.string().optional(),targetPagePathname:u.string().optional(),targetPageSearch:u.string().optional(),targetPageHash:u.string().optional(),targetPageTitle:u.string().optional(),targetCapturedAt:u.number().optional(),environmentBrowser:u.string().optional(),environmentOperatingSystem:u.string().optional(),environmentDeviceType:u.string().optional(),environmentScreenWidth:u.number().optional(),environmentScreenHeight:u.number().optional(),environmentDevicePixelRatio:u.number().optional(),status:u.string().indexed(),workflowRevision:u.number(),createdAt:u.number().indexed(),updatedAt:u.number().indexed(),resolvedAt:u.number().optional(),resolvedById:u.string().optional(),deletingAt:u.number().optional()}),commentWorkflowClaims:u.entity({claimKey:u.string().unique().indexed(),commentId:u.string().indexed(),expectedWorkflowRevision:u.number(),createdAt:u.number().indexed()}),commentPageNumberClaims:u.entity({claimKey:u.string().unique().indexed(),pageId:u.string().indexed(),pageNumber:u.number(),commentId:u.string().indexed(),createdAt:u.number().indexed()}),commentTargetElements:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),selector:u.string(),tagName:u.string(),textPreview:u.string(),pageX:u.number(),pageY:u.number(),width:u.number(),height:u.number(),viewportX:u.number(),viewportY:u.number(),viewportWidth:u.number(),viewportHeight:u.number(),authorAuthUserId:u.string().indexed()}),commentScopeEntries:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),key:u.string(),valueType:u.string(),stringValue:u.string().optional(),numberValue:u.number().optional(),booleanValue:u.boolean().optional(),authorAuthUserId:u.string().indexed()}),commentSourceContexts:u.entity({commentId:u.string().unique().indexed(),pageId:u.string().indexed(),authorAuthUserId:u.string().indexed(),contextJson:u.string(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed()}),replies:u.entity({pageId:u.string().indexed(),commentId:u.string().indexed(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number().indexed()}),reviewMessages:u.entity({pageId:u.string().indexed(),pathname:u.string().indexed(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string(),createdAt:u.number().indexed(),updatedAt:u.number().indexed()})},links:{projectOwners:{forward:{on:`projects`,has:`many`,label:`owners`},reverse:{on:`$users`,has:`many`,label:`ownedProjects`}},projectPages:{forward:{on:`projects`,has:`many`,label:`pages`},reverse:{on:`reviewPages`,has:`one`,label:`project`}},projectOriginProject:{forward:{on:`projects`,has:`many`,label:`origins`},reverse:{on:`projectOrigins`,has:`one`,label:`project`}},pageCurrentBuild:{forward:{on:`reviewPageBuilds`,has:`one`,label:`page`,required:!0,onDelete:`cascade`},reverse:{on:`reviewPages`,has:`one`,label:`currentBuild`}},projectReviewers:{forward:{on:`projects`,has:`many`,label:`reviewers`},reverse:{on:`reviewers`,has:`one`,label:`project`}},reviewerUser:{forward:{on:`reviewers`,has:`one`,label:`$user`},reverse:{on:`$users`,has:`many`,label:`reviewerMemberships`}},projectInvitations:{forward:{on:`projects`,has:`many`,label:`invitations`},reverse:{on:`invitations`,has:`one`,label:`project`}},invitationReviewer:{forward:{on:`invitations`,has:`one`,label:`reviewer`},reverse:{on:`reviewers`,has:`many`,label:`acceptedInvitations`}},invitationActiveUser:{forward:{on:`invitations`,has:`one`,label:`activeUser`},reverse:{on:`$users`,has:`many`,label:`activeReviewInvitations`}},pageComments:{forward:{on:`reviewPages`,has:`many`,label:`comments`},reverse:{on:`comments`,has:`one`,label:`page`}},pageReviewMessages:{forward:{on:`reviewPages`,has:`many`,label:`reviewMessages`},reverse:{on:`reviewMessages`,has:`one`,label:`page`}},commentReplies:{forward:{on:`comments`,has:`many`,label:`replies`},reverse:{on:`replies`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`}},commentTargetElements:{forward:{on:`commentTargetElements`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`targetElements`}},commentScopeEntries:{forward:{on:`commentScopeEntries`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`scopeEntries`}},commentSourceContext:{forward:{on:`commentSourceContexts`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`one`,label:`sourceContext`}},pageSourceContexts:{forward:{on:`commentSourceContexts`,has:`one`,label:`page`,required:!0},reverse:{on:`reviewPages`,has:`many`,label:`sourceContexts`}},sourceContextAuthor:{forward:{on:`commentSourceContexts`,has:`one`,label:`author`,required:!0},reverse:{on:`reviewers`,has:`many`,label:`sourceContexts`}},commentAuthor:{forward:{on:`comments`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`comments`}},replyAuthor:{forward:{on:`replies`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`replies`}},reviewMessageAuthor:{forward:{on:`reviewMessages`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`reviewMessages`}}},rooms:{visualReview:{presence:u.entity({reviewerId:u.string(),displayName:u.string(),color:u.string(),active:u.boolean(),pathname:u.string().optional(),cursorX:u.number().optional(),cursorY:u.number().optional(),viewportWidth:u.number().optional(),viewportHeight:u.number().optional(),cursorChat:u.string().optional()})}}}),p={$default:`false`},m={$default:`false`},h=`$user.activeReviewInvitations`,g=`${h}.project.id`;function _(e){return[`auth.id != null`,`size(auth.ref('${h}.id')) > 0`,`size(auth.ref('${g}')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.status')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.expiresAt')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.revokedAt')) == 0`,`auth.ref('${h}.status').all(status, status == 'accepted')`,`auth.ref('${h}.expiresAt').all(expiresAt, request.time < timestamp(expiresAt))`,e].join(` && `)}function v(...e){return e.map(e=>`(${e})`).join(` && `)}function y(...e){return e.map(e=>`(${e})`).join(` || `)}function b(e){let t=`data.ref('${e}project.origins.origin')`,n=`data.ref('${e}project.allowedOrigin')`,r=`data.ref('${e}project.originModelVersion')`;return y(`request.origin in ${t}`,v(`size(${r}) == 0`,`request.origin in ${n}`))}_(`data.ref('project.id')[0] in auth.ref('${g}')`),y(`request.origin in data.ref('project.origins.origin')`,v(`size(data.ref('project.originModelVersion')) == 0`,`request.origin in data.ref('project.allowedOrigin')`)),y(`request.origin in data.ref('origins.origin')`,v(`size(data.ref('originModelVersion')) == 0`,`request.origin in data.ref('allowedOrigin')`)),_(`data.ref('page.project.id')[0] in auth.ref('${g}')`),b(`page.`),y(`auth.id != null && auth.id in data.ref('comment.page.project.owners.id')`,v(_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`))),_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`),b(``);const x=v(`data.reviewerId != null`,`data.projectId in data.ref('project.id')`,`data.reviewerId in data.ref('reviewer.id')`,`data.projectId in data.ref('reviewer.project.id')`,`auth.id in data.ref('reviewer.$user.id')`,`auth.email in data.ref('reviewer.email')`);v(`auth.id != null`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`request.modifiedFields.all(field, field in ['status', 'claimedByAuthUserId', 'acceptedAt', 'claimRevision', 'activeUser'])`,`'status' in request.modifiedFields`,`'claimedByAuthUserId' in request.modifiedFields`,`'acceptedAt' in request.modifiedFields`,`'claimRevision' in request.modifiedFields`,`'activeUser' in request.modifiedFields`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.revokedAt == null`,`linkedData.status == 'accepted'`,`linkedData.claimedByAuthUserId == auth.id`,`request.time < timestamp(linkedData.expiresAt)`,`math.abs((request.time - timestamp(linkedData.acceptedAt)).getMinutes()) <= 1`,`linkedData.projectId in linkedData.ref('project.id')`,`linkedData.reviewerId in linkedData.ref('reviewer.id')`,`linkedData.projectId in linkedData.ref('reviewer.project.id')`,`auth.id in linkedData.ref('reviewer.$user.id')`,`auth.email in linkedData.ref('reviewer.email')`),v(`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.claimedByAuthUserId == auth.id`,`auth.id in data.ref('activeUser.id')`),v(`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.claimedByAuthUserId == auth.id`,`auth.id in linkedData.ref('activeUser.id')`),{...p},{...p},{...m},{...m},_(`data.id in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...p},{...m},_(`data.ref('project.id')[0] in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...m},{...p},{...m},{...p},{...p},{...p},{...p},{...p},{...p},{...p};function ee(e){if(!e.trim())throw Error(`InstantDB app ID is required`);return d({appId:e,schema:f,devtool:!1})}var te=class{db;session;#e;attachScreenshot;constructor(e,t,n={}){this.db=e,this.session=t,this.#e=n.commentTransport,this.#e&&(this.attachScreenshot=(e,t,n)=>this.#e.uploadScreenshot({pageId:e,commentId:t,screenshot:n}))}async getSnapshot(e){let{data:t}=await this.db.queryOnce(S(e,this.session.authUserId));return w(e,t.comments,t.replies)}async getProjectSnapshot(e){this.#t(e);let{data:t}=await this.db.queryOnce(C(e,this.session.authUserId));return T(e,t.comments,t.replies)}subscribe(e,t,n){return this.db.subscribeQuery(S(e,this.session.authUserId),r=>{if(r.error){n?.(j(r.error));return}if(r.data)try{t(w(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}subscribeProject(e,t,n){return this.#t(e),this.db.subscribeQuery(C(e,this.session.authUserId),r=>{if(r.error){n?.(j(r.error));return}if(r.data)try{t(T(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}async mutate(e,t){switch(t.type){case`comment.upsert`:{if(t.comment.pageId!==e)throw Error(`Comment page mismatch`);let n=t.comment;if(n.authorId!==this.session.reviewerId)throw Error(`Comment author mismatch`);let i=a(n.body).trim(),s=I(n.createdAt,`Comment createdAt`);if(I(n.updatedAt,`Comment updatedAt`)<s)throw new o(`Comment updatedAt precedes createdAt`);if(n.createdAt!==n.updatedAt)throw new o(`Comment edits must go through the review service`);if(n.status!==`open`||n.resolvedAt!==void 0||n.resolvedById!==void 0)throw new o(`New comments must start open`);let c=r(n.target);if(!k(n.anchor,c.publicTarget.anchor))throw new o(`Comment anchor does not match its target`);if(!this.#e)throw new o(`Comment creation requires the review service transport`);await this.#e.createComment({pageId:e,commentId:n.id,body:i,target:n.target,...n.scope===void 0?{}:{scope:n.scope}});return}case`comment.delete`:throw new o(`Comment deletion must go through the review service`);case`reply.upsert`:{let e=t.reply;if(e.authorId!==this.session.reviewerId)throw Error(`Reply author mismatch`);let n=a(e.body,`Reply body`).trim(),r=I(e.createdAt,`Reply createdAt`);if(I(e.updatedAt,`Reply updatedAt`)<r)throw new o(`Reply updatedAt precedes createdAt`);if(e.createdAt!==e.updatedAt)throw new o(`Reply edits must go through the review service`);if(!this.#e)throw new o(`Reply creation requires the review service transport`);await this.#e.createReply({commentId:e.commentId,replyId:e.id,body:n});return}case`reply.delete`:throw new o(`Reply deletion must go through the review service`)}}#t(e){if(!e.trim()||e!==this.session.projectId)throw Error(`Project does not belong to the reviewer session`)}};function S(e,t){return{comments:{$:{where:{pageId:e},order:{createdAt:`asc`}},targetElements:{},scopeEntries:{}},replies:{$:{where:{pageId:e},order:{createdAt:`asc`}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function C(e,t){return{comments:{$:{where:{"page.project.id":e},order:{createdAt:`asc`}},page:{project:{}},targetElements:{},scopeEntries:{}},replies:{$:{where:{"comment.page.project.id":e},order:{createdAt:`asc`}},comment:{page:{project:{}}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function w(e,t,n){let r=t.map(t=>D(t,e));A(r,`comment`);let i=new Set(r.map(({id:e})=>e)),a=n.map(t=>O(t,e));A(a,`reply`);for(let e of a)if(!i.has(e.commentId))throw new o(`Reply ${e.id} references a comment outside the page snapshot`);return{pageId:e,comments:r,replies:a}}function T(e,t,n){let r=new Map,i=t.map(t=>{let n=P(t.pageId,`comment pageId`);E(t.page,n,e,`comment ${String(t.id)}`);let i=M(t.page,`comment ${String(t.id)} page`),a=D(t,n),o=`/`;try{o=new URL(a.target.pageUrl).pathname||`/`}catch{}return r.set(n,{id:n,name:typeof i.name==`string`&&i.name.trim()?i.name.trim():a.target.pageTitle.trim()||o,pathname:typeof i.pathname==`string`&&i.pathname.trim()?i.pathname.trim():o}),a});A(i,`comment`);let a=new Map(i.map(e=>[e.id,e])),s=n.map(t=>{let n=P(t.pageId,`reply pageId`),r=M(t.comment,`reply ${String(t.id)} comment`),i=P(t.commentId,`reply ${String(t.id)} commentId`);if(P(r.id,`reply ${String(t.id)} comment relation id`)!==i)throw new o(`Reply ${String(t.id)} has an inconsistent comment relation`);E(r.page,n,e,`reply ${String(t.id)}`);let s=O(t,n),c=a.get(s.commentId);if(!c||c.pageId!==n)throw new o(`Reply ${s.id} references a comment outside the project snapshot`);return s});return A(s,`reply`),{projectId:e,pages:[...r.values()].sort((e,t)=>e.pathname.localeCompare(t.pathname)||e.id.localeCompare(t.id)),comments:i,replies:s}}function E(e,t,n,r){let i=M(e,`${r} page`);if(P(i.id,`${r} page id`)!==t)throw new o(`${r} escaped its page relation`);if(P(M(i.project,`${r} project`).id,`${r} project id`)!==n)throw new o(`${r} escaped its project scope`)}function D(r,s){let c=M(r,`comment`),l=P(c.id,`comment id`);if(P(c.pageId,`comment ${l} pageId`)!==s)throw new o(`Comment ${l} escaped its page scope`);P(c.authorAuthUserId,`comment ${l} authorAuthUserId`);let u=!(Object.hasOwn(c,`anchor`)&&Object.hasOwn(c,`target`)),d=u?n(c,c.targetElements,`comment ${l} target`):e(c.target,`comment ${l} target`),f=u?{...d.anchor}:ne(c.anchor,`comment ${l} anchor`);if(!k(f,d.anchor))throw new o(`Comment ${l} has inconsistent anchors`);let p=c.status;if(p!==`open`&&p!==`resolved`)throw new o(`Comment ${l} has an invalid status`);let m=I(c.createdAt,`comment ${l} createdAt`),h=I(c.updatedAt,`comment ${l} updatedAt`);if(h<m)throw new o(`Comment ${l} updatedAt precedes createdAt`);let g=ae(c.resolvedAt,`comment ${l} resolvedAt`),_=oe(c.resolvedById,`comment ${l} resolvedById`);if(p===`open`&&(g!==void 0||_!==void 0))throw new o(`Open comment ${l} contains resolution metadata`);if(p===`resolved`&&(g===void 0||_===void 0||g!==h))throw new o(`Resolved comment ${l} has invalid workflow metadata`);return{id:l,pageId:s,...se(c.pageNumber,`comment ${l} pageNumber`),authorId:P(c.authorId,`comment ${l} authorId`),authorName:P(c.authorName,`comment ${l} authorName`),body:a(c.body,`comment ${l} body`),anchor:f,target:d,...u?re(i(c.scopeEntries)):c.scope==null?{}:{scope:t(c.scope)},status:p,workflowRevision:L(c.workflowRevision,`comment ${l} workflowRevision`),createdAt:m,updatedAt:h,...g===void 0?{}:{resolvedAt:g},..._===void 0?{}:{resolvedById:_}}}function O(e,t){let n=M(e,`reply`),r=P(n.id,`reply id`);if(P(n.pageId,`reply ${r} pageId`)!==t)throw new o(`Reply ${r} escaped its page scope`);P(n.authorAuthUserId,`reply ${r} authorAuthUserId`);let i=I(n.createdAt,`reply ${r} createdAt`),s=I(n.updatedAt,`reply ${r} updatedAt`);if(s<i)throw new o(`Reply ${r} updatedAt precedes createdAt`);return{id:r,commentId:P(n.commentId,`reply ${r} commentId`),authorId:P(n.authorId,`reply ${r} authorId`),authorName:P(n.authorName,`reply ${r} authorName`),body:a(n.body,`reply ${r} body`),createdAt:i,updatedAt:s}}function ne(e,t){let n=M(e,t);if(Object.keys(n).length!==5||!Object.hasOwn(n,`selector`)||!Object.hasOwn(n,`offsetX`)||!Object.hasOwn(n,`offsetY`)||!Object.hasOwn(n,`viewportX`)||!Object.hasOwn(n,`viewportY`))throw new o(`${t} has invalid fields`);return{selector:N(n.selector,`${t} selector`),offsetX:F(n.offsetX,`${t} offsetX`),offsetY:F(n.offsetY,`${t} offsetY`),viewportX:F(n.viewportX,`${t} viewportX`),viewportY:F(n.viewportY,`${t} viewportY`)}}function k(e,t){return e.selector===t.selector&&e.offsetX===t.offsetX&&e.offsetY===t.offsetY&&e.viewportX===t.viewportX&&e.viewportY===t.viewportY}function re(e){return e===void 0?{}:{scope:e}}function A(e,t){let n=new Set;for(let r of e){if(n.has(r.id))throw new o(`Duplicate ${t} ${r.id}`);n.add(r.id)}}function j(e){let t=ie(e).toLowerCase();return t.includes(`expired`)||t.includes(`token`)?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:t.includes(`permission`)||t.includes(`forbidden`)||t.includes(`unauthorized`)?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:`transport`,recovery:`retry`,clearSnapshot:!0}}function ie(e){return e instanceof Error?e.message:e&&typeof e==`object`&&`message`in e?String(e.message):``}function M(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw new o(`${t} is missing or invalid`);return e}function N(e,t){if(typeof e!=`string`)throw new o(`${t} is invalid`);return e}function P(e,t){let n=N(e,t);if(!n.trim())throw new o(`${t} is empty`);return n}function F(e,t){if(typeof e!=`number`||!Number.isFinite(e)||e<0||e>1)throw new o(`${t} is invalid`);return e}function I(e,t){if(!Number.isSafeInteger(e)||e<0||e>0xe677d21fdbff)throw new o(`${t} is invalid`);return e}function L(e,t){if(!Number.isSafeInteger(e)||e<0)throw new o(`${t} is invalid`);return e}function ae(e,t){return e==null?void 0:I(e,t)}function oe(e,t){return e==null?void 0:P(e,t)}function se(e,t){if(e==null)return{};let n=L(e,t);if(n<1)throw new o(`${t} is invalid`);return{pageNumber:n}}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`}),Object.freeze({comments:200,replies:1e3,sourceContexts:200});var ce=class{db;session;roomCredential;#e=new Map;constructor(e,t,n){this.db=e,this.session=t,this.roomCredential=n}subscribe(e,t,n,r){let i=[],a=!1,o=[],s=()=>{a||n({pageId:e,peers:structuredClone(i)})};if(t.cursors||t.chat){let n=U(e,t.roomId),c,l,u=this.roomCredential?.id===t.roomId?this.roomCredential:void 0,d=u??{id:t.roomId,expiresAt:2**53-1,refresh:async()=>void 0},f=t=>{let a=this.db.joinRoom(`visualReview`,t),o=a.subscribePresence({},e=>{if(e.error){r?.(ue(e.error));return}i=Object.values(e.peers).map(e=>le(e)).filter(e=>e!==null).sort((e,t)=>e.displayName.localeCompare(t.displayName)||e.peerId.localeCompare(t.peerId)),s()}),l=c;c=()=>{o(),a.leaveRoom()},this.#e.set(n,{pageId:e,roomId:t,publishPresence:e=>a.publishPresence(e)}),l?.()},p=()=>{if(!u||a)return;let e=Math.max(1e3,d.expiresAt-Date.now()+250);l=setTimeout(()=>{m()},e)},m=async()=>{try{let e=await d.refresh();if(a||!e)return;d={...e,refresh:d.refresh},u&&(u.id=e.id,u.expiresAt=e.expiresAt),f(e.id),p()}catch(e){if(a)return;r?.(ue(e)),a||(l=setTimeout(()=>{m()},5e3))}};f(d.id),p(),o.push(()=>{l&&clearTimeout(l),this.#e.delete(n),c?.()})}return queueMicrotask(s),()=>{if(!a){a=!0;for(let e of o.reverse())try{e()}catch{}}}}publishPresence(e,t,n){let r=this.#e.get(U(e,t));r&&r.publishPresence({reviewerId:this.session.reviewerId,displayName:this.session.displayName,color:n.color,active:n.active,pathname:z(n.pathname),...n.cursorX===void 0?{}:{cursorX:n.cursorX},...n.cursorY===void 0?{}:{cursorY:n.cursorY},...n.viewportWidth===void 0?{}:{viewportWidth:n.viewportWidth},...n.viewportHeight===void 0?{}:{viewportHeight:n.viewportHeight},cursorChat:R(n.cursorChat)})}};function le(e){let t=B(e.reviewerId,128),n=B(e.displayName,512),r=typeof e.color==`string`&&/^#[0-9a-f]{6}$/iu.test(e.color)?e.color:null;if(!t||!n||!r||typeof e.active!=`boolean`)return null;let i=V(e.cursorX),a=V(e.cursorY),o=H(e.viewportWidth),s=H(e.viewportHeight),c=R(e.cursorChat),l=z(e.pathname);return{peerId:e.peerId,reviewerId:t,displayName:n,color:r,active:e.active,pathname:l,...i===void 0?{}:{cursorX:i},...a===void 0?{}:{cursorY:a},...o===void 0?{}:{viewportWidth:o},...s===void 0?{}:{viewportHeight:s},...c?{cursorChat:c}:{}}}function R(e){return typeof e==`string`?e.trim().slice(0,280):``}function z(e){if(typeof e!=`string`)return`/`;let t=e.trim();return t.startsWith(`/`)?t.slice(0,2e3):`/`}function B(e,t){if(typeof e!=`string`)return null;let n=e.trim();return n&&n.length<=t?n:null}function V(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function H(e){let t=V(e);return t!==void 0&&t>0?t:void 0}function U(e,t){return JSON.stringify([e,t])}function ue(e){let t=e&&typeof e==`object`&&`code`in e?String(e.code):``;if(/^INVITATION_(?:EXPIRED|NOT_FOUND|REQUIRED|REVOKED)$/u.test(t))return{type:`access-lost`,code:t===`INVITATION_EXPIRED`?`session-expired`:`membership-revoked`,recovery:`reauthenticate`,clearSnapshot:!0};let n=String(e instanceof Error||e&&typeof e==`object`&&`message`in e?e.message:e).toLowerCase();return n.includes(`expired`)||n.includes(`token`)?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:n.includes(`permission`)||n.includes(`forbidden`)||n.includes(`unauthorized`)?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:`transport`,recovery:`retry`,clearSnapshot:!0}}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`});var W=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewerCommentSaveError`}};function de(e,t){let n=e.replace(/\/+$/,``),r=async(e,r)=>{let i=await t();if(!i)throw new W(`AUTH_REQUIRED`,`로그인 세션이 만료되었습니다. 새로고침 후 다시 시도해 주세요.`);let a;try{a=await fetch(`${n}${e}`,{method:`POST`,headers:{authorization:`Bearer ${i}`,"content-type":`application/json`},body:JSON.stringify(r)})}catch{throw new W(`NETWORK`,`리뷰 서버에 연결하지 못했습니다. 연결을 확인하고 다시 시도해 주세요.`)}if(!a.ok)throw new W(...await G(a))};return{createComment:({target:e,...t})=>{let{environment:n,...i}=e;return r(`/v1/comments`,{...t,target:i,...n?{environment:n}:{}})},createReply:e=>r(`/v1/replies`,e),uploadScreenshot:async({pageId:e,commentId:r,screenshot:i})=>{let a=await t();if(!a)throw new W(`AUTH_REQUIRED`,`로그인 세션이 만료되어 위치 캡처를 저장하지 못했습니다.`);let o;try{o=await fetch(`${n}/v1/comments/${encodeURIComponent(r)}/screenshot`,{method:`PUT`,headers:{authorization:`Bearer ${a}`,"content-type":i.contentType,"x-review-page-id":e,"x-review-image-width":String(i.width),"x-review-image-height":String(i.height)},body:fe(i.bytes)})}catch{throw new W(`NETWORK`,`리뷰 서버에 연결하지 못해 위치 캡처를 저장하지 못했습니다.`)}if(!o.ok)throw new W(...await G(o))}}}function fe(e){let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}async function G(e){let t=e.status>=500?`리뷰 서버가 코멘트를 저장하지 못했습니다. 잠시 후 다시 시도해 주세요.`:`코멘트를 저장하지 못했습니다.`;try{let n=await e.json();if(!n||typeof n!=`object`)return[`UNKNOWN`,t];let r=n;return[typeof r.error==`string`?r.error:`UNKNOWN`,typeof r.message==`string`&&r.message.trim()?r.message:t]}catch{return[`UNKNOWN`,t]}}function pe(e,t={}){let n=e.trim();if(!n)throw Error(`InstantDB app ID is required`);return{async resolve(e){K(e.routeKey,e.location.pathname);let{db:r,user:i}=await me(n,e,t);t.resolveReviewPage&&await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname,signal:e.signal});let{data:a}=await r.queryOnce({reviewers:{$:{where:{authUserId:i.id}}},reviewPages:{},invitations:{}}),o=ge(a.invitations,a.reviewers,a.reviewPages,{invitationId:e.invitationId,authUserId:i.id,email:i.email??``,location:e.location,now:Date.now()});if(!o.ok)throw new s(o.code,$(o.code));let c={authUserId:i.id,reviewerId:o.reviewer.id,projectId:o.reviewer.projectId,invitationId:e.invitationId,email:o.reviewer.email||i.email||``,displayName:o.reviewer.displayName||i.email||`Reviewer`,role:o.reviewer.role===`agency`?`agency`:`reviewer`},l=o.page.id,u=e.collaboration?.scopeId,d=(e.collaboration?.cursors===!0||e.collaboration?.chat===!0)&&t.resolveCollaborationRoom?await t.resolveCollaborationRoom({refreshToken:i.refresh_token,invitationId:e.invitationId,pageId:o.page.id,scopeId:e.collaboration.scopeId,signal:e.signal}):void 0,f=d&&t.resolveCollaborationRoom?{...d,refresh:async()=>{let n=await r.getAuth();if(!(!n?.refresh_token||u===void 0))return t.resolveCollaborationRoom?.({refreshToken:n.refresh_token,invitationId:e.invitationId,pageId:l,scopeId:u,signal:new AbortController().signal})}}:void 0,p=0;return{pageId:o.page.id,resolvePage:async n=>{let i=++p,a=()=>i!==p||n.signal.aborted,o=(e,t,n)=>({pageId:e,...n&&f?{collaborationRoomId:n.id}:{},commit:()=>{a()||(l=e,t!==void 0&&(u=t),!(!n||!f)&&(f.id=n.id,f.expiresAt=n.expiresAt))}});if(n.location.origin!==e.location.origin)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));K(n.routeKey,n.location.pathname);let{data:d}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}});if(a())throw Z();let m=Q(n.location.pathname),h=q(d.reviewPages,c.projectId,n.location.origin,m);if(!h&&t.resolveReviewPage){let i=await r.getAuth();if(a())throw Z();if(!i?.refresh_token)throw Error(`리뷰 세션이 만료되어 페이지를 등록할 수 없습니다.`);if(await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:n.location.origin,pathname:m,signal:n.signal}),a()||({data:d}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}}),a()))throw Z();h=q(d.reviewPages,c.projectId,n.location.origin,m)}if(!h)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));if(n.collaboration?.cursors!==!0&&n.collaboration?.chat!==!0||f===void 0||t.resolveCollaborationRoom===void 0||!n.collaboration)return o(h.id,n.collaboration?.scopeId);let g=await r.getAuth();if(a())throw Z();if(!g?.refresh_token)throw Error(`리뷰 세션이 만료되어 실시간 협업 연결을 갱신할 수 없습니다.`);let _=await t.resolveCollaborationRoom({refreshToken:g.refresh_token,invitationId:e.invitationId,pageId:h.id,scopeId:n.collaboration.scopeId,signal:n.signal});if(a())throw Z();return o(h.id,n.collaboration.scopeId,_)},session:c,repository:new te(r,c,{...t.serviceUrl===void 0?{}:{commentTransport:de(t.serviceUrl,async()=>(await r.getAuth())?.refresh_token??null)}}),...e.collaboration?.cursors===!0||e.collaboration?.chat===!0?{collaborationRepository:new ce(r,c,f)}:{},...(e.collaboration?.cursors===!0||e.collaboration?.chat===!0)&&f?{collaborationRoomId:f.id}:{},allowedStagingHosts:[e.location.hostname],sessionScope:`browser`}}}}function K(e,t){if(e!==void 0&&e!==Q(t))throw Error(`Query/hash page identity requires the Convex review runtime.`)}async function me(e,t,n){let r=(n.databaseFactory??ee)(e),i=!!t.emailToken,a;if(t.emailToken){if(!n.redeemEmailInvitation)throw new s(`INVITATION_EMAIL_REQUIRED`,`이 리뷰는 이메일 초대 링크에서만 시작할 수 있습니다.`);await l(t.authHost,t.signal,t.styleNonce);let e=await n.redeemEmailInvitation({invitationId:t.invitationId,emailToken:t.emailToken,origin:t.location.origin,pathname:t.location.pathname,signal:t.signal});await J(t.signal,()=>r.getAuth())&&await J(t.signal,async()=>r.auth.signOut()),await J(t.signal,async()=>r.auth.signInWithToken(e));let i=await J(t.signal,()=>r.getAuth());if(!i)throw Error(`인증 세션을 확인할 수 없습니다.`);a=i}else a=await _e(r,t.signal);if(!i)try{await he(n.acceptInvitation,{refreshToken:a.refresh_token,invitationId:t.invitationId,suggestedDisplayName:ve(a.email),authHost:t.authHost,signal:t.signal,styleNonce:t.styleNonce})}catch(e){throw X(e,`INVITATION_EMAIL_MISMATCH`)?new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 다시 열어 주세요.`):e}return{db:r,user:a}}async function he(e,t){if(!e)return;let n={refreshToken:t.refreshToken,invitationId:t.invitationId,signal:t.signal};try{await e(n);return}catch(e){if(!X(e,`DISPLAY_NAME_REQUIRED`))throw e}let r=await c(t.authHost,t.signal,t.suggestedDisplayName,t.styleNonce);await e({...n,displayName:r})}function ge(e,t,n,r){let i=e.find(({id:e})=>e===r.invitationId);if(!i||be(i.email)!==be(r.email))return{ok:!1,code:`INVITATION_REQUIRED`};if(i.status===`revoked`)return{ok:!1,code:`INVITATION_REVOKED`};if(i.status===`expired`||i.expiresAt<=r.now)return{ok:!1,code:`INVITATION_EXPIRED`};if(i.status!==`accepted`)return{ok:!1,code:`INVITATION_NOT_ACCEPTED`};let a=t.find(e=>e.authUserId===r.authUserId&&e.projectId===i.projectId);if(!a)return{ok:!1,code:`MEMBERSHIP_REQUIRED`};let o=Q(r.location.pathname),s=q(n,i.projectId,r.location.origin,o);return s?{ok:!0,invitation:i,reviewer:a,page:s}:{ok:!1,code:`PAGE_NOT_FOUND`}}function q(e,t,n,r){let i=e.filter(e=>e.projectId===t&&Q(e.pathname)===r),a=i.filter(({stagingOrigin:e})=>e===n);return(a.length>0?a:i).sort((e,t)=>(e.createdAt??2**53-1)-(t.createdAt??2**53-1)||e.id.localeCompare(t.id))[0]}async function _e(e,t){let n=await J(t,()=>e.getAuth());if(n)return n;throw new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 열어 주세요.`)}async function J(e,t){Y(e);let n=await t();return Y(e),n}function Y(e){if(e.aborted)throw ye()}function ve(e){return e?.split(`@`)[0]?.trim().slice(0,120)??``}function X(e,t){return!!(e&&typeof e==`object`&&`code`in e&&e.code===t)}function Z(){return new DOMException(`Aborted`,`AbortError`)}function ye(){return new s(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function Q(e){return e.replace(/\/+$/,``)||`/`}function be(e){return e.trim().toLowerCase()}function $(e){switch(e){case`INVITATION_REQUIRED`:return`현재 계정과 일치하는 리뷰 초대를 찾을 수 없습니다.`;case`INVITATION_NOT_ACCEPTED`:return`아직 수락되지 않은 리뷰 초대입니다.`;case`INVITATION_REVOKED`:return`취소된 리뷰 초대입니다. 계속 검토하려면 담당자에게 다시 요청하세요.`;case`INVITATION_EXPIRED`:return`만료된 리뷰 초대입니다. 담당자에게 새 링크를 요청하세요.`;case`MEMBERSHIP_REQUIRED`:return`이 프로젝트의 reviewer membership이 없습니다.`;case`PAGE_NOT_FOUND`:return`이 페이지는 아직 리뷰 대상이 아닙니다. 담당자에게 문의하세요.`}}export{pe as createInstantReviewRuntime};
1
+ import{a as e,c as t,i as n,o as r,r as i,s as a,t as o}from"./src-CjYM9---.js";import{a as s,n as c,t as l}from"./reviewer-auth-presentation-CAm-YbaR.js";import{i as u,init as d}from"@instantdb/core";new Uint8Array([107,167,184,17,157,173,17,209,128,180,0,192,79,212,48,200]);const f=u.schema({entities:{$files:u.entity({path:u.string().unique().indexed(),url:u.string()}),$users:u.entity({email:u.string().unique().indexed()}),projects:u.entity({name:u.string(),slug:u.string().unique().indexed(),allowedOrigin:u.string().optional(),originDisconnectedAt:u.number().optional(),originModelVersion:u.number().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),projectOrigins:u.entity({projectId:u.string().indexed(),originKey:u.string().unique().indexed(),origin:u.string().indexed(),label:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPages:u.entity({projectId:u.string().indexed(),autoRegistrationSlot:u.string().unique().indexed().optional(),name:u.string(),pathname:u.string(),stagingOrigin:u.string(),createdAt:u.number().indexed(),updatedAt:u.number()}),reviewPageBuilds:u.entity({projectId:u.string().indexed(),pageId:u.string().unique().indexed(),buildId:u.string(),gitCommit:u.string().optional(),generatedAt:u.number().optional(),mode:u.string(),updatedAt:u.number().indexed()}),reviewers:u.entity({authUserId:u.string().indexed(),projectId:u.string().indexed(),email:u.string().indexed(),displayName:u.string(),role:u.string(),createdAt:u.number()}),reviewerIdentityClaims:u.entity({reviewerId:u.string().unique().indexed(),projectId:u.string().indexed(),authUserId:u.string().indexed(),email:u.string(),displayName:u.string(),role:u.string(),createdAt:u.number()}),invitations:u.entity({projectId:u.string().indexed(),reviewerId:u.string().indexed().optional(),email:u.string().indexed(),status:u.string().indexed(),claimRevision:u.number(),claimedByAuthUserId:u.string().indexed().optional(),expiresAt:u.number().indexed(),createdAt:u.number(),acceptedAt:u.number().optional(),revokedAt:u.number().optional(),emailTokenHash:u.string().optional(),emailSentAt:u.number().optional(),reviewUrl:u.string().optional()}),comments:u.entity({pageId:u.string().indexed(),pageNumber:u.number().optional(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),anchorSelector:u.string().optional(),anchorOffsetX:u.number().optional(),anchorOffsetY:u.number().optional(),anchorViewportX:u.number().optional(),anchorViewportY:u.number().optional(),targetKind:u.string().optional(),targetPageX:u.number().optional(),targetPageY:u.number().optional(),targetWidth:u.number().optional(),targetHeight:u.number().optional(),targetViewportX:u.number().optional(),targetViewportY:u.number().optional(),targetViewportWidth:u.number().optional(),targetViewportHeight:u.number().optional(),targetPageOrigin:u.string().optional(),targetPagePathname:u.string().optional(),targetPageSearch:u.string().optional(),targetPageHash:u.string().optional(),targetPageTitle:u.string().optional(),targetCapturedAt:u.number().optional(),environmentBrowser:u.string().optional(),environmentOperatingSystem:u.string().optional(),environmentDeviceType:u.string().optional(),environmentScreenWidth:u.number().optional(),environmentScreenHeight:u.number().optional(),environmentDevicePixelRatio:u.number().optional(),status:u.string().indexed(),workflowRevision:u.number(),createdAt:u.number().indexed(),updatedAt:u.number().indexed(),resolvedAt:u.number().optional(),resolvedById:u.string().optional(),deletingAt:u.number().optional()}),commentWorkflowClaims:u.entity({claimKey:u.string().unique().indexed(),commentId:u.string().indexed(),expectedWorkflowRevision:u.number(),createdAt:u.number().indexed()}),commentPageNumberClaims:u.entity({claimKey:u.string().unique().indexed(),pageId:u.string().indexed(),pageNumber:u.number(),commentId:u.string().indexed(),createdAt:u.number().indexed()}),commentTargetElements:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),selector:u.string(),tagName:u.string(),textPreview:u.string(),pageX:u.number(),pageY:u.number(),width:u.number(),height:u.number(),viewportX:u.number(),viewportY:u.number(),viewportWidth:u.number(),viewportHeight:u.number(),authorAuthUserId:u.string().indexed()}),commentScopeEntries:u.entity({commentId:u.string().indexed(),slotKey:u.string().unique().indexed().optional(),ordinal:u.number(),key:u.string(),valueType:u.string(),stringValue:u.string().optional(),numberValue:u.number().optional(),booleanValue:u.boolean().optional(),authorAuthUserId:u.string().indexed()}),commentSourceContexts:u.entity({commentId:u.string().unique().indexed(),pageId:u.string().indexed(),authorAuthUserId:u.string().indexed(),contextJson:u.string(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed()}),replies:u.entity({pageId:u.string().indexed(),commentId:u.string().indexed(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string().optional(),requestPayloadHash:u.string().optional(),createdAt:u.number().indexed(),updatedAt:u.number().indexed()}),reviewMessages:u.entity({pageId:u.string().indexed(),pathname:u.string().indexed(),authorId:u.string().indexed(),authorAuthUserId:u.string().indexed(),authorName:u.string(),body:u.string(),bodyContentMarker:u.string(),createdAt:u.number().indexed(),updatedAt:u.number().indexed()})},links:{projectOwners:{forward:{on:`projects`,has:`many`,label:`owners`},reverse:{on:`$users`,has:`many`,label:`ownedProjects`}},projectPages:{forward:{on:`projects`,has:`many`,label:`pages`},reverse:{on:`reviewPages`,has:`one`,label:`project`}},projectOriginProject:{forward:{on:`projects`,has:`many`,label:`origins`},reverse:{on:`projectOrigins`,has:`one`,label:`project`}},pageCurrentBuild:{forward:{on:`reviewPageBuilds`,has:`one`,label:`page`,required:!0,onDelete:`cascade`},reverse:{on:`reviewPages`,has:`one`,label:`currentBuild`}},projectReviewers:{forward:{on:`projects`,has:`many`,label:`reviewers`},reverse:{on:`reviewers`,has:`one`,label:`project`}},reviewerUser:{forward:{on:`reviewers`,has:`one`,label:`$user`},reverse:{on:`$users`,has:`many`,label:`reviewerMemberships`}},projectInvitations:{forward:{on:`projects`,has:`many`,label:`invitations`},reverse:{on:`invitations`,has:`one`,label:`project`}},invitationReviewer:{forward:{on:`invitations`,has:`one`,label:`reviewer`},reverse:{on:`reviewers`,has:`many`,label:`acceptedInvitations`}},invitationActiveUser:{forward:{on:`invitations`,has:`one`,label:`activeUser`},reverse:{on:`$users`,has:`many`,label:`activeReviewInvitations`}},pageComments:{forward:{on:`reviewPages`,has:`many`,label:`comments`},reverse:{on:`comments`,has:`one`,label:`page`}},pageReviewMessages:{forward:{on:`reviewPages`,has:`many`,label:`reviewMessages`},reverse:{on:`reviewMessages`,has:`one`,label:`page`}},commentReplies:{forward:{on:`comments`,has:`many`,label:`replies`},reverse:{on:`replies`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`}},commentTargetElements:{forward:{on:`commentTargetElements`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`targetElements`}},commentScopeEntries:{forward:{on:`commentScopeEntries`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`many`,label:`scopeEntries`}},commentSourceContext:{forward:{on:`commentSourceContexts`,has:`one`,label:`comment`,required:!0,onDelete:`cascade`},reverse:{on:`comments`,has:`one`,label:`sourceContext`}},pageSourceContexts:{forward:{on:`commentSourceContexts`,has:`one`,label:`page`,required:!0},reverse:{on:`reviewPages`,has:`many`,label:`sourceContexts`}},sourceContextAuthor:{forward:{on:`commentSourceContexts`,has:`one`,label:`author`,required:!0},reverse:{on:`reviewers`,has:`many`,label:`sourceContexts`}},commentAuthor:{forward:{on:`comments`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`comments`}},replyAuthor:{forward:{on:`replies`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`replies`}},reviewMessageAuthor:{forward:{on:`reviewMessages`,has:`one`,label:`author`},reverse:{on:`reviewers`,has:`many`,label:`reviewMessages`}}},rooms:{visualReview:{presence:u.entity({reviewerId:u.string(),displayName:u.string(),color:u.string(),active:u.boolean(),pathname:u.string().optional(),cursorX:u.number().optional(),cursorY:u.number().optional(),viewportWidth:u.number().optional(),viewportHeight:u.number().optional(),cursorChat:u.string().optional()})}}}),p={$default:`false`},m={$default:`false`},h=`$user.activeReviewInvitations`,g=`${h}.project.id`;function _(e){return[`auth.id != null`,`size(auth.ref('${h}.id')) > 0`,`size(auth.ref('${g}')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.status')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.expiresAt')) == size(auth.ref('${h}.id'))`,`size(auth.ref('${h}.revokedAt')) == 0`,`auth.ref('${h}.status').all(status, status == 'accepted')`,`auth.ref('${h}.expiresAt').all(expiresAt, request.time < timestamp(expiresAt))`,e].join(` && `)}function v(...e){return e.map(e=>`(${e})`).join(` && `)}function y(...e){return e.map(e=>`(${e})`).join(` || `)}function b(e){let t=`data.ref('${e}project.origins.origin')`,n=`data.ref('${e}project.allowedOrigin')`,r=`data.ref('${e}project.originModelVersion')`;return y(`request.origin in ${t}`,v(`size(${r}) == 0`,`request.origin in ${n}`))}_(`data.ref('project.id')[0] in auth.ref('${g}')`),y(`request.origin in data.ref('project.origins.origin')`,v(`size(data.ref('project.originModelVersion')) == 0`,`request.origin in data.ref('project.allowedOrigin')`)),y(`request.origin in data.ref('origins.origin')`,v(`size(data.ref('originModelVersion')) == 0`,`request.origin in data.ref('allowedOrigin')`)),_(`data.ref('page.project.id')[0] in auth.ref('${g}')`),b(`page.`),y(`auth.id != null && auth.id in data.ref('comment.page.project.owners.id')`,v(_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`))),_(`data.ref('comment.page.project.id')[0] in auth.ref('${g}')`),b(`comment.page.`),b(``);const x=v(`data.reviewerId != null`,`data.projectId in data.ref('project.id')`,`data.reviewerId in data.ref('reviewer.id')`,`data.projectId in data.ref('reviewer.project.id')`,`auth.id in data.ref('reviewer.$user.id')`,`auth.email in data.ref('reviewer.email')`);v(`auth.id != null`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`request.modifiedFields.all(field, field in ['status', 'claimedByAuthUserId', 'acceptedAt', 'claimRevision', 'activeUser'])`,`'status' in request.modifiedFields`,`'claimedByAuthUserId' in request.modifiedFields`,`'acceptedAt' in request.modifiedFields`,`'claimRevision' in request.modifiedFields`,`'activeUser' in request.modifiedFields`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.revokedAt == null`,`data.status in ['pending', 'accepted']`,`data.status == 'pending' || data.claimedByAuthUserId == auth.id`,`request.time < timestamp(data.expiresAt)`,`newData.status == 'accepted'`,`newData.claimedByAuthUserId == auth.id`,`newData.claimRevision == data.claimRevision + 1`,`math.abs((request.time - timestamp(newData.acceptedAt)).getMinutes()) <= 1`,x),v(`actions.data == 'update'`,`actions.linkedData == 'update'`,`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.revokedAt == null`,`linkedData.status == 'accepted'`,`linkedData.claimedByAuthUserId == auth.id`,`request.time < timestamp(linkedData.expiresAt)`,`math.abs((request.time - timestamp(linkedData.acceptedAt)).getMinutes()) <= 1`,`linkedData.projectId in linkedData.ref('project.id')`,`linkedData.reviewerId in linkedData.ref('reviewer.id')`,`linkedData.projectId in linkedData.ref('reviewer.project.id')`,`auth.id in linkedData.ref('reviewer.$user.id')`,`auth.email in linkedData.ref('reviewer.email')`),v(`auth.id != null`,`auth.email != null`,`linkedData.id == auth.id`,`linkedData.email == auth.email`,`data.email == auth.email`,`data.claimedByAuthUserId == auth.id`,`auth.id in data.ref('activeUser.id')`),v(`auth.id != null`,`auth.email != null`,`data.id == auth.id`,`data.email == auth.email`,`linkedData.email == auth.email`,`linkedData.claimedByAuthUserId == auth.id`,`auth.id in linkedData.ref('activeUser.id')`),{...p},{...p},{...m},{...m},_(`data.id in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...p},{...m},_(`data.ref('project.id')[0] in auth.ref('${g}')`),{...p},{...m},{...p},{...p},{...m},{...m},{...p},{...m},{...p},{...p},{...p},{...p},{...p},{...p},{...p};function ee(e){if(!e.trim())throw Error(`InstantDB app ID is required`);return d({appId:e,schema:f,devtool:!1})}var te=class{db;session;#e;attachScreenshot;constructor(e,t,n={}){this.db=e,this.session=t,this.#e=n.commentTransport,this.#e&&(this.attachScreenshot=(e,t,n)=>this.#e.uploadScreenshot({pageId:e,commentId:t,screenshot:n}))}async getSnapshot(e){let{data:t}=await this.db.queryOnce(S(e,this.session.authUserId));return w(e,t.comments,t.replies)}async getProjectSnapshot(e){this.#t(e);let{data:t}=await this.db.queryOnce(C(e,this.session.authUserId));return T(e,t.comments,t.replies)}subscribe(e,t,n){return this.db.subscribeQuery(S(e,this.session.authUserId),r=>{if(r.error){n?.(j(r.error));return}if(r.data)try{t(w(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}subscribeProject(e,t,n){return this.#t(e),this.db.subscribeQuery(C(e,this.session.authUserId),r=>{if(r.error){n?.(j(r.error));return}if(r.data)try{t(T(e,r.data.comments,r.data.replies))}catch{n?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})}async mutate(e,t){switch(t.type){case`comment.upsert`:{if(t.comment.pageId!==e)throw Error(`Comment page mismatch`);let n=t.comment;if(n.authorId!==this.session.reviewerId)throw Error(`Comment author mismatch`);let i=a(n.body).trim(),s=I(n.createdAt,`Comment createdAt`);if(I(n.updatedAt,`Comment updatedAt`)<s)throw new o(`Comment updatedAt precedes createdAt`);if(n.createdAt!==n.updatedAt)throw new o(`Comment edits must go through the review service`);if(n.status!==`open`||n.resolvedAt!==void 0||n.resolvedById!==void 0)throw new o(`New comments must start open`);let c=r(n.target);if(!k(n.anchor,c.publicTarget.anchor))throw new o(`Comment anchor does not match its target`);if(!this.#e)throw new o(`Comment creation requires the review service transport`);await this.#e.createComment({pageId:e,commentId:n.id,body:i,target:n.target,...n.scope===void 0?{}:{scope:n.scope}});return}case`comment.delete`:throw new o(`Comment deletion must go through the review service`);case`reply.upsert`:{let e=t.reply;if(e.authorId!==this.session.reviewerId)throw Error(`Reply author mismatch`);let n=a(e.body,`Reply body`).trim(),r=I(e.createdAt,`Reply createdAt`);if(I(e.updatedAt,`Reply updatedAt`)<r)throw new o(`Reply updatedAt precedes createdAt`);if(e.createdAt!==e.updatedAt)throw new o(`Reply edits must go through the review service`);if(!this.#e)throw new o(`Reply creation requires the review service transport`);await this.#e.createReply({commentId:e.commentId,replyId:e.id,body:n});return}case`reply.delete`:throw new o(`Reply deletion must go through the review service`)}}#t(e){if(!e.trim()||e!==this.session.projectId)throw Error(`Project does not belong to the reviewer session`)}};function S(e,t){return{comments:{$:{where:{pageId:e},order:{createdAt:`asc`}},targetElements:{},scopeEntries:{}},replies:{$:{where:{pageId:e},order:{createdAt:`asc`}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function C(e,t){return{comments:{$:{where:{"page.project.id":e},order:{createdAt:`asc`}},page:{project:{}},targetElements:{},scopeEntries:{}},replies:{$:{where:{"comment.page.project.id":e},order:{createdAt:`asc`}},comment:{page:{project:{}}}},$users:{$:{where:{id:t}},activeReviewInvitations:{project:{}}}}}function w(e,t,n){let r=t.map(t=>D(t,e));A(r,`comment`);let i=new Set(r.map(({id:e})=>e)),a=n.map(t=>O(t,e));A(a,`reply`);for(let e of a)if(!i.has(e.commentId))throw new o(`Reply ${e.id} references a comment outside the page snapshot`);return{pageId:e,comments:r,replies:a}}function T(e,t,n){let r=new Map,i=t.map(t=>{let n=P(t.pageId,`comment pageId`);E(t.page,n,e,`comment ${String(t.id)}`);let i=M(t.page,`comment ${String(t.id)} page`),a=D(t,n),o=`/`;try{o=new URL(a.target.pageUrl).pathname||`/`}catch{}return r.set(n,{id:n,name:typeof i.name==`string`&&i.name.trim()?i.name.trim():a.target.pageTitle.trim()||o,pathname:typeof i.pathname==`string`&&i.pathname.trim()?i.pathname.trim():o}),a});A(i,`comment`);let a=new Map(i.map(e=>[e.id,e])),s=n.map(t=>{let n=P(t.pageId,`reply pageId`),r=M(t.comment,`reply ${String(t.id)} comment`),i=P(t.commentId,`reply ${String(t.id)} commentId`);if(P(r.id,`reply ${String(t.id)} comment relation id`)!==i)throw new o(`Reply ${String(t.id)} has an inconsistent comment relation`);E(r.page,n,e,`reply ${String(t.id)}`);let s=O(t,n),c=a.get(s.commentId);if(!c||c.pageId!==n)throw new o(`Reply ${s.id} references a comment outside the project snapshot`);return s});return A(s,`reply`),{projectId:e,pages:[...r.values()].sort((e,t)=>e.pathname.localeCompare(t.pathname)||e.id.localeCompare(t.id)),comments:i,replies:s}}function E(e,t,n,r){let i=M(e,`${r} page`);if(P(i.id,`${r} page id`)!==t)throw new o(`${r} escaped its page relation`);if(P(M(i.project,`${r} project`).id,`${r} project id`)!==n)throw new o(`${r} escaped its project scope`)}function D(r,s){let c=M(r,`comment`),l=P(c.id,`comment id`);if(P(c.pageId,`comment ${l} pageId`)!==s)throw new o(`Comment ${l} escaped its page scope`);P(c.authorAuthUserId,`comment ${l} authorAuthUserId`);let u=!(Object.hasOwn(c,`anchor`)&&Object.hasOwn(c,`target`)),d=u?n(c,c.targetElements,`comment ${l} target`):e(c.target,`comment ${l} target`),f=u?{...d.anchor}:ne(c.anchor,`comment ${l} anchor`);if(!k(f,d.anchor))throw new o(`Comment ${l} has inconsistent anchors`);let p=c.status;if(p!==`open`&&p!==`resolved`)throw new o(`Comment ${l} has an invalid status`);let m=I(c.createdAt,`comment ${l} createdAt`),h=I(c.updatedAt,`comment ${l} updatedAt`);if(h<m)throw new o(`Comment ${l} updatedAt precedes createdAt`);let g=ae(c.resolvedAt,`comment ${l} resolvedAt`),_=oe(c.resolvedById,`comment ${l} resolvedById`);if(p===`open`&&(g!==void 0||_!==void 0))throw new o(`Open comment ${l} contains resolution metadata`);if(p===`resolved`&&(g===void 0||_===void 0||g!==h))throw new o(`Resolved comment ${l} has invalid workflow metadata`);return{id:l,pageId:s,...se(c.pageNumber,`comment ${l} pageNumber`),authorId:P(c.authorId,`comment ${l} authorId`),authorName:P(c.authorName,`comment ${l} authorName`),body:a(c.body,`comment ${l} body`),anchor:f,target:d,...u?re(i(c.scopeEntries)):c.scope==null?{}:{scope:t(c.scope)},status:p,workflowRevision:L(c.workflowRevision,`comment ${l} workflowRevision`),createdAt:m,updatedAt:h,...g===void 0?{}:{resolvedAt:g},..._===void 0?{}:{resolvedById:_}}}function O(e,t){let n=M(e,`reply`),r=P(n.id,`reply id`);if(P(n.pageId,`reply ${r} pageId`)!==t)throw new o(`Reply ${r} escaped its page scope`);P(n.authorAuthUserId,`reply ${r} authorAuthUserId`);let i=I(n.createdAt,`reply ${r} createdAt`),s=I(n.updatedAt,`reply ${r} updatedAt`);if(s<i)throw new o(`Reply ${r} updatedAt precedes createdAt`);return{id:r,commentId:P(n.commentId,`reply ${r} commentId`),authorId:P(n.authorId,`reply ${r} authorId`),authorName:P(n.authorName,`reply ${r} authorName`),body:a(n.body,`reply ${r} body`),createdAt:i,updatedAt:s}}function ne(e,t){let n=M(e,t);if(Object.keys(n).length!==5||!Object.hasOwn(n,`selector`)||!Object.hasOwn(n,`offsetX`)||!Object.hasOwn(n,`offsetY`)||!Object.hasOwn(n,`viewportX`)||!Object.hasOwn(n,`viewportY`))throw new o(`${t} has invalid fields`);return{selector:N(n.selector,`${t} selector`),offsetX:F(n.offsetX,`${t} offsetX`),offsetY:F(n.offsetY,`${t} offsetY`),viewportX:F(n.viewportX,`${t} viewportX`),viewportY:F(n.viewportY,`${t} viewportY`)}}function k(e,t){return e.selector===t.selector&&e.offsetX===t.offsetX&&e.offsetY===t.offsetY&&e.viewportX===t.viewportX&&e.viewportY===t.viewportY}function re(e){return e===void 0?{}:{scope:e}}function A(e,t){let n=new Set;for(let r of e){if(n.has(r.id))throw new o(`Duplicate ${t} ${r.id}`);n.add(r.id)}}function j(e){let t=ie(e).toLowerCase();return t.includes(`expired`)||t.includes(`token`)?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:t.includes(`permission`)||t.includes(`forbidden`)||t.includes(`unauthorized`)?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:`transport`,recovery:`retry`,clearSnapshot:!0}}function ie(e){return e instanceof Error?e.message:e&&typeof e==`object`&&`message`in e?String(e.message):``}function M(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw new o(`${t} is missing or invalid`);return e}function N(e,t){if(typeof e!=`string`)throw new o(`${t} is invalid`);return e}function P(e,t){let n=N(e,t);if(!n.trim())throw new o(`${t} is empty`);return n}function F(e,t){if(typeof e!=`number`||!Number.isFinite(e)||e<0||e>1)throw new o(`${t} is invalid`);return e}function I(e,t){if(!Number.isSafeInteger(e)||e<0||e>0xe677d21fdbff)throw new o(`${t} is invalid`);return e}function L(e,t){if(!Number.isSafeInteger(e)||e<0)throw new o(`${t} is invalid`);return e}function ae(e,t){return e==null?void 0:I(e,t)}function oe(e,t){return e==null?void 0:P(e,t)}function se(e,t){if(e==null)return{};let n=L(e,t);if(n<1)throw new o(`${t} is invalid`);return{pageNumber:n}}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`}),Object.freeze({comments:200,replies:1e3,sourceContexts:200});var ce=class{db;session;roomCredential;#e=new Map;constructor(e,t,n){this.db=e,this.session=t,this.roomCredential=n}subscribe(e,t,n,r){let i=[],a=!1,o=[],s=()=>{a||n({pageId:e,peers:structuredClone(i)})};if(t.cursors||t.chat){let n=U(e,t.roomId),c,l,u=this.roomCredential?.id===t.roomId?this.roomCredential:void 0,d=u??{id:t.roomId,expiresAt:2**53-1,refresh:async()=>void 0},f=t=>{let a=this.db.joinRoom(`visualReview`,t),o=a.subscribePresence({},e=>{if(e.error){r?.(ue(e.error));return}i=Object.values(e.peers).map(e=>le(e)).filter(e=>e!==null).sort((e,t)=>e.displayName.localeCompare(t.displayName)||e.peerId.localeCompare(t.peerId)),s()}),l=c;c=()=>{o(),a.leaveRoom()},this.#e.set(n,{pageId:e,roomId:t,publishPresence:e=>a.publishPresence(e)}),l?.()},p=()=>{if(!u||a)return;let e=Math.max(1e3,d.expiresAt-Date.now()+250);l=setTimeout(()=>{m()},e)},m=async()=>{try{let e=await d.refresh();if(a||!e)return;d={...e,refresh:d.refresh},u&&(u.id=e.id,u.expiresAt=e.expiresAt),f(e.id),p()}catch(e){if(a)return;r?.(ue(e)),a||(l=setTimeout(()=>{m()},5e3))}};f(d.id),p(),o.push(()=>{l&&clearTimeout(l),this.#e.delete(n),c?.()})}return queueMicrotask(s),()=>{if(!a){a=!0;for(let e of o.reverse())try{e()}catch{}}}}publishPresence(e,t,n){let r=this.#e.get(U(e,t));r&&r.publishPresence({reviewerId:this.session.reviewerId,displayName:this.session.displayName,color:n.color,active:n.active,pathname:z(n.pathname),...n.cursorX===void 0?{}:{cursorX:n.cursorX},...n.cursorY===void 0?{}:{cursorY:n.cursorY},...n.viewportWidth===void 0?{}:{viewportWidth:n.viewportWidth},...n.viewportHeight===void 0?{}:{viewportHeight:n.viewportHeight},cursorChat:R(n.cursorChat)})}};function le(e){let t=B(e.reviewerId,128),n=B(e.displayName,512),r=typeof e.color==`string`&&/^#[0-9a-f]{6}$/iu.test(e.color)?e.color:null;if(!t||!n||!r||typeof e.active!=`boolean`)return null;let i=V(e.cursorX),a=V(e.cursorY),o=H(e.viewportWidth),s=H(e.viewportHeight),c=R(e.cursorChat),l=z(e.pathname);return{peerId:e.peerId,reviewerId:t,displayName:n,color:r,active:e.active,pathname:l,...i===void 0?{}:{cursorX:i},...a===void 0?{}:{cursorY:a},...o===void 0?{}:{viewportWidth:o},...s===void 0?{}:{viewportHeight:s},...c?{cursorChat:c}:{}}}function R(e){return typeof e==`string`?e.trim().slice(0,280):``}function z(e){if(typeof e!=`string`)return`/`;let t=e.trim();return t.startsWith(`/`)?t.slice(0,2e3):`/`}function B(e,t){if(typeof e!=`string`)return null;let n=e.trim();return n&&n.length<=t?n:null}function V(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function H(e){let t=V(e);return t!==void 0&&t>0?t:void 0}function U(e,t){return JSON.stringify([e,t])}function ue(e){let t=e&&typeof e==`object`&&`code`in e?String(e.code):``;if(/^INVITATION_(?:EXPIRED|NOT_FOUND|REQUIRED|REVOKED)$/u.test(t))return{type:`access-lost`,code:t===`INVITATION_EXPIRED`?`session-expired`:`membership-revoked`,recovery:`reauthenticate`,clearSnapshot:!0};let n=String(e instanceof Error||e&&typeof e==`object`&&`message`in e?e.message:e).toLowerCase();return n.includes(`expired`)||n.includes(`token`)?{type:`access-lost`,code:`session-expired`,recovery:`reauthenticate`,clearSnapshot:!0}:n.includes(`permission`)||n.includes(`forbidden`)||n.includes(`unauthorized`)?{type:`access-lost`,code:`permission-denied`,recovery:`reauthenticate`,clearSnapshot:!0}:{type:`error`,code:`transport`,recovery:`retry`,clearSnapshot:!0}}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`});var W=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewerCommentSaveError`}};function de(e,t){let n=e.replace(/\/+$/,``),r=async(e,r)=>{let i=await t();if(!i)throw new W(`AUTH_REQUIRED`,`로그인 세션이 만료되었습니다. 새로고침 후 다시 시도해 주세요.`);let a;try{a=await fetch(`${n}${e}`,{method:`POST`,headers:{authorization:`Bearer ${i}`,"content-type":`application/json`},body:JSON.stringify(r)})}catch{throw new W(`NETWORK`,`리뷰 서버에 연결하지 못했습니다. 연결을 확인하고 다시 시도해 주세요.`)}if(!a.ok)throw new W(...await G(a))};return{createComment:({target:e,...t})=>{let{environment:n,...i}=e;return r(`/v1/comments`,{...t,target:i,...n?{environment:n}:{}})},createReply:e=>r(`/v1/replies`,e),uploadScreenshot:async({pageId:e,commentId:r,screenshot:i})=>{let a=await t();if(!a)throw new W(`AUTH_REQUIRED`,`로그인 세션이 만료되어 위치 캡처를 저장하지 못했습니다.`);let o;try{o=await fetch(`${n}/v1/comments/${encodeURIComponent(r)}/screenshot`,{method:`PUT`,headers:{authorization:`Bearer ${a}`,"content-type":i.contentType,"x-review-page-id":e,"x-review-image-width":String(i.width),"x-review-image-height":String(i.height)},body:fe(i.bytes)})}catch{throw new W(`NETWORK`,`리뷰 서버에 연결하지 못해 위치 캡처를 저장하지 못했습니다.`)}if(!o.ok)throw new W(...await G(o))}}}function fe(e){let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}async function G(e){let t=e.status>=500?`리뷰 서버가 코멘트를 저장하지 못했습니다. 잠시 후 다시 시도해 주세요.`:`코멘트를 저장하지 못했습니다.`;try{let n=await e.json();if(!n||typeof n!=`object`)return[`UNKNOWN`,t];let r=n;return[typeof r.error==`string`?r.error:`UNKNOWN`,typeof r.message==`string`&&r.message.trim()?r.message:t]}catch{return[`UNKNOWN`,t]}}function pe(e,t={}){let n=e.trim();if(!n)throw Error(`InstantDB app ID is required`);return{async resolve(e){K(e.routeKey,e.location.pathname);let{db:r,user:i}=await me(n,e,t);t.resolveReviewPage&&await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname,signal:e.signal});let{data:a}=await r.queryOnce({reviewers:{$:{where:{authUserId:i.id}}},reviewPages:{},invitations:{}}),o=ge(a.invitations,a.reviewers,a.reviewPages,{invitationId:e.invitationId,authUserId:i.id,email:i.email??``,location:e.location,now:Date.now()});if(!o.ok)throw new s(o.code,$(o.code));let c={authUserId:i.id,reviewerId:o.reviewer.id,projectId:o.reviewer.projectId,invitationId:e.invitationId,email:o.reviewer.email||i.email||``,displayName:o.reviewer.displayName||i.email||`Reviewer`,role:o.reviewer.role===`agency`?`agency`:`reviewer`},l=o.page.id,u=e.collaboration?.scopeId,d=(e.collaboration?.cursors===!0||e.collaboration?.chat===!0)&&t.resolveCollaborationRoom?await t.resolveCollaborationRoom({refreshToken:i.refresh_token,invitationId:e.invitationId,pageId:o.page.id,scopeId:e.collaboration.scopeId,signal:e.signal}):void 0,f=d&&t.resolveCollaborationRoom?{...d,refresh:async()=>{let n=await r.getAuth();if(!(!n?.refresh_token||u===void 0))return t.resolveCollaborationRoom?.({refreshToken:n.refresh_token,invitationId:e.invitationId,pageId:l,scopeId:u,signal:new AbortController().signal})}}:void 0,p=0;return{pageId:o.page.id,resolvePage:async n=>{let i=++p,a=()=>i!==p||n.signal.aborted,o=(e,t,n)=>({pageId:e,...n&&f?{collaborationRoomId:n.id}:{},commit:()=>{a()||(l=e,t!==void 0&&(u=t),!(!n||!f)&&(f.id=n.id,f.expiresAt=n.expiresAt))}});if(n.location.origin!==e.location.origin)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));K(n.routeKey,n.location.pathname);let{data:d}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}});if(a())throw Z();let m=Q(n.location.pathname),h=q(d.reviewPages,c.projectId,n.location.origin,m);if(!h&&t.resolveReviewPage){let i=await r.getAuth();if(a())throw Z();if(!i?.refresh_token)throw Error(`리뷰 세션이 만료되어 페이지를 등록할 수 없습니다.`);if(await t.resolveReviewPage({refreshToken:i.refresh_token,invitationId:e.invitationId,origin:n.location.origin,pathname:m,signal:n.signal}),a()||({data:d}=await r.queryOnce({reviewPages:{$:{where:{projectId:c.projectId}}}}),a()))throw Z();h=q(d.reviewPages,c.projectId,n.location.origin,m)}if(!h)throw new s(`PAGE_NOT_FOUND`,$(`PAGE_NOT_FOUND`));if(n.collaboration?.cursors!==!0&&n.collaboration?.chat!==!0||f===void 0||t.resolveCollaborationRoom===void 0||!n.collaboration)return o(h.id,n.collaboration?.scopeId);let g=await r.getAuth();if(a())throw Z();if(!g?.refresh_token)throw Error(`리뷰 세션이 만료되어 실시간 협업 연결을 갱신할 수 없습니다.`);let _=await t.resolveCollaborationRoom({refreshToken:g.refresh_token,invitationId:e.invitationId,pageId:h.id,scopeId:n.collaboration.scopeId,signal:n.signal});if(a())throw Z();return o(h.id,n.collaboration.scopeId,_)},session:c,repository:new te(r,c,{...t.serviceUrl===void 0?{}:{commentTransport:de(t.serviceUrl,async()=>(await r.getAuth())?.refresh_token??null)}}),...e.collaboration?.cursors===!0||e.collaboration?.chat===!0?{collaborationRepository:new ce(r,c,f)}:{},...(e.collaboration?.cursors===!0||e.collaboration?.chat===!0)&&f?{collaborationRoomId:f.id}:{},allowedStagingHosts:[e.location.hostname],sessionScope:`browser`}}}}function K(e,t){if(e!==void 0&&e!==Q(t))throw Error(`Query/hash page identity requires the Convex review runtime.`)}async function me(e,t,n){let r=(n.databaseFactory??ee)(e),i=!!t.emailToken,a;if(t.emailToken){if(!n.redeemEmailInvitation)throw new s(`INVITATION_EMAIL_REQUIRED`,`이 리뷰는 이메일 초대 링크에서만 시작할 수 있습니다.`);await l(t.authHost,t.signal,t.styleNonce);let e=await n.redeemEmailInvitation({invitationId:t.invitationId,emailToken:t.emailToken,origin:t.location.origin,pathname:t.location.pathname,signal:t.signal});await J(t.signal,()=>r.getAuth())&&await J(t.signal,async()=>r.auth.signOut()),await J(t.signal,async()=>r.auth.signInWithToken(e));let i=await J(t.signal,()=>r.getAuth());if(!i)throw Error(`인증 세션을 확인할 수 없습니다.`);a=i}else a=await _e(r,t.signal);if(!i)try{await he(n.acceptInvitation,{refreshToken:a.refresh_token,invitationId:t.invitationId,suggestedDisplayName:ve(a.email),authHost:t.authHost,signal:t.signal,styleNonce:t.styleNonce})}catch(e){throw X(e,`INVITATION_EMAIL_MISMATCH`)?new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 다시 열어 주세요.`):e}return{db:r,user:a}}async function he(e,t){if(!e)return;let n={refreshToken:t.refreshToken,invitationId:t.invitationId,signal:t.signal};try{await e(n);return}catch(e){if(!X(e,`DISPLAY_NAME_REQUIRED`))throw e}let r=await c(t.authHost,t.signal,t.suggestedDisplayName,t.styleNonce);await e({...n,displayName:r})}function ge(e,t,n,r){let i=e.find(({id:e})=>e===r.invitationId);if(!i||be(i.email)!==be(r.email))return{ok:!1,code:`INVITATION_REQUIRED`};if(i.status===`revoked`)return{ok:!1,code:`INVITATION_REVOKED`};if(i.status===`expired`||i.expiresAt<=r.now)return{ok:!1,code:`INVITATION_EXPIRED`};if(i.status!==`accepted`)return{ok:!1,code:`INVITATION_NOT_ACCEPTED`};let a=t.find(e=>e.authUserId===r.authUserId&&e.projectId===i.projectId);if(!a)return{ok:!1,code:`MEMBERSHIP_REQUIRED`};let o=Q(r.location.pathname),s=q(n,i.projectId,r.location.origin,o);return s?{ok:!0,invitation:i,reviewer:a,page:s}:{ok:!1,code:`PAGE_NOT_FOUND`}}function q(e,t,n,r){let i=e.filter(e=>e.projectId===t&&Q(e.pathname)===r),a=i.filter(({stagingOrigin:e})=>e===n);return(a.length>0?a:i).sort((e,t)=>(e.createdAt??2**53-1)-(t.createdAt??2**53-1)||e.id.localeCompare(t.id))[0]}async function _e(e,t){let n=await J(t,()=>e.getAuth());if(n)return n;throw new s(`INVITATION_EMAIL_REQUIRED`,`초대받은 이메일의 링크를 열어 주세요.`)}async function J(e,t){Y(e);let n=await t();return Y(e),n}function Y(e){if(e.aborted)throw ye()}function ve(e){return e?.split(`@`)[0]?.trim().slice(0,120)??``}function X(e,t){return!!(e&&typeof e==`object`&&`code`in e&&e.code===t)}function Z(){return new DOMException(`Aborted`,`AbortError`)}function ye(){return new s(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function Q(e){return e.replace(/\/+$/,``)||`/`}function be(e){return e.trim().toLowerCase()}function $(e){switch(e){case`INVITATION_REQUIRED`:return`현재 계정과 일치하는 리뷰 초대를 찾을 수 없습니다.`;case`INVITATION_NOT_ACCEPTED`:return`아직 수락되지 않은 리뷰 초대입니다.`;case`INVITATION_REVOKED`:return`취소된 리뷰 초대입니다. 계속 검토하려면 담당자에게 다시 요청하세요.`;case`INVITATION_EXPIRED`:return`만료된 리뷰 초대입니다. 담당자에게 새 링크를 요청하세요.`;case`MEMBERSHIP_REQUIRED`:return`이 프로젝트의 reviewer membership이 없습니다.`;case`PAGE_NOT_FOUND`:return`이 페이지는 아직 리뷰 대상이 아닙니다. 담당자에게 문의하세요.`}}export{pe as createInstantReviewRuntime};
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./install-DaKugI9S.js";import{useEffect as t,useRef as n}from"react";function r({activationParam:r,build:i,collaboration:a,developerTools:o,enabled:s,navigateToFeedback:c,offset:l,onError:u,onInactive:d,onReady:f,pageIdentity:p,position:m,runtime:h,serviceUrl:g,sourceContextResolver:_,styleNonce:v}){let y=n(u),b=n(d),x=n(f),S=n(null),C=i?.buildId,w=i?.gitCommit,T=i?.generatedAt,E=i?.mode,D=o?.codeContextCopy===!0||o?.reactGrab===!0,O=a?.cursors===!0,k=a?.chat===!0;return y.current=u,b.current=d,x.current=f,t(()=>{if(s===!1)return;let t=e({activationParam:r,build:C===void 0&&w===void 0&&T===void 0&&E===void 0?void 0:{buildId:C,gitCommit:w,generatedAt:T,mode:E},collaboration:O||k?{cursors:O,chat:k}:void 0,developerTools:D?{codeContextCopy:!0}:void 0,enabled:s,navigateToFeedback:c,offset:l,onError:e=>y.current?.(e),onInactive:e=>{let t=`${e.reason}\n${e.origin}`;S.current!==t&&(S.current=t,b.current?.(e))},onReady:e=>{S.current=null,x.current?.(e)},pageIdentity:p,runtime:h,position:m,serviceUrl:g,sourceContextResolver:_,styleNonce:v});return()=>t.dispose()},[r,C,E,k,O,D,s,T,w,c,l?.x,l?.y,p,m,h,g,_,v]),null}r.displayName=`VisualReview`;export{r as VisualReview};
1
+ import{t as e}from"./install-Crgjt0uw.js";import{useEffect as t,useRef as n}from"react";function r({activationParam:r,build:i,collaboration:a,developerTools:o,enabled:s,navigateToFeedback:c,offset:l,onError:u,onInactive:d,onReady:f,pageIdentity:p,position:m,runtime:h,serviceUrl:g,sourceContextResolver:_,styleNonce:v}){let y=n(u),b=n(d),x=n(f),S=n(null),C=i?.buildId,w=i?.gitCommit,T=i?.generatedAt,E=i?.mode,D=o?.codeContextCopy===!0||o?.reactGrab===!0,O=a?.cursors===!0,k=a?.chat===!0;return y.current=u,b.current=d,x.current=f,t(()=>{if(s===!1)return;let t=e({activationParam:r,build:C===void 0&&w===void 0&&T===void 0&&E===void 0?void 0:{buildId:C,gitCommit:w,generatedAt:T,mode:E},collaboration:O||k?{cursors:O,chat:k}:void 0,developerTools:D?{codeContextCopy:!0}:void 0,enabled:s,navigateToFeedback:c,offset:l,onError:e=>y.current?.(e),onInactive:e=>{let t=`${e.reason}\n${e.origin}`;S.current!==t&&(S.current=t,b.current?.(e))},onReady:e=>{S.current=null,x.current?.(e)},pageIdentity:p,runtime:h,position:m,serviceUrl:g,sourceContextResolver:_,styleNonce:v});return()=>t.dispose()},[r,C,E,k,O,D,s,T,w,c,l?.x,l?.y,p,m,h,g,_,v]),null}r.displayName=`VisualReview`;export{r as VisualReview};
@@ -0,0 +1,13 @@
1
+ var e=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewBootstrapError`}};function t(e,t,n=``,r){if(t.aborted)return Promise.reject(u());let i=a(e);i.replaceChildren(o(i,r),s(i));let c=i.querySelector(`form`),l=c.elements.namedItem(`displayName`),f=i.querySelector(`button`),p=i.querySelector(`.error`);return l.value=n.trim().slice(0,120),l.focus(),l.select(),new Promise((e,n)=>{let r=!1,a=e=>{r||(r=!0,t.removeEventListener(`abort`,o),i.removeEventListener(`keydown`,h),c.removeEventListener(`submit`,m),e())},o=()=>a(()=>n(u())),s=()=>{let t=l.value.trim();if(!t){p.textContent=`이름을 입력해 주세요.`,l.focus();return}if(t.length>120){p.textContent=`이름은 120자 이하여야 합니다.`,l.focus();return}p.textContent=``,f.disabled=!0,c.setAttribute(`aria-busy`,`true`),a(()=>e(t))},m=e=>{e.preventDefault(),s()},h=e=>{let t=e;if(t.key===`Escape`){t.preventDefault(),o();return}if(t.key===`Tab`){d(i,t);return}t.key===`Enter`&&!t.isComposing&&t.target===l&&(t.preventDefault(),s())};t.addEventListener(`abort`,o,{once:!0}),i.addEventListener(`keydown`,h),c.addEventListener(`submit`,m)})}function n(e,t,n){if(t.aborted)return Promise.reject(u());let r=a(e);r.replaceChildren(o(r,n),c(r));let i=r.querySelector(`form`),s=r.querySelector(`button`);return s.focus(),new Promise((e,n)=>{let a=!1,o=e=>{a||(a=!0,t.removeEventListener(`abort`,c),r.removeEventListener(`keydown`,f),i.removeEventListener(`submit`,l),e())},c=()=>o(()=>n(u())),l=t=>{t.preventDefault(),s.disabled=!0,i.setAttribute(`aria-busy`,`true`),o(e)},f=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),c()):t.key===`Tab`&&d(r,t)};t.addEventListener(`abort`,c,{once:!0}),r.addEventListener(`keydown`,f),i.addEventListener(`submit`,l)})}function r(e,t,n,r){if(t.aborted)return Promise.reject(u());let i=a(e),s=o(i,r);return new Promise((e,r)=>{let a=!1,o=null,c=null,f=()=>{o&&c&&o.removeEventListener(`submit`,c),o=null,c=null},p=e=>{a||(a=!0,t.removeEventListener(`abort`,m),i.removeEventListener(`keydown`,h),f(),e())},m=()=>p(()=>r(u())),h=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),m()):t.key===`Tab`&&d(i,t)},g=(e,t)=>{f(),o=e,c=t,e.addEventListener(`submit`,t),i.replaceChildren(s,e)},_=(t,r)=>{let a=i.ownerDocument,o=a.createElement(`form`);o.setAttribute(`role`,`dialog`),o.setAttribute(`aria-modal`,`true`);let s=l(a,`p`,`01.works`,`eyebrow`),c=l(a,`h2`,`이메일 확인`),u=l(a,`p`,`${r.emailHint}로 보낸 6자리 코드를 입력하세요.`,`description`),d=l(a,`label`,`인증 코드`),f=a.createElement(`input`);f.name=`code`,f.inputMode=`numeric`,f.autocomplete=`one-time-code`,f.pattern=`[0-9]{6}`,f.maxLength=6,f.required=!0;let m=l(a,`p`,``,`error`);m.setAttribute(`role`,`alert`);let h=l(a,`button`,`리뷰 시작`);h.type=`submit`,d.append(f),o.append(s,c,u,d,m,h),g(o,i=>{i.preventDefault();let a=f.value.trim();if(!/^\d{6}$/u.test(a)){m.textContent=`6자리 숫자를 입력해 주세요.`;return}h.disabled=!0,o.setAttribute(`aria-busy`,`true`),m.textContent=``,n.verifyCode(r.challengeId,r.attemptId,t,a).then(t=>p(()=>e(t)),e=>{h.disabled=!1,o.removeAttribute(`aria-busy`),m.textContent=e instanceof Error?e.message:`인증 코드를 확인하지 못했습니다.`,f.select()})}),f.focus()},v=i.ownerDocument,y=v.createElement(`form`);y.setAttribute(`role`,`dialog`),y.setAttribute(`aria-modal`,`true`);let b=l(v,`p`,`01.works`,`eyebrow`),x=l(v,`h2`,`리뷰 로그인`),S=l(v,`p`,`초대받은 이메일로 인증 코드를 보내드립니다.`,`description`),C=l(v,`label`,`이메일`),w=v.createElement(`input`);w.name=`email`,w.type=`email`,w.autocomplete=`email`,w.required=!0;let T=l(v,`p`,``,`error`);T.setAttribute(`role`,`alert`);let E=l(v,`button`,`인증 코드 받기`);E.type=`submit`,C.append(w),y.append(b,x,S,C,T,E),g(y,e=>{e.preventDefault();let t=w.value.trim().toLowerCase();if(t.length>320||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(t)){T.textContent=`유효한 이메일을 입력해 주세요.`;return}E.disabled=!0,y.setAttribute(`aria-busy`,`true`),T.textContent=``,n.requestCode(t).then(e=>_(t,e),e=>{E.disabled=!1,y.removeAttribute(`aria-busy`),T.textContent=e instanceof Error?e.message:`인증 코드를 보내지 못했습니다.`})}),t.addEventListener(`abort`,m,{once:!0}),i.addEventListener(`keydown`,h),i.replaceChildren(s,y),w.focus()})}async function i(e,t,n,r){if(t.aborted)throw u();let i=await n.requestCode();if(t.aborted)throw u();let s=a(e),c=s.ownerDocument.createElement(`form`);c.setAttribute(`role`,`dialog`),c.setAttribute(`aria-modal`,`true`);let f=l(s.ownerDocument,`p`,`01.works`,`eyebrow`),p=l(s.ownerDocument,`h2`,`리뷰 접속 복구`),m=l(s.ownerDocument,`p`,`${i.emailHint}로 보낸 6자리 코드를 입력하세요.`,`description`),h=l(s.ownerDocument,`label`,`복구 코드`),g=s.ownerDocument.createElement(`input`);g.name=`code`,g.inputMode=`numeric`,g.autocomplete=`one-time-code`,g.pattern=`[0-9]{6}`,g.maxLength=6,g.required=!0;let _=l(s.ownerDocument,`p`,``,`error`);_.setAttribute(`role`,`alert`);let v=l(s.ownerDocument,`button`,`접속 복구`);return v.type=`submit`,h.append(g),c.append(f,p,m,h,_,v),s.replaceChildren(o(s,r),c),g.focus(),new Promise((e,r)=>{let a=!1,o=e=>{a||(a=!0,t.removeEventListener(`abort`,l),s.removeEventListener(`keydown`,f),c.removeEventListener(`submit`,p),e())},l=()=>o(()=>r(u())),f=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),l()):t.key===`Tab`&&d(s,t)},p=t=>{t.preventDefault();let r=g.value.trim();if(!/^\d{6}$/.test(r)){_.textContent=`6자리 숫자를 입력해 주세요.`;return}v.disabled=!0,c.setAttribute(`aria-busy`,`true`),_.textContent=``,n.verifyCode(i.challengeId,r).then(t=>o(()=>e(t)),e=>{v.disabled=!1,c.removeAttribute(`aria-busy`),_.textContent=e instanceof Error?e.message:`복구 코드를 확인하지 못했습니다.`,g.select()})};t.addEventListener(`abort`,l,{once:!0}),s.addEventListener(`keydown`,f),c.addEventListener(`submit`,p)})}function a(e){return e.shadowRoot??e.attachShadow({mode:`open`})}function o(e,t){let n=e.ownerDocument.createElement(`style`);return t&&(n.nonce=t),n.textContent=f,n}function s(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-name-title`),n.setAttribute(`aria-describedby`,`visual-review-name-description`);let r=l(t,`p`,`01.works`,`eyebrow`),i=l(t,`h2`,`표시 이름`);i.id=`visual-review-name-title`;let a=l(t,`p`,`피드백에 이 이름이 표시됩니다.`,`description`);a.id=`visual-review-name-description`;let o=l(t,`label`,`이름`);o.htmlFor=`visual-review-display-name`;let s=t.createElement(`input`);s.id=`visual-review-display-name`,s.name=`displayName`,s.autocomplete=`name`,s.maxLength=120,s.required=!0;let c=l(t,`p`,``,`error`);c.setAttribute(`role`,`alert`);let u=l(t,`button`,`리뷰 시작`);return u.type=`submit`,n.append(r,i,a,o,s,c,u),n}function c(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-invitation-title`),n.setAttribute(`aria-describedby`,`visual-review-invitation-description`);let r=l(t,`p`,`01.works`,`eyebrow`),i=l(t,`h2`,`리뷰 초대`);i.id=`visual-review-invitation-title`;let a=l(t,`p`,`초대받은 프로젝트를 열고 화면에 피드백을 남길 수 있습니다.`,`description`);a.id=`visual-review-invitation-description`;let o=l(t,`button`,`리뷰 시작`);return o.type=`submit`,n.append(r,i,a,o),n}function l(e,t,n,r){let i=e.createElement(t);return i.textContent=n,r&&(i.className=r),i}function u(){return new e(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function d(e,t){let n=Array.from(e.querySelectorAll(`input:not([disabled]),button:not([disabled])`)).filter(e=>!e.closest(`[hidden]`));if(n.length===0)return;let r=n[0],i=n.at(-1);t.shiftKey&&e.activeElement===r?(t.preventDefault(),i.focus()):!t.shiftKey&&e.activeElement===i&&(t.preventDefault(),r.focus())}const f=`
2
+ :host{all:initial;--vr-panel:#fff;--vr-text:#161616;--vr-muted:#707070;--vr-label:#3f3f3f;--vr-control:#fff;--vr-border:#d9d9d9;--vr-action:#161616;--vr-action-text:#fff;position:fixed;inset:0;z-index:2147483646;display:grid;place-items:center;overflow:auto;padding:8px;box-sizing:border-box;background:rgba(15,15,15,.34);font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:var(--vr-text)}
3
+ form{box-sizing:border-box;width:min(320px,calc(100vw - 16px));padding:20px;border:0;border-radius:14px;background:var(--vr-panel);box-shadow:0 18px 56px rgba(0,0,0,.18)}
4
+ .eyebrow{margin:0 0 10px;color:var(--vr-muted);font-size:10px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}
5
+ h2{margin:0 0 5px;font-size:16px;line-height:1.3;letter-spacing:-.01em}.description{margin:0 0 18px;color:var(--vr-muted);font-size:13px;line-height:1.45}
6
+ [hidden]{display:none!important}label{display:grid;gap:6px;margin-top:12px;color:var(--vr-label);font-size:12px;font-weight:600}input{box-sizing:border-box;width:100%;height:40px;margin-top:6px;padding:0 11px;border:1px solid var(--vr-border);border-radius:9px;background:var(--vr-control);color:var(--vr-text);font:400 13px/1 Inter,ui-sans-serif,system-ui,sans-serif}
7
+ label input{margin-top:0}input:focus{border-color:#999;outline:0;box-shadow:0 0 0 3px rgba(127,127,127,.16)}.error{min-height:17px;margin:8px 0 0;color:#d92d20;font-size:11px;line-height:1.45}
8
+ button{width:100%;height:40px;margin-top:7px;border:0;border-radius:9px;background:var(--vr-action);color:var(--vr-action-text);font:650 13px/1 Inter,ui-sans-serif,system-ui,sans-serif;cursor:pointer}button:focus-visible{outline:2px solid var(--vr-action);outline-offset:2px}button:disabled{opacity:.55;cursor:wait}
9
+ @media (prefers-color-scheme:dark){:host{--vr-panel:#161616;--vr-text:#fff;--vr-muted:#a7a7a7;--vr-label:#d6d6d6;--vr-control:#222;--vr-border:#3a3a3a;--vr-action:#fff;--vr-action-text:#171717}}
10
+ @media (pointer:coarse){input,button{height:44px}}
11
+ @media (max-width:480px){form{padding:18px}input{font-size:16px}}
12
+ @media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
13
+ `;export{e as a,i,t as n,r,n as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@01.works/visual-review",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Invitation-only visual feedback for staging websites, hosted by 01.works.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- import{r as e}from"./install-DaKugI9S.js";import{r as t,t as n}from"./reviewer-auth-presentation-DusgaOej.js";function r(e){let t=[`01works-reviewer-device-proof-v1`,e.purpose,e.deviceSessionId,e.challengeId,e.challenge,String(e.counter),String(e.expiresAt),e.invitationId,e.origin,e.pathname];return new TextEncoder().encode(t.map(e=>`${e.length}:${e}`).join(``))}const i=`reviewer-device-credentials-v1`;function a(){try{return globalThis.indexedDB?new c(globalThis.indexedDB):null}catch{return null}}async function o(e=globalThis.crypto){if(!e?.subtle||typeof e.randomUUID!=`function`)throw Error(`Visual Review device authentication requires Web Crypto`);let t=await e.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},!1,[`sign`,`verify`]);if(!(t instanceof Object)||!(`privateKey`in t)||!(`publicKey`in t)||t.privateKey.extractable)throw Error(`Visual Review could not create a non-extractable device key`);let n=await e.subtle.exportKey(`jwk`,t.publicKey);if(n.kty!==`EC`||n.crv!==`P-256`||typeof n.x!=`string`||typeof n.y!=`string`)throw Error(`Visual Review received an invalid device public key`);return{deviceSessionId:e.randomUUID(),privateKey:t.privateKey,publicKey:{kty:`EC`,crv:`P-256`,x:n.x,y:n.y}}}async function s(e,t,n=globalThis.crypto){if(e.extractable||e.type!==`private`||e.algorithm.name!==`ECDSA`||!e.usages.includes(`sign`))throw Error(`Visual Review device key is invalid`);let i=await n.subtle.sign({name:`ECDSA`,hash:`SHA-256`},e,r(t));return f(new Uint8Array(i))}var c=class{factory;constructor(e){this.factory=e}async get(e){let t=await this.open();try{return l(await u(t.transaction(i,`readonly`).objectStore(i).get(e)))}finally{t.close()}}async set(e,t){let n=await this.open();try{await d(n.transaction(i,`readwrite`),n=>{n.put(t,e)})}finally{n.close()}}async delete(e){let t=await this.open();try{await d(t.transaction(i,`readwrite`),t=>{t.delete(e)})}finally{t.close()}}open(){return new Promise((e,t)=>{let n=this.factory.open(`01works-visual-review`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(i)||n.result.createObjectStore(i)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error??Error(`IndexedDB open failed`)),n.onblocked=()=>t(Error(`IndexedDB upgrade blocked`))})}};function l(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;let t=e;return typeof t.deviceSessionId!=`string`||!Number.isSafeInteger(t.expiresAt)||(t.expiresAt??0)<=Date.now()||!t.privateKey||t.privateKey.type!==`private`||t.privateKey.extractable||t.privateKey.algorithm.name!==`ECDSA`||!t.privateKey.usages.includes(`sign`)?null:t}function u(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??Error(`IndexedDB request failed`))})}function d(e,t){return new Promise((n,r)=>{e.oncomplete=()=>n(),e.onerror=()=>r(e.error??Error(`IndexedDB transaction failed`)),e.onabort=()=>r(e.error??Error(`IndexedDB transaction aborted`)),t(e.objectStore(i))})}function f(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replace(/=+$/u,``)}var p=class extends Error{code;status;constructor(e,t,n){super(t),this.code=e,this.status=n,this.name=`HostedBootstrapError`}};function m(e={}){return h(e)}function h(t={},n={}){let r=e(t.serviceUrl??`https://review.01.works`),i=t.fetch??globalThis.fetch,o=n.now??Date.now,s=new Map,c=n.reviewerDeviceStore===void 0?a():n.reviewerDeviceStore,l=n.crypto??globalThis.crypto,u=n.reviewerSessionStorage===void 0?P(F(),I()):P(n.reviewerSessionStorage);if(typeof i!=`function`)throw Error(`Visual Review bootstrap requires the Fetch API`);return{async resolve(e){let t=D(e),a=await A(r,i,t,e.signal),d=o(),f={serviceUrl:r,...n.instantAuthDatabaseFactory===void 0?{}:{databaseFactory:n.instantAuthDatabaseFactory},redeemEmailInvitation:async e=>(await S(r,i,e)).refreshToken,acceptInvitation:async e=>{let t=await i(`${r}/v1/invitations/accept`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${e.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:e.invitationId,...e.displayName===void 0?{}:{displayName:e.displayName}}),cache:`no-store`,credentials:`omit`,signal:e.signal}),n=await H(t);if(!t.ok)throw new p(U(n,`error`)??`INVITATION_ACCEPT_FAILED`,U(n,`message`)??`리뷰 초대 수락에 실패했습니다.`,t.status)},resolveReviewPage:e=>k(r,i,e),...e.collaboration?.cursors===!0||e.collaboration?.chat===!0?{resolveCollaborationRoom:e=>O(r,i,e)}:{}},m=a.apiVersion===2?await g({config:a,convexRuntimeFactory:n.convexRuntimeFactory??(await import(`./convex-runtime-8kViWXIL.js`)).createConvexReviewRuntime,fetcher:i,memorySessions:s,reviewerDeviceStore:c,cryptoProvider:l,request:e,legacySessionStorages:u,serviceUrl:r}):{...await(n.instantRuntimeFactory??(await import(`./instant-runtime-CFL7Ffms.js`)).createInstantReviewRuntime)(a.instantAppId,f).resolve(e),sessionScope:`browser`,sessionExpiresAt:a.invitationExpiresAt},h=m.resolvePage;if(!h)return m;let _=async t=>{await A(r,i,{invitationId:e.invitationId,origin:t.location.origin,pathname:t.location.pathname},t.signal),d=o()};return{...m,resolvePage:async e=>(o()-d>=3e5&&await _(e),h(e))}}}}async function g(e){let t=N(e.serviceUrl,e.request.location.origin,e.request.invitationId);B(e.legacySessionStorages,t);let r;if(e.request.emailToken){await n(e.request.authHost,e.request.signal,e.request.styleNonce);let t=await o(e.cryptoProvider);try{r=await x(e.serviceUrl,e.fetcher,{invitationId:e.request.invitationId,emailToken:e.request.emailToken,origin:e.request.location.origin,pathname:e.request.location.pathname,signal:e.request.signal,device:t})}catch(n){if(!(n instanceof p)||n.code!==`INVITATION_TOKEN_USED`)throw n;r=await _(e,t)}}else r=await L(e.reviewerDeviceStore,e.memorySessions,t);r||=await _(e,await o(e.cryptoProvider)),await R(e.reviewerDeviceStore,e.memorySessions,t,r);let{expiresAt:i}=r;return{...await e.convexRuntimeFactory(e.config.convexUrl,{fetchAccessToken:n=>v(e.serviceUrl,e.fetcher,r,n,e.cryptoProvider,()=>z(e.reviewerDeviceStore,e.memorySessions,t))}).resolve(e.request),sessionScope:`browser`,sessionExpiresAt:i??e.config.invitationExpiresAt,clearLocalSession:()=>{z(e.reviewerDeviceStore,e.memorySessions,t);let n=new AbortController,i=setTimeout(()=>n.abort(),5e3);b(e.serviceUrl,e.fetcher,r,{invitationId:e.request.invitationId,origin:e.request.location.origin,pathname:e.request.location.pathname,forceRefreshToken:!1,signal:n.signal},e.cryptoProvider).catch(()=>void 0).finally(()=>clearTimeout(i))}}}async function _(e,n){let r={invitationId:e.request.invitationId,pathname:e.request.location.pathname,signal:e.request.signal};return t(e.request.authHost,e.request.signal,{requestCode:()=>C(e.serviceUrl,e.fetcher,r),verifyCode:(t,i)=>w(e.serviceUrl,e.fetcher,{...r,challengeId:t,code:i,device:n})},e.request.styleNonce)}async function v(e,t,n,r,i,a){let o;try{o=await y(e,t,n.deviceSessionId,r)}catch(e){throw e instanceof p&&[401,403,409,410].includes(e.status)&&await a(),e}let c=await s(n.privateKey,{purpose:`token`,deviceSessionId:n.deviceSessionId,...o,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname},i),l=await t(`${e}/v1/convex/token`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,deviceSessionId:n.deviceSessionId,proof:{deviceSessionId:n.deviceSessionId,...o,signature:c}}),cache:`no-store`,credentials:`omit`,signal:r.signal}),u=await H(l),d=U(u,`token`);if(!l.ok||!d||d.length>16e3)throw[401,403,409,410].includes(l.status)&&await a(),new p(U(u,`error`)??`CONVEX_AUTH_FAILED`,U(u,`message`)??`리뷰 인증을 갱신하지 못했습니다.`,l.status);return d}async function y(e,t,n,r,i=`token`){let a=await t(`${e}/v1/convex/token/challenge`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({deviceSessionId:n,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,purpose:i}),cache:`no-store`,credentials:`omit`,signal:r.signal}),o=await H(a),s=U(o,`challengeId`),c=U(o,`challenge`),l=W(o,`counter`),u=M(W(o,`expiresAt`));if(!a.ok||!s||!c||!Number.isSafeInteger(l)||l<1||u==null)throw new p(U(o,`error`)??`DEVICE_CHALLENGE_FAILED`,U(o,`message`)??`리뷰 기기 인증을 시작하지 못했습니다.`,a.status);return{challengeId:s,challenge:c,counter:l,expiresAt:u}}async function b(e,t,n,r,i){let a=await y(e,t,n.deviceSessionId,r,`revoke`),o=await s(n.privateKey,{purpose:`revoke`,deviceSessionId:n.deviceSessionId,...a,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname},i);if(!(await t(`${e}/v1/convex/session/revoke`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({deviceSessionId:n.deviceSessionId,invitationId:r.invitationId,origin:r.origin,pathname:r.pathname,proof:{deviceSessionId:n.deviceSessionId,...a,signature:o}}),cache:`no-store`,credentials:`omit`,keepalive:!0,signal:r.signal})).ok)throw Error(`Reviewer device revocation failed`)}async function x(e,t,n){let r=await t(`${e}/v1/invitations/redeem-email`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,emailToken:n.emailToken,pathname:n.pathname,deviceSessionId:n.device.deviceSessionId,devicePublicKey:n.device.publicKey}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await H(r);if(!r.ok)throw new p(U(i,`error`)??`INVITATION_REDEEM_FAILED`,U(i,`message`)??`리뷰 초대를 시작하지 못했습니다.`,r.status);let a=U(i,`deviceSessionId`);if(!a||a!==n.device.deviceSessionId)throw new p(`INVALID_INVITATION_REDEEM_RESPONSE`,`잘못된 초대 인증 응답입니다.`,502);let o=M(W(i,`expiresAt`));if(o===null)throw new p(`INVALID_INVITATION_REDEEM_RESPONSE`,`잘못된 초대 인증 응답입니다.`,502);if(o===void 0)throw new p(`INVALID_INVITATION_REDEEM_RESPONSE`,`device session 만료 시각이 필요합니다.`,502);return{deviceSessionId:a,privateKey:n.device.privateKey,expiresAt:o}}async function S(e,t,n){let r=await t(`${e}/v1/invitations/redeem-email`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,emailToken:n.emailToken,pathname:n.pathname}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await H(r),a=U(i,`refreshToken`);if(!r.ok||!a||a.length>4096)throw new p(U(i,`error`)??`INVITATION_REDEEM_FAILED`,U(i,`message`)??`리뷰 초대를 시작하지 못했습니다.`,r.status);return{refreshToken:a}}async function C(e,t,n){let r=await T(e,t,`start`,n),i=U(r,`challengeId`),a=U(r,`emailHint`);if(!i||!a)throw new p(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);return{challengeId:i,emailHint:a}}async function w(e,t,n){let r=await T(e,t,`verify`,n),i=U(r,`deviceSessionId`);if(!i||i!==n.device.deviceSessionId)throw new p(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);let a=M(W(r,`expiresAt`));if(a===null)throw new p(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);if(a===void 0)throw new p(`INVALID_RECOVERY_RESPONSE`,`device session 만료 시각이 필요합니다.`,502);return{deviceSessionId:i,privateKey:n.device.privateKey,expiresAt:a}}async function T(e,t,n,r){let{signal:i,...a}=r,o=a;if(`device`in a&&E(a.device)){let{device:e,...t}=a;o={...t,deviceSessionId:e.deviceSessionId,devicePublicKey:e.publicKey}}let s=await t(`${e}/v1/invitations/recovery/${n}`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify(o),cache:`no-store`,credentials:`omit`,signal:i}),c=await H(s);if(!s.ok)throw new p(U(c,`error`)??`RECOVERY_FAILED`,U(c,`message`)??`리뷰 접속을 복구하지 못했습니다.`,s.status);if(!c||typeof c!=`object`||Array.isArray(c))throw new p(`INVALID_RECOVERY_RESPONSE`,`잘못된 복구 응답입니다.`,502);return c}function E(e){return!!(e&&typeof e==`object`&&`deviceSessionId`in e&&`privateKey`in e&&`publicKey`in e)}function D(e){return{invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname}}async function O(e,t,n){let r=await t(`${e}/v1/collaboration/room`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${n.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,pageId:n.pageId,scopeId:n.scopeId}),cache:`no-store`,credentials:`omit`,signal:n.signal}),i=await H(r);if(!r.ok)throw new p(U(i,`error`)??`COLLABORATION_ROOM_FAILED`,U(i,`message`)??`실시간 협업 연결에 실패했습니다.`,r.status);if(!i||typeof i!=`object`||Array.isArray(i))throw new p(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);let a=i,o=U(a,`id`);if(!o||!/^vr_[A-Za-z0-9_-]{43}$/u.test(o)||!Number.isSafeInteger(a.expiresAt))throw new p(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);return{id:o,expiresAt:a.expiresAt}}async function k(e,t,n){let r=await t(`${e}/v1/pages/resolve`,{method:`POST`,headers:{accept:`application/json`,authorization:`Bearer ${n.refreshToken}`,"content-type":`application/json`},body:JSON.stringify({invitationId:n.invitationId,origin:n.origin,pathname:n.pathname}),cache:`no-store`,credentials:`omit`,signal:n.signal});if(r.ok)return;let i=await H(r);throw new p(U(i,`error`)??`PAGE_RESOLVE_FAILED`,U(i,`message`)??`리뷰 페이지를 준비하지 못했습니다.`,r.status)}async function A(e,t,n,r){let i=await t(`${e}/v1/bootstrap`,{method:`POST`,headers:{accept:`application/json`,"content-type":`application/json`},body:JSON.stringify(n),cache:`no-store`,credentials:`omit`,signal:r}),a=await H(i);if(!i.ok)throw new p(U(a,`error`)??`BOOTSTRAP_FAILED`,U(a,`message`)??`Visual Review bootstrap에 실패했습니다.`,i.status);return j(a)}function j(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new p(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);let t=e;if(typeof t.apiVersion==`number`&&t.apiVersion!==1&&t.apiVersion!==2)throw new p(`UNSUPPORTED_SERVICE_VERSION`,`이 Visual Review 위젯 버전은 더 이상 지원되지 않습니다. 사이트의 @01.works/visual-review를 업데이트해 주세요.`,502);if(t.apiVersion===2){let e=V(t.convexUrl),n=M(t.invitationExpiresAt);if(t.provider!==`convex`||!e||n===null)throw new p(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{apiVersion:2,provider:`convex`,convexUrl:e,...n===void 0?{}:{invitationExpiresAt:n}}}let n=U(t,`instantAppId`),r=M(t.invitationExpiresAt);if(t.apiVersion!==1||!n||!G(n)||r===null)throw new p(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{apiVersion:1,instantAppId:n,...r===void 0?{}:{invitationExpiresAt:r}}}function M(e){if(e!==void 0)return Number.isSafeInteger(e)&&e>0?e:null}function N(e,t,n){return`01works.visual-review.convex-session.v1:${JSON.stringify([e,t,n])}`}function P(...e){return e.filter(e=>e!==null)}function F(){try{return globalThis.localStorage??null}catch{return null}}function I(){try{return globalThis.sessionStorage??null}catch{return null}}async function L(e,t,n){try{let t=await e?.get(n);if(t)return t}catch{}let r=t.get(n)??null;return r&&r.expiresAt<=Date.now()?(t.delete(n),null):r}async function R(e,t,n,r){t.set(n,r);try{await e?.set(n,r)}catch{}}async function z(e,t,n){t.delete(n);try{await e?.delete(n)}catch{}}function B(e,t){for(let n of e)try{n.removeItem(t)}catch{}}function V(e){if(typeof e!=`string`||!e.trim())return null;try{let t=new URL(e.trim());return t.origin!==e.trim()||t.protocol!==`https:`||!t.hostname.endsWith(`.convex.cloud`)?null:t.origin}catch{return null}}async function H(e){try{return await e.json()}catch{throw new p(`INVALID_BOOTSTRAP_RESPONSE`,`JSON bootstrap 응답이 필요합니다.`,502)}}function U(e,t){let n=W(e,t);return typeof n==`string`&&n.trim()?n.trim():null}function W(e,t){if(!(!e||typeof e!=`object`||Array.isArray(e)))return e[t]}function G(e){return/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}export{m as createHostedReviewRuntime};
@@ -1,13 +0,0 @@
1
- var e=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewBootstrapError`}};function t(e,t,n=``,r){if(t.aborted)return Promise.reject(l());let s=i(e);s.replaceChildren(a(s,r),o(s));let c=s.querySelector(`form`),d=c.elements.namedItem(`displayName`),f=s.querySelector(`button`),p=s.querySelector(`.error`);return d.value=n.trim().slice(0,120),d.focus(),d.select(),new Promise((e,n)=>{let r=!1,i=e=>{r||(r=!0,t.removeEventListener(`abort`,a),s.removeEventListener(`keydown`,h),c.removeEventListener(`submit`,m),e())},a=()=>i(()=>n(l())),o=()=>{let t=d.value.trim();if(!t){p.textContent=`이름을 입력해 주세요.`,d.focus();return}if(t.length>120){p.textContent=`이름은 120자 이하여야 합니다.`,d.focus();return}p.textContent=``,f.disabled=!0,c.setAttribute(`aria-busy`,`true`),i(()=>e(t))},m=e=>{e.preventDefault(),o()},h=e=>{let t=e;if(t.key===`Escape`){t.preventDefault(),a();return}if(t.key===`Tab`){u(s,t);return}t.key===`Enter`&&!t.isComposing&&t.target===d&&(t.preventDefault(),o())};t.addEventListener(`abort`,a,{once:!0}),s.addEventListener(`keydown`,h),c.addEventListener(`submit`,m)})}function n(e,t,n){if(t.aborted)return Promise.reject(l());let r=i(e);r.replaceChildren(a(r,n),s(r));let o=r.querySelector(`form`),c=r.querySelector(`button`);return c.focus(),new Promise((e,n)=>{let i=!1,a=e=>{i||(i=!0,t.removeEventListener(`abort`,s),r.removeEventListener(`keydown`,f),o.removeEventListener(`submit`,d),e())},s=()=>a(()=>n(l())),d=t=>{t.preventDefault(),c.disabled=!0,o.setAttribute(`aria-busy`,`true`),a(e)},f=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),s()):t.key===`Tab`&&u(r,t)};t.addEventListener(`abort`,s,{once:!0}),r.addEventListener(`keydown`,f),o.addEventListener(`submit`,d)})}async function r(e,t,n,r){if(t.aborted)throw l();let o=await n.requestCode();if(t.aborted)throw l();let s=i(e),d=s.ownerDocument.createElement(`form`);d.setAttribute(`role`,`dialog`),d.setAttribute(`aria-modal`,`true`);let f=c(s.ownerDocument,`p`,`01.works`,`eyebrow`),p=c(s.ownerDocument,`h2`,`리뷰 접속 복구`),m=c(s.ownerDocument,`p`,`${o.emailHint}로 보낸 6자리 코드를 입력하세요.`,`description`),h=c(s.ownerDocument,`label`,`복구 코드`),g=s.ownerDocument.createElement(`input`);g.name=`code`,g.inputMode=`numeric`,g.autocomplete=`one-time-code`,g.pattern=`[0-9]{6}`,g.maxLength=6,g.required=!0;let _=c(s.ownerDocument,`p`,``,`error`);_.setAttribute(`role`,`alert`);let v=c(s.ownerDocument,`button`,`접속 복구`);return v.type=`submit`,h.append(g),d.append(f,p,m,h,_,v),s.replaceChildren(a(s,r),d),g.focus(),new Promise((e,r)=>{let i=!1,a=e=>{i||(i=!0,t.removeEventListener(`abort`,c),s.removeEventListener(`keydown`,f),d.removeEventListener(`submit`,p),e())},c=()=>a(()=>r(l())),f=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),c()):t.key===`Tab`&&u(s,t)},p=t=>{t.preventDefault();let r=g.value.trim();if(!/^\d{6}$/.test(r)){_.textContent=`6자리 숫자를 입력해 주세요.`;return}v.disabled=!0,d.setAttribute(`aria-busy`,`true`),_.textContent=``,n.verifyCode(o.challengeId,r).then(t=>a(()=>e(t)),e=>{v.disabled=!1,d.removeAttribute(`aria-busy`),_.textContent=e instanceof Error?e.message:`복구 코드를 확인하지 못했습니다.`,g.select()})};t.addEventListener(`abort`,c,{once:!0}),s.addEventListener(`keydown`,f),d.addEventListener(`submit`,p)})}function i(e){return e.shadowRoot??e.attachShadow({mode:`open`})}function a(e,t){let n=e.ownerDocument.createElement(`style`);return t&&(n.nonce=t),n.textContent=d,n}function o(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-name-title`),n.setAttribute(`aria-describedby`,`visual-review-name-description`);let r=c(t,`p`,`01.works`,`eyebrow`),i=c(t,`h2`,`표시 이름`);i.id=`visual-review-name-title`;let a=c(t,`p`,`피드백에 이 이름이 표시됩니다.`,`description`);a.id=`visual-review-name-description`;let o=c(t,`label`,`이름`);o.htmlFor=`visual-review-display-name`;let s=t.createElement(`input`);s.id=`visual-review-display-name`,s.name=`displayName`,s.autocomplete=`name`,s.maxLength=120,s.required=!0;let l=c(t,`p`,``,`error`);l.setAttribute(`role`,`alert`);let u=c(t,`button`,`리뷰 시작`);return u.type=`submit`,n.append(r,i,a,o,s,l,u),n}function s(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-invitation-title`),n.setAttribute(`aria-describedby`,`visual-review-invitation-description`);let r=c(t,`p`,`01.works`,`eyebrow`),i=c(t,`h2`,`리뷰 초대`);i.id=`visual-review-invitation-title`;let a=c(t,`p`,`초대받은 프로젝트를 열고 화면에 피드백을 남길 수 있습니다.`,`description`);a.id=`visual-review-invitation-description`;let o=c(t,`button`,`리뷰 시작`);return o.type=`submit`,n.append(r,i,a,o),n}function c(e,t,n,r){let i=e.createElement(t);return i.textContent=n,r&&(i.className=r),i}function l(){return new e(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function u(e,t){let n=Array.from(e.querySelectorAll(`input:not([disabled]),button:not([disabled])`)).filter(e=>!e.closest(`[hidden]`));if(n.length===0)return;let r=n[0],i=n.at(-1);t.shiftKey&&e.activeElement===r?(t.preventDefault(),i.focus()):!t.shiftKey&&e.activeElement===i&&(t.preventDefault(),r.focus())}const d=`
2
- :host{all:initial;--vr-panel:#fff;--vr-text:#161616;--vr-muted:#707070;--vr-label:#3f3f3f;--vr-control:#fff;--vr-border:#d9d9d9;--vr-action:#161616;--vr-action-text:#fff;position:fixed;inset:0;z-index:2147483646;display:grid;place-items:center;overflow:auto;padding:8px;box-sizing:border-box;background:rgba(15,15,15,.34);font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:var(--vr-text)}
3
- form{box-sizing:border-box;width:min(320px,calc(100vw - 16px));padding:20px;border:0;border-radius:14px;background:var(--vr-panel);box-shadow:0 18px 56px rgba(0,0,0,.18)}
4
- .eyebrow{margin:0 0 10px;color:var(--vr-muted);font-size:10px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}
5
- h2{margin:0 0 5px;font-size:16px;line-height:1.3;letter-spacing:-.01em}.description{margin:0 0 18px;color:var(--vr-muted);font-size:13px;line-height:1.45}
6
- [hidden]{display:none!important}label{display:grid;gap:6px;margin-top:12px;color:var(--vr-label);font-size:12px;font-weight:600}input{box-sizing:border-box;width:100%;height:40px;margin-top:6px;padding:0 11px;border:1px solid var(--vr-border);border-radius:9px;background:var(--vr-control);color:var(--vr-text);font:400 13px/1 Inter,ui-sans-serif,system-ui,sans-serif}
7
- label input{margin-top:0}input:focus{border-color:#999;outline:0;box-shadow:0 0 0 3px rgba(127,127,127,.16)}.error{min-height:17px;margin:8px 0 0;color:#d92d20;font-size:11px;line-height:1.45}
8
- button{width:100%;height:40px;margin-top:7px;border:0;border-radius:9px;background:var(--vr-action);color:var(--vr-action-text);font:650 13px/1 Inter,ui-sans-serif,system-ui,sans-serif;cursor:pointer}button:focus-visible{outline:2px solid var(--vr-action);outline-offset:2px}button:disabled{opacity:.55;cursor:wait}
9
- @media (prefers-color-scheme:dark){:host{--vr-panel:#161616;--vr-text:#fff;--vr-muted:#a7a7a7;--vr-label:#d6d6d6;--vr-control:#222;--vr-border:#3a3a3a;--vr-action:#fff;--vr-action-text:#171717}}
10
- @media (pointer:coarse){input,button{height:44px}}
11
- @media (max-width:480px){form{padding:18px}input{font-size:16px}}
12
- @media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
13
- `;export{e as i,t as n,r,n as t};