@01.works/visual-review 0.7.0 → 0.7.1
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.
- package/README.md +12 -12
- package/dist/hosted-runtime-omn7O2tq.js +13 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +1 -1
- package/dist/{install-C3gBk4f6.js → install-CIPBUJ9O.js} +1 -1
- package/dist/next-config.js +3 -4
- package/dist/react.js +1 -1
- package/dist/src-CKG_8hE0.js +986 -0
- package/package.json +2 -1
- package/dist/hosted-runtime-T7DtCpZJ.js +0 -13
- package/dist/src-DIG2uBK3.js +0 -928
package/README.md
CHANGED
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
01.works가 호스팅하는 초대 전용 staging visual feedback widget입니다. 설치한
|
|
4
4
|
project는 InstantDB App ID나 admin credential을 설정하지 않습니다.
|
|
5
5
|
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
> `visual-review.01.works`도 DNS/TLS 검증 전입니다. 아래 설치 예시는 최초 release
|
|
9
|
-
> 후의 public API입니다.
|
|
6
|
+
> 배포 상태와 출시 체크리스트는 저장소의 `ROADMAP.md`를 기준으로 확인합니다.
|
|
7
|
+
> 이 문서는 현재 widget 계약과 integration API를 설명합니다.
|
|
10
8
|
|
|
11
9
|
Package metadata의 consumer engine 선언은 Node `>=18.18`입니다. Release CI는
|
|
12
10
|
Node `24.18.0`에서 실행합니다.
|
|
@@ -183,22 +181,24 @@ import와 network bootstrap을 시작하지 않습니다. production에는 flag
|
|
|
183
181
|
말고 Preview/Staging environment에만 설정합니다.
|
|
184
182
|
|
|
185
183
|
처음에는 `?visual-review=<invitation-id>` 초대 링크로 접근합니다. hosted service가
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
project의 단일 `allowedOrigin`과 현재 pathname을 activation gate로 확인합니다.
|
|
185
|
+
허용된 origin에서 아직 등록되지 않은 첫 pathname은 bootstrap 시 page로 자동
|
|
186
|
+
등록됩니다. bootstrap 응답은 public Instant App ID만 반환하며 reviewer session이나
|
|
187
|
+
이메일을 포함하지 않습니다. 이후 magic-code login을 완료한 browser가 accept
|
|
188
|
+
endpoint에 refresh token을 보냅니다. Server는
|
|
188
189
|
`db.asUser({ token })`으로 user-scoped claim을 실행하며 완료된 뒤에만 client가
|
|
189
190
|
정확한 page를 선택해 Instant subscription과 overlay를 mount합니다. `revokedAt`,
|
|
190
191
|
expiry와 claim 시 membership/project mismatch는 fail closed합니다.
|
|
191
192
|
|
|
192
|
-
public `enabled` flag는 load gate이고
|
|
193
|
+
public `enabled` flag는 load gate이고 allowed origin/current pathname은 hosted
|
|
193
194
|
activation/page-selection gate입니다. 지속 InstantDB data capability는 active,
|
|
194
195
|
accepted, unexpired, non-tombstoned invitation이 선택한 project 범위입니다.
|
|
195
196
|
reviewer membership은 claim 검증과 comment/reply author identity에 사용되며,
|
|
196
197
|
접근 철회는 invitation revoke로 수행합니다. admin token은 browser로 전달되지
|
|
197
198
|
않습니다.
|
|
198
199
|
|
|
199
|
-
정식 default service는 `https://visual-review.01.works`입니다.
|
|
200
|
-
|
|
201
|
-
`DASHBOARD_ORIGIN`과 달라 실제 owner workflow의 대체 URL이 아닙니다.
|
|
200
|
+
정식 default service는 `https://visual-review.01.works`입니다. local/self-hosted
|
|
201
|
+
integration에서는 명시적으로 service URL을 바꿀 수 있습니다.
|
|
202
202
|
|
|
203
203
|
```tsx
|
|
204
204
|
<VisualReview
|
|
@@ -207,8 +207,8 @@ reviewer membership은 claim 검증과 comment/reply author identity에 사용
|
|
|
207
207
|
/>
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
`serviceUrl`은 local/self-hosted integration test용입니다.
|
|
211
|
-
|
|
210
|
+
`serviceUrl`은 local/self-hosted integration test용입니다. hosted consumer는 이
|
|
211
|
+
override를 제거합니다.
|
|
212
212
|
|
|
213
213
|
strict CSP host는 response의 style nonce를 `styleNonce`로 전달할 수 있습니다.
|
|
214
214
|
생략하면 document의 nonce가 있는 script/style에서 감지합니다. nonce는 Visual
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{r as e}from"./install-CIPBUJ9O.js";import{t}from"./embedded-source-DNWVCQpa.js";import{i as n,init as r}from"@instantdb/core";const i=n.schema({entities:{$users:n.entity({email:n.string().unique().indexed()}),projects:n.entity({name:n.string(),slug:n.string().unique().indexed(),allowedOrigin:n.string().optional(),originDisconnectedAt:n.number().optional(),createdAt:n.number().indexed(),updatedAt:n.number()}),reviewPages:n.entity({projectId:n.string().indexed(),autoRegistrationSlot:n.string().unique().indexed().optional(),name:n.string(),pathname:n.string(),stagingOrigin:n.string(),createdAt:n.number().indexed(),updatedAt:n.number()}),reviewPageBuilds:n.entity({projectId:n.string().indexed(),pageId:n.string().unique().indexed(),buildId:n.string(),gitCommit:n.string().optional(),generatedAt:n.number().optional(),mode:n.string(),updatedAt:n.number().indexed()}),reviewers:n.entity({authUserId:n.string().indexed(),projectId:n.string().indexed(),email:n.string().indexed(),displayName:n.string(),role:n.string(),createdAt:n.number()}),reviewerIdentityClaims:n.entity({reviewerId:n.string().unique().indexed(),projectId:n.string().indexed(),authUserId:n.string().indexed(),email:n.string(),displayName:n.string(),role:n.string(),createdAt:n.number()}),invitations:n.entity({projectId:n.string().indexed(),reviewerId:n.string().indexed().optional(),email:n.string().indexed(),status:n.string().indexed(),claimRevision:n.number(),claimedByAuthUserId:n.string().indexed().optional(),expiresAt:n.number().indexed(),createdAt:n.number(),acceptedAt:n.number().optional(),revokedAt:n.number().optional()}),comments:n.entity({pageId:n.string().indexed(),authorId:n.string().indexed(),authorAuthUserId:n.string().indexed(),authorName:n.string(),body:n.string(),bodyContentMarker:n.string().optional(),anchorSelector:n.string().optional(),anchorOffsetX:n.number().optional(),anchorOffsetY:n.number().optional(),anchorViewportX:n.number().optional(),anchorViewportY:n.number().optional(),targetKind:n.string().optional(),targetPageX:n.number().optional(),targetPageY:n.number().optional(),targetWidth:n.number().optional(),targetHeight:n.number().optional(),targetViewportX:n.number().optional(),targetViewportY:n.number().optional(),targetViewportWidth:n.number().optional(),targetViewportHeight:n.number().optional(),targetPageOrigin:n.string().optional(),targetPagePathname:n.string().optional(),targetPageSearch:n.string().optional(),targetPageHash:n.string().optional(),targetPageTitle:n.string().optional(),targetCapturedAt:n.number().optional(),status:n.string().indexed(),workflowRevision:n.number(),createdAt:n.number().indexed(),updatedAt:n.number().indexed(),resolvedAt:n.number().optional(),resolvedById:n.string().optional()}),commentWorkflowClaims:n.entity({claimKey:n.string().unique().indexed(),commentId:n.string().indexed(),expectedWorkflowRevision:n.number(),createdAt:n.number().indexed()}),commentTargetElements:n.entity({commentId:n.string().indexed(),slotKey:n.string().unique().indexed().optional(),ordinal:n.number(),selector:n.string(),tagName:n.string(),textPreview:n.string(),pageX:n.number(),pageY:n.number(),width:n.number(),height:n.number(),viewportX:n.number(),viewportY:n.number(),viewportWidth:n.number(),viewportHeight:n.number(),authorAuthUserId:n.string().indexed()}),commentScopeEntries:n.entity({commentId:n.string().indexed(),slotKey:n.string().unique().indexed().optional(),ordinal:n.number(),key:n.string(),valueType:n.string(),stringValue:n.string().optional(),numberValue:n.number().optional(),booleanValue:n.boolean().optional(),authorAuthUserId:n.string().indexed()}),commentSourceContexts:n.entity({commentId:n.string().unique().indexed(),pageId:n.string().indexed(),authorAuthUserId:n.string().indexed(),contextJson:n.string(),createdAt:n.number().indexed()}),replies:n.entity({pageId:n.string().indexed(),commentId:n.string().indexed(),authorId:n.string().indexed(),authorAuthUserId:n.string().indexed(),authorName:n.string(),body:n.string(),bodyContentMarker:n.string().optional(),createdAt:n.number().indexed(),updatedAt:n.number().indexed()}),reviewMessages:n.entity({pageId:n.string().indexed(),pathname:n.string().indexed(),authorId:n.string().indexed(),authorAuthUserId:n.string().indexed(),authorName:n.string(),body:n.string(),bodyContentMarker:n.string(),createdAt:n.number().indexed(),updatedAt:n.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`}},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:`one`,label:`activeReviewInvitation`}},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`}},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:n.entity({reviewerId:n.string(),displayName:n.string(),color:n.string(),active:n.boolean(),cursorX:n.number().optional(),cursorY:n.number().optional(),viewportWidth:n.number().optional(),viewportHeight:n.number().optional()})}}}),a=new Uint8Array([107,167,184,17,157,173,17,209,128,180,0,192,79,212,48,200]);function o(e,t,n){return u(e,n),`${e}:${t}:${n}`}async function s(e,t,n){return l(o(e,t,n))}async function c(e){if(!e.trim())throw Error(`Comment ID is required for source context`);return l(`${e}:source-context`)}async function l(e){let t=globalThis.crypto?.subtle;if(!t)throw Error(`Web Crypto is required to persist review capture context`);let n=new TextEncoder().encode(`01works.visual-review/v1/${e}`),r=new Uint8Array(a.length+n.length);r.set(a),r.set(n,a.length);let i=new Uint8Array(await t.digest(`SHA-1`,r)).slice(0,16);i[6]=i[6]&15|80,i[8]=i[8]&63|128;let o=[...i].map(e=>e.toString(16).padStart(2,`0`)).join(``);return`${o.slice(0,8)}-${o.slice(8,12)}-${o.slice(12,16)}-${o.slice(16,20)}-${o.slice(20)}`}function u(e,t){if(!e.trim())throw Error(`Comment ID is required for capture context`);if(!Number.isSafeInteger(t)||t<0)throw Error(`Capture child ordinal must be a non-negative integer`)}const d=131072,f=32768,p=4096,m=1e3;var h=class extends Error{constructor(e,t){super(e,t),this.name=`InstantCaptureIntegrityError`}};function g(e){let t=oe(e,`comment target`,!0),n={kind:t.kind,anchor:fe(t.anchor),region:he(t.region),elements:t.elements.map(e=>({selector:e.selector,tagName:e.tagName,textPreview:e.textPreview,rect:he(e.rect),source:null,componentStack:[]})),pageUrl:t.pageUrl,pageTitle:t.pageTitle,capturedAt:t.capturedAt},r={elements:t.elements.map(e=>({selector:e.selector,source:e.source?ge(e.source):null,componentStack:e.componentStack.map(ge)})),...t.sourceProvenance?{sourceProvenance:_e(t.sourceProvenance)}:{}};D(n,d,`Public target`);let i=we(r,65536,`Source context`);return{publicTarget:n,commentFields:te(n),targetElements:n.elements.map(ne),sourceContextJson:i}}function ee(e,t,n){let r=S(e,n),i={selector:C(r.anchorSelector,`${n} anchor selector`,p),offsetX:E(r.anchorOffsetX,`${n} anchor offsetX`),offsetY:E(r.anchorOffsetY,`${n} anchor offsetY`),viewportX:E(r.anchorViewportX,`${n} anchor viewportX`),viewportY:E(r.anchorViewportY,`${n} anchor viewportY`)},a=y({kind:r.targetKind,anchor:i,region:{pageX:r.targetPageX,pageY:r.targetPageY,width:r.targetWidth,height:r.targetHeight,viewportX:r.targetViewportX,viewportY:r.targetViewportY,viewportWidth:r.targetViewportWidth,viewportHeight:r.targetViewportHeight},elements:[],pageUrl:me({origin:w(r.targetPageOrigin,`${n} page origin`,1024),pathname:w(r.targetPagePathname,`${n} page pathname`,4096),search:C(r.targetPageSearch,`${n} page search`,1024),hash:C(r.targetPageHash,`${n} page hash`,1024)}),pageTitle:r.targetPageTitle,capturedAt:r.targetCapturedAt},n),o=re(t,`${n} elements`),s=a;for(let e of o)try{s=y({...a,elements:[...s.elements,e]},n)}catch{}return s}function _(e){let t=ae(e);return t?Object.entries(t).map(([e,t],n)=>typeof t==`string`?{ordinal:n,key:e,valueType:`string`,stringValue:t}:typeof t==`number`?{ordinal:n,key:e,valueType:`number`,numberValue:t}:typeof t==`boolean`?{ordinal:n,key:e,valueType:`boolean`,booleanValue:t}:{ordinal:n,key:e,valueType:`null`}):[]}function v(e){if(!Array.isArray(e)||e.length===0)return;let t=[...e].map(e=>{try{let t=S(e,`Comment scope entry`);return{row:t,ordinal:be(t.ordinal,`Comment scope entry ordinal`,0,19),id:typeof t.id==`string`?t.id:``}}catch{return null}}).filter(e=>e!==null).sort((e,t)=>e.ordinal-t.ordinal||e.id.localeCompare(t.id)),n={},r=new Set;for(let{row:e,ordinal:i}of t)if(!r.has(i)){r.add(i);try{let t=w(e.key,`Comment scope key`,256);if(Object.hasOwn(n,t))continue;switch(e.valueType){case`string`:n[t]=C(e.stringValue,`Comment scope ${t}`,m);break;case`number`:n[t]=T(e.numberValue,`Comment scope ${t}`);break;case`boolean`:if(typeof e.booleanValue!=`boolean`)throw O(`Comment scope ${t} is invalid`);n[t]=e.booleanValue;break;case`null`:n[t]=null;break;default:throw O(`Comment scope ${t} is invalid`)}}catch{}}if(Object.keys(n).length!==0)try{return D(n,f,`Comment scope`),n}catch{return}}function te(e){let t=new URL(e.pageUrl);return{anchorSelector:e.anchor.selector,anchorOffsetX:e.anchor.offsetX,anchorOffsetY:e.anchor.offsetY,anchorViewportX:e.anchor.viewportX,anchorViewportY:e.anchor.viewportY,targetKind:e.kind,targetPageX:e.region.pageX,targetPageY:e.region.pageY,targetWidth:e.region.width,targetHeight:e.region.height,targetViewportX:e.region.viewportX,targetViewportY:e.region.viewportY,targetViewportWidth:e.region.viewportWidth,targetViewportHeight:e.region.viewportHeight,targetPageOrigin:t.origin,targetPagePathname:pe(t.pathname),targetPageSearch:t.search.slice(1),targetPageHash:t.hash.slice(1),targetPageTitle:e.pageTitle,targetCapturedAt:e.capturedAt}}function ne(e,t){return{ordinal:t,selector:e.selector,tagName:e.tagName,textPreview:e.textPreview,pageX:e.rect.pageX,pageY:e.rect.pageY,width:e.rect.width,height:e.rect.height,viewportX:e.rect.viewportX,viewportY:e.rect.viewportY,viewportWidth:e.rect.viewportWidth,viewportHeight:e.rect.viewportHeight}}function re(e,t){if(!Array.isArray(e)||e.length===0)return[];let n=[...e].map(e=>{try{let n=S(e,t);return{row:n,ordinal:be(n.ordinal,`${t} ordinal`,0,7),id:typeof n.id==`string`?n.id:``}}catch{return null}}).filter(e=>e!==null).sort((e,t)=>e.ordinal-t.ordinal||e.id.localeCompare(t.id)),r=[],i=new Set;for(let{row:e,ordinal:a}of n)if(!i.has(a)){i.add(a);try{r.push(se({selector:e.selector,tagName:e.tagName,textPreview:e.textPreview,rect:{pageX:e.pageX,pageY:e.pageY,width:e.width,height:e.height,viewportX:e.viewportX,viewportY:e.viewportY,viewportWidth:e.viewportWidth,viewportHeight:e.viewportHeight},source:null,componentStack:[]},`${t}[${a}]`,!1))}catch{}}return r}function y(e,t){return oe(e,t,!1)}function b(e,t=`Comment body`){return w(e,t,2e4)}function ie(e,t=`Comment body`){let n=b(e,t);for(let e of n)if(e.trim())return e;throw O(`${t} is empty`)}function ae(e){if(e==null)return;let t=S(e,`Comment scope`);if(Object.keys(t).length>20)throw O(`Comment scope has too many fields`);let n={};for(let[e,r]of Object.entries(t)){if(!e.trim()||e.length>256)throw O(`Comment scope has an invalid key`);if(typeof r==`string`){if(r.length>m)throw O(`Comment scope ${e} is too long`);n[e]=r}else if(typeof r==`number`)n[e]=T(r,`Comment scope ${e}`);else if(typeof r==`boolean`||r===null)n[e]=r;else throw O(`Comment scope ${e} is invalid`)}return D(n,f,`Comment scope`),n}function oe(e,t,n){let r=x(e,t,[`kind`,`anchor`,`region`,`elements`,`pageUrl`,`pageTitle`,`capturedAt`,`sourceProvenance`],[`sourceProvenance`]),i=r.kind;if(i!==`element`&&i!==`region`&&i!==`viewport`)throw O(`${t} kind is invalid`);let a=ve(r.elements,`${t} elements`);if(a.length>8)throw O(`${t} contains too many elements`);if(!n&&r.sourceProvenance!=null)throw O(`${t} exposes source provenance`);let o=w(r.pageUrl,`${t} pageUrl`,8192);Ce(o,`${t} pageUrl`);let s={kind:i,anchor:ce(r.anchor,`${t} anchor`),region:le(r.region,`${t} region`),elements:a.map((e,r)=>se(e,`${t} elements[${r}]`,n)),pageUrl:o,pageTitle:C(r.pageTitle,`${t} pageTitle`,m),capturedAt:xe(r.capturedAt,`${t} capturedAt`),...n&&r.sourceProvenance!=null?{sourceProvenance:de(r.sourceProvenance,`${t} provenance`)}:{}};return D(s,n?196608:d,t),s}function se(e,t,n){let r=x(e,t,[`selector`,`tagName`,`textPreview`,`rect`,`source`,`componentStack`]),i=ve(r.componentStack,`${t} componentStack`);if(i.length>32)throw O(`${t} componentStack is too large`);if(!n&&(r.source!=null||i.length>0))throw O(`${t} exposes source metadata`);return{selector:w(r.selector,`${t} selector`,p),tagName:w(r.tagName,`${t} tagName`,128),textPreview:C(r.textPreview,`${t} textPreview`,m),rect:le(r.rect,`${t} rect`),source:n&&r.source!=null?ue(r.source,`${t} source`):null,componentStack:n?i.map((e,n)=>ue(e,`${t} stack[${n}]`)):[]}}function ce(e,t){let n=x(e,t,[`selector`,`offsetX`,`offsetY`,`viewportX`,`viewportY`]);return{selector:C(n.selector,`${t} selector`,p),offsetX:E(n.offsetX,`${t} offsetX`),offsetY:E(n.offsetY,`${t} offsetY`),viewportX:E(n.viewportX,`${t} viewportX`),viewportY:E(n.viewportY,`${t} viewportY`)}}function le(e,t){let n=x(e,t,[`pageX`,`pageY`,`width`,`height`,`viewportX`,`viewportY`,`viewportWidth`,`viewportHeight`]),r={pageX:T(n.pageX,`${t} pageX`),pageY:T(n.pageY,`${t} pageY`),width:T(n.width,`${t} width`),height:T(n.height,`${t} height`),viewportX:T(n.viewportX,`${t} viewportX`),viewportY:T(n.viewportY,`${t} viewportY`),viewportWidth:T(n.viewportWidth,`${t} viewportWidth`),viewportHeight:T(n.viewportHeight,`${t} viewportHeight`)};if(r.width<0||r.height<0||r.viewportWidth<0||r.viewportHeight<0)throw O(`${t} has a negative dimension`);return r}function ue(e,n){let r=x(e,n,[`filePath`,`lineNumber`,`columnNumber`,`componentName`,`origin`,`generated`],[`generated`]),i=r.origin;if(i!==`app`&&i!==`shared-ui`&&i!==`dependency`&&i!==`generated`&&i!==`unknown`)throw O(`${n} origin is invalid`);let a=w(r.filePath,`${n} filePath`,p);return{filePath:a,lineNumber:Se(r.lineNumber,`${n} lineNumber`),columnNumber:Se(r.columnNumber,`${n} columnNumber`),componentName:r.componentName==null?null:w(r.componentName,`${n} componentName`,512),origin:t(a)}}function de(e,t){let n=x(e,t,[`provider`,`build`,`resolvedAt`]),r=n.provider;if(r!==`react-grab`&&r!==`embedded`&&r!==`custom`)throw O(`${t} provider is invalid`);let i=x(n.build,`${t} build`,[`buildId`,`gitCommit`,`generatedAt`,`mode`],[`generatedAt`]),a=i.mode;if(a!==`development`&&a!==`preview`&&a!==`production`)throw O(`${t} build mode is invalid`);return{provider:r,build:{buildId:w(i.buildId,`${t} buildId`,512),gitCommit:i.gitCommit==null?null:w(i.gitCommit,`${t} gitCommit`,256),...i.generatedAt==null?{}:{generatedAt:xe(i.generatedAt,`${t} generatedAt`)},mode:a},resolvedAt:xe(n.resolvedAt,`${t} resolvedAt`)}}function fe(e){return{...e}}function pe(e){return e.replace(/\/+$/,``)||`/`}function me(e){return`${e.origin}${e.pathname}${e.search?`?${e.search}`:``}${e.hash?`#${e.hash}`:``}`}function he(e){return{...e}}function ge(e){return{...e}}function _e(e){return{...e,build:{...e.build}}}function x(e,t,n,r=[]){let i=S(e,t);if(Object.keys(i).some(e=>!n.includes(e)))throw O(`${t} contains unknown fields`);for(let e of n)if(!r.includes(e)&&!Object.hasOwn(i,e))throw O(`${t} is missing ${e}`);return i}function S(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw O(`${t} is missing or invalid`);return e}function ve(e,t){if(!Array.isArray(e))throw O(`${t} is missing or invalid`);return e}function C(e,t,n){if(typeof e!=`string`||ye(e,n)>n)throw O(`${t} is invalid or too long`);return e}function ye(e,t){let n=0;for(let r of e)if(n+=1,n>t)return n;return n}function w(e,t,n){let r=C(e,t,n);if(!r.trim())throw O(`${t} is empty`);return r}function T(e,t){if(typeof e!=`number`||!Number.isFinite(e))throw O(`${t} is invalid`);return e}function be(e,t,n,r){if(typeof e!=`number`||!Number.isSafeInteger(e)||e<n||e>r)throw O(`${t} is invalid`);return e}function E(e,t){let n=T(e,t);if(n<0||n>1)throw O(`${t} is outside 0..1`);return n}function xe(e,t){if(typeof e!=`number`||!Number.isSafeInteger(e)||e<0||e>0xe677d21fdbff)throw O(`${t} is invalid`);return e}function Se(e,t){if(e===null)return null;if(typeof e!=`number`||!Number.isSafeInteger(e)||e<0)throw O(`${t} is invalid`);return e}function Ce(e,t){let n;try{n=new URL(e)}catch(e){throw O(`${t} is invalid`,e)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw O(`${t} must use HTTP or HTTPS`)}function D(e,t,n){we(e,t,n)}function we(e,t,n){let r;try{r=JSON.stringify(e)}catch(e){throw O(`${n} cannot be serialized`,e)}if(r.length>t)throw O(`${n} is too large`);return r}function O(e,t){return new h(e,{cause:t instanceof Error?t:void 0})}function Te(e){if(!e.trim())throw Error(`InstantDB app ID is required`);return r({appId:e,schema:i,devtool:!1})}var Ee=class{db;session;#e;constructor(e,t,n={}){this.db=e,this.session=t,this.#e=n.commentTransport}async getSnapshot(e){let{data:t}=await this.db.queryOnce(De(e,this.session.authUserId));return ke(e,t.comments,t.replies)}async getProjectSnapshot(e){this.#t(e);let{data:t}=await this.db.queryOnce(Oe(e,this.session.authUserId));return Ae(e,t.comments,t.replies)}subscribe(e,t,n){return this.db.subscribeQuery(De(e,this.session.authUserId),r=>{if(r.error){n?.(Le(r.error));return}if(r.data)try{t(ke(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(Oe(e,this.session.authUserId),r=>{if(r.error){n?.(Le(r.error));return}if(r.data)try{t(Ae(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 r=b(n.body).trim(),i=ie(r),a=N(n.createdAt,`Comment createdAt`),l=N(n.updatedAt,`Comment updatedAt`);if(l<a)throw new h(`Comment updatedAt precedes createdAt`);if(n.createdAt!==n.updatedAt)throw new h(`Comment edits must go through the review service`);if(n.status!==`open`||n.resolvedAt!==void 0||n.resolvedById!==void 0)throw new h(`New comments must start open`);let u=g(n.target);if(!Fe(n.anchor,u.publicTarget.anchor))throw new h(`Comment anchor does not match its target`);if(this.#e){await this.#e.createComment({pageId:e,commentId:n.id,body:r,target:n.target,...n.scope===void 0?{}:{scope:n.scope}});return}let d=_(n.scope),f=await Promise.all(u.targetElements.map(async e=>({element:e,id:await s(n.id,`element`,e.ordinal),slotKey:o(n.id,`element`,e.ordinal)}))),p=await Promise.all(d.map(async e=>({entry:e,id:await s(n.id,`scope`,e.ordinal),slotKey:o(n.id,`scope`,e.ordinal)}))),m=await c(n.id);await this.db.transact([this.db.tx.comments[n.id].update({pageId:e,authorId:this.session.reviewerId,authorAuthUserId:this.session.authUserId,authorName:this.session.displayName,body:r,bodyContentMarker:i,...u.commentFields,status:n.status,workflowRevision:0,createdAt:a,updatedAt:l}).link({page:e,author:this.session.reviewerId}),...f.map(({element:e,id:t,slotKey:r})=>this.db.tx.commentTargetElements[t].update({commentId:n.id,slotKey:r,authorAuthUserId:this.session.authUserId,...e}).link({comment:n.id})),...p.map(({entry:e,id:t,slotKey:r})=>this.db.tx.commentScopeEntries[t].update({commentId:n.id,slotKey:r,authorAuthUserId:this.session.authUserId,...e}).link({comment:n.id})),this.db.tx.commentSourceContexts[m].update({commentId:n.id,pageId:e,authorAuthUserId:this.session.authUserId,contextJson:u.sourceContextJson,createdAt:a}).link({comment:n.id,page:e,author:this.session.reviewerId})]);return}case`comment.delete`:throw new h(`Comment deletion must go through the review service`);case`reply.upsert`:{let n=t.reply;if(n.authorId!==this.session.reviewerId)throw Error(`Reply author mismatch`);let r=b(n.body,`Reply body`).trim(),i=ie(r,`Reply body`),a=N(n.createdAt,`Reply createdAt`),o=N(n.updatedAt,`Reply updatedAt`);if(o<a)throw new h(`Reply updatedAt precedes createdAt`);if(n.createdAt!==n.updatedAt)throw new h(`Reply edits must go through the review service`);if(this.#e){await this.#e.createReply({commentId:n.commentId,replyId:n.id,body:r});return}await this.db.transact(this.db.tx.replies[n.id].update({pageId:e,commentId:n.commentId,authorId:this.session.reviewerId,authorAuthUserId:this.session.authUserId,authorName:this.session.displayName,body:r,bodyContentMarker:i,createdAt:a,updatedAt:o}).link({comment:n.commentId,author:this.session.reviewerId}));return}case`reply.delete`:throw new h(`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 De(e,t){return{comments:{$:{where:{pageId:e},order:{createdAt:`asc`}},targetElements:{},scopeEntries:{}},replies:{$:{where:{pageId:e},order:{createdAt:`asc`}}},$users:{$:{where:{id:t}},activeReviewInvitation:{project:{}}}}}function Oe(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}},activeReviewInvitation:{project:{}}}}}function ke(e,t,n){let r=t.map(t=>Me(t,e));k(r,`comment`);let i=new Set(r.map(({id:e})=>e)),a=n.map(t=>Ne(t,e));k(a,`reply`);for(let e of a)if(!i.has(e.commentId))throw new h(`Reply ${e.id} references a comment outside the page snapshot`);return{pageId:e,comments:r,replies:a}}function Ae(e,t,n){let r=t.map(t=>{let n=j(t.pageId,`comment pageId`);return je(t.page,n,e,`comment ${String(t.id)}`),Me(t,n)});k(r,`comment`);let i=new Map(r.map(e=>[e.id,e])),a=n.map(t=>{let n=j(t.pageId,`reply pageId`),r=A(t.comment,`reply ${String(t.id)} comment`),a=j(t.commentId,`reply ${String(t.id)} commentId`);if(j(r.id,`reply ${String(t.id)} comment relation id`)!==a)throw new h(`Reply ${String(t.id)} has an inconsistent comment relation`);je(r.page,n,e,`reply ${String(t.id)}`);let o=Ne(t,n),s=i.get(o.commentId);if(!s||s.pageId!==n)throw new h(`Reply ${o.id} references a comment outside the project snapshot`);return o});return k(a,`reply`),{projectId:e,comments:r,replies:a}}function je(e,t,n,r){let i=A(e,`${r} page`);if(j(i.id,`${r} page id`)!==t)throw new h(`${r} escaped its page relation`);if(j(A(i.project,`${r} project`).id,`${r} project id`)!==n)throw new h(`${r} escaped its project scope`)}function Me(e,t){let n=A(e,`comment`),r=j(n.id,`comment id`);if(j(n.pageId,`comment ${r} pageId`)!==t)throw new h(`Comment ${r} escaped its page scope`);j(n.authorAuthUserId,`comment ${r} authorAuthUserId`);let i=!(Object.hasOwn(n,`anchor`)&&Object.hasOwn(n,`target`)),a=i?ee(n,n.targetElements,`comment ${r} target`):y(n.target,`comment ${r} target`),o=i?{...a.anchor}:Pe(n.anchor,`comment ${r} anchor`);if(!Fe(o,a.anchor))throw new h(`Comment ${r} has inconsistent anchors`);let s=n.status;if(s!==`open`&&s!==`resolved`)throw new h(`Comment ${r} has an invalid status`);let c=N(n.createdAt,`comment ${r} createdAt`),l=N(n.updatedAt,`comment ${r} updatedAt`);if(l<c)throw new h(`Comment ${r} updatedAt precedes createdAt`);let u=Ve(n.resolvedAt,`comment ${r} resolvedAt`),d=He(n.resolvedById,`comment ${r} resolvedById`);if(s===`open`&&(u!==void 0||d!==void 0))throw new h(`Open comment ${r} contains resolution metadata`);if(s===`resolved`&&(u===void 0||d===void 0||u!==l))throw new h(`Resolved comment ${r} has invalid workflow metadata`);return{id:r,pageId:t,authorId:j(n.authorId,`comment ${r} authorId`),authorName:j(n.authorName,`comment ${r} authorName`),body:b(n.body,`comment ${r} body`),anchor:o,target:a,...i?Ie(v(n.scopeEntries)):n.scope==null?{}:{scope:ae(n.scope)},status:s,workflowRevision:Be(n.workflowRevision,`comment ${r} workflowRevision`),createdAt:c,updatedAt:l,...u===void 0?{}:{resolvedAt:u},...d===void 0?{}:{resolvedById:d}}}function Ne(e,t){let n=A(e,`reply`),r=j(n.id,`reply id`);if(j(n.pageId,`reply ${r} pageId`)!==t)throw new h(`Reply ${r} escaped its page scope`);j(n.authorAuthUserId,`reply ${r} authorAuthUserId`);let i=N(n.createdAt,`reply ${r} createdAt`),a=N(n.updatedAt,`reply ${r} updatedAt`);if(a<i)throw new h(`Reply ${r} updatedAt precedes createdAt`);return{id:r,commentId:j(n.commentId,`reply ${r} commentId`),authorId:j(n.authorId,`reply ${r} authorId`),authorName:j(n.authorName,`reply ${r} authorName`),body:b(n.body,`reply ${r} body`),createdAt:i,updatedAt:a}}function Pe(e,t){let n=A(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 h(`${t} has invalid fields`);return{selector:ze(n.selector,`${t} selector`),offsetX:M(n.offsetX,`${t} offsetX`),offsetY:M(n.offsetY,`${t} offsetY`),viewportX:M(n.viewportX,`${t} viewportX`),viewportY:M(n.viewportY,`${t} viewportY`)}}function Fe(e,t){return e.selector===t.selector&&e.offsetX===t.offsetX&&e.offsetY===t.offsetY&&e.viewportX===t.viewportX&&e.viewportY===t.viewportY}function Ie(e){return e===void 0?{}:{scope:e}}function k(e,t){let n=new Set;for(let r of e){if(n.has(r.id))throw new h(`Duplicate ${t} ${r.id}`);n.add(r.id)}}function Le(e){let t=Re(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 Re(e){return e instanceof Error?e.message:e&&typeof e==`object`&&`message`in e?String(e.message):``}function A(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw new h(`${t} is missing or invalid`);return e}function ze(e,t){if(typeof e!=`string`)throw new h(`${t} is invalid`);return e}function j(e,t){let n=ze(e,t);if(!n.trim())throw new h(`${t} is empty`);return n}function M(e,t){if(typeof e!=`number`||!Number.isFinite(e)||e<0||e>1)throw new h(`${t} is invalid`);return e}function N(e,t){if(!Number.isSafeInteger(e)||e<0||e>0xe677d21fdbff)throw new h(`${t} is invalid`);return e}function Be(e,t){if(!Number.isSafeInteger(e)||e<0)throw new h(`${t} is invalid`);return e}function Ve(e,t){return e==null?void 0:N(e,t)}function He(e,t){return e==null?void 0:j(e,t)}Object.freeze({field:`createdAt`,direction:`desc`,tieBreaker:`id`,tieDirection:`desc`}),Object.freeze({comments:200,replies:1e3,sourceContexts:200});var Ue=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=[],o=!1,s=[],c=()=>{o||n({pageId:e,peers:structuredClone(i),messages:structuredClone(a)})};if(t.cursors){let n=Qe(e,t.roomId),a,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 o=this.db.joinRoom(`visualReview`,t),s=o.subscribePresence({},e=>{if(e.error){r?.(I(e.error));return}i=Object.values(e.peers).map(e=>Ge(e)).filter(e=>e!==null).sort((e,t)=>e.displayName.localeCompare(t.displayName)||e.peerId.localeCompare(t.peerId)),c()}),l=a;a=()=>{s(),o.leaveRoom()},this.#e.set(n,{pageId:e,roomId:t,publishPresence:e=>o.publishPresence(e)}),l?.()},p=()=>{if(!u||o)return;let e=Math.max(1e3,d.expiresAt-Date.now()+250);l=setTimeout(()=>{m()},e)},m=async()=>{try{let e=await d.refresh();if(o||!e)return;d={...e,refresh:d.refresh},u&&(u.id=e.id,u.expiresAt=e.expiresAt),f(e.id),p()}catch(e){if(o)return;r?.(I(e)),o||(l=setTimeout(()=>{m()},5e3))}};f(d.id),p(),s.push(()=>{l&&clearTimeout(l),this.#e.delete(n),a?.()})}return t.chat&&s.push(this.db.subscribeQuery(We(e,this.session.authUserId),t=>{if(t.error){r?.(I(t.error));return}if(t.data)try{a=t.data.reviewMessages.map(t=>Ke(t,e)).sort($e),c()}catch{r?.({type:`error`,code:`data-integrity`,recovery:`retry`,clearSnapshot:!0})}})),queueMicrotask(c),()=>{if(!o){o=!0;for(let e of s.reverse())try{e()}catch{}}}}publishPresence(e,t,n){let r=this.#e.get(Qe(e,t));r&&r.publishPresence({reviewerId:this.session.reviewerId,displayName:this.session.displayName,color:n.color,active:n.active,...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}})}async sendMessage(e,t){if(t.pageId!==e)throw Error(`Review message page mismatch`);if(t.authorId!==this.session.reviewerId)throw Error(`Review message author mismatch`);let n=qe(t.body),r=Je(t.pathname),i=F(t.createdAt,`Review message createdAt`),a=F(t.updatedAt,`Review message updatedAt`);if(i!==a)throw Error(`New review messages must be immutable`);await this.db.transact(this.db.tx.reviewMessages[t.id].update({pageId:e,pathname:r,authorId:this.session.reviewerId,authorAuthUserId:this.session.authUserId,authorName:this.session.displayName,body:n,bodyContentMarker:ie(n,`Review message body`),createdAt:i,updatedAt:a}).link({page:e,author:this.session.reviewerId}))}};function We(e,t){return{reviewMessages:{$:{where:{pageId:e},order:{createdAt:`asc`}}},$users:{$:{where:{id:t}},activeReviewInvitation:{project:{}}}}}function Ge(e){let t=Ye(e.reviewerId,128),n=Ye(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=Xe(e.cursorX),a=Xe(e.cursorY),o=Ze(e.viewportWidth),s=Ze(e.viewportHeight);return{peerId:e.peerId,reviewerId:t,displayName:n,color:r,active:e.active,...i===void 0?{}:{cursorX:i},...a===void 0?{}:{cursorY:a},...o===void 0?{}:{viewportWidth:o},...s===void 0?{}:{viewportHeight:s}}}function Ke(e,t){let n=P(e.id,`Review message id`,128);if(P(e.pageId,`Review message pageId`,128)!==t)throw Error(`Review message page mismatch`);return{id:n,pageId:t,pathname:Je(e.pathname),authorId:P(e.authorId,`Review message authorId`,128),authorName:P(e.authorName,`Review message authorName`,512),body:qe(e.body),createdAt:F(e.createdAt,`Review message createdAt`),updatedAt:F(e.updatedAt,`Review message updatedAt`)}}function qe(e){return P(e,`Review message body`,4e3)}function Je(e){let t=P(e,`Review message pathname`,2e3);if(!t.startsWith(`/`))throw Error(`Review message pathname is invalid`);return t}function P(e,t,n){if(typeof e!=`string`)throw Error(`${t} is invalid`);let r=e.trim();if(!r||r.length>n)throw Error(`${t} is invalid`);return r}function Ye(e,t){if(typeof e!=`string`)return null;let n=e.trim();return n&&n.length<=t?n:null}function Xe(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function Ze(e){let t=Xe(e);return t!==void 0&&t>0?t:void 0}function F(e,t){if(!Number.isSafeInteger(e)||e<0)throw Error(`${t} is invalid`);return e}function Qe(e,t){return JSON.stringify([e,t])}function $e(e,t){return e.createdAt-t.createdAt||e.id.localeCompare(t.id)}function I(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`});const L={$default:`false`},R={$default:`false`},z=`$user.activeReviewInvitation.project.id`;function B(e){return[`auth.id != null`,`size(auth.ref('$user.activeReviewInvitation.id')) == 1`,`size(auth.ref('${z}')) == 1`,`size(auth.ref('$user.activeReviewInvitation.status')) == 1`,`size(auth.ref('$user.activeReviewInvitation.expiresAt')) == 1`,`size(auth.ref('$user.activeReviewInvitation.revokedAt')) == 0`,`size(auth.ref('$user.activeReviewInvitation.project.originDisconnectedAt')) == 0`,`auth.ref('$user.activeReviewInvitation.status')[0] == 'accepted'`,`request.time < timestamp(auth.ref('$user.activeReviewInvitation.expiresAt')[0])`,e].join(` && `)}function V(...e){return e.map(e=>`(${e})`).join(` && `)}function H(...e){return e.map(e=>`(${e})`).join(` || `)}function U(e,t){return`!has(${e}.${t}) || ${e}.${t} == null`}const et=`actions.data == 'create' && actions.linkedData == 'update'`,tt=e=>`${e}.matches('[^ \\t\\n\\r\\f\\u000b\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]')`,nt=e=>V(`${e} >= 0`,`${e} <= 253402300799999`,`${e} % 1 == 0`),rt=e=>V(nt(e),`(request.time - timestamp(${e})).getSeconds() <= 86400`,`(request.time - timestamp(${e})).getSeconds() >= -300`);function W(e){let t=`data.ref('${e}project.allowedOrigin')`,n=`data.ref('${e}stagingOrigin')`;return H(`size(${t}) == 0`,V(`size(${n}) == 1`,`${n}[0] in ${t}`))}const it=B(`auth.ref('${z}')[0] in data.ref('project.id')`);H(`size(data.ref('project.allowedOrigin')) == 0`,V(`has(data.stagingOrigin) && data.stagingOrigin != null`,`data.stagingOrigin in data.ref('project.allowedOrigin')`)),V(`actions.data == 'update' && actions.linkedData == 'create'`,H(`auth.id != null && auth.id in data.ref('project.owners.id')`,it));const at=B(`auth.ref('${z}')[0] in data.ref('page.project.id')`);W(`page.`),V(et,H(`auth.id != null && auth.id in data.ref('page.project.owners.id')`,at),`auth.id != null && auth.id == data.authorAuthUserId && auth.id in data.ref('author.$user.id')`,`data.pageId in data.ref('page.id')`,`data.authorId in data.ref('author.id') && data.authorAuthUserId in data.ref('author.$user.id') && data.authorName in data.ref('author.displayName')`,`data.pageId in data.ref('author.project.pages.id')`),V(et,`data.pageId == linkedData.id`,`auth.id != null && auth.id == data.authorAuthUserId`,`data.authorId in auth.ref('$user.reviewerMemberships.id')`,`data.authorId in linkedData.ref('project.reviewers.id')`,H(`auth.id != null && auth.id in linkedData.ref('project.owners.id')`,B(`auth.ref('${z}')[0] in linkedData.ref('project.id')`))),H(`auth.id != null && auth.id in data.ref('comment.page.project.owners.id')`,V(B(`auth.ref('${z}')[0] in data.ref('comment.page.project.id')`),W(`comment.page.`))),B(`auth.ref('${z}')[0] in data.ref('comment.page.project.id')`),W(`comment.page.`),B(`auth.ref('${z}')[0] in data.ref('page.project.id')`),W(`page.`);const ot=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`,ot),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`,ot),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')`),{...L},{...R},{...R},B(`data.id == auth.ref('${z}')[0]`),{...L},V(`isOwner`,H(V(U(`data`,`allowedOrigin`),U(`newData`,`allowedOrigin`)),`newData.allowedOrigin == data.allowedOrigin`),H(V(U(`data`,`originDisconnectedAt`),U(`newData`,`originDisconnectedAt`)),`newData.originDisconnectedAt == data.originDisconnectedAt`)),{...R},{...L},V(`isOwner`,H(V(U(`data`,`autoRegistrationSlot`),U(`newData`,`autoRegistrationSlot`)),`newData.autoRegistrationSlot == data.autoRegistrationSlot`)),{...R},{...L},{...R},B(`auth.ref('${z}')[0] in data.ref('project.id')`),{...L},{...R},{...L},{...L},{...R},{...R},{...L},{...R},{...L},{...L},{...L},{...L},{...L},V(`data.createdAt == data.updatedAt`,rt(`data.createdAt`),`size(data.body) > 0`,`size(data.body) <= 4000`,`size(data.bodyContentMarker) == 1`,tt(`data.bodyContentMarker`),`data.body.startsWith(data.bodyContentMarker)`,`size(data.authorName) > 0`,`size(data.authorName) <= 512`,tt(`data.authorName`),`size(data.pathname) > 0`,`size(data.pathname) <= 2000`,`request.modifiedFields.all(field, field in ['pageId', 'pathname', 'authorId', 'authorAuthUserId', 'authorName', 'body', 'bodyContentMarker', 'createdAt', 'updatedAt', 'page', 'author'])`),{...L},{...R};var st=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewerCommentSaveError`}};function ct(e,t){let n=e.replace(/\/+$/,``),r=async(e,r)=>{let i=await t();if(!i)throw new st(`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 st(`NETWORK`,`리뷰 서버에 연결하지 못했습니다. 연결을 확인하고 다시 시도해 주세요.`)}if(!a.ok)throw new st(...await lt(a))};return{createComment:e=>r(`/v1/comments`,e),createReply:e=>r(`/v1/replies`,e)}}async function lt(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]}}var G=class extends Error{code;constructor(e,t){super(t),this.code=e,this.name=`ReviewBootstrapError`}};const K=new Map;function ut(e,t={}){let n=e.trim();if(!n)throw Error(`InstantDB app ID is required`);return{async resolve(e){let r=(t.databaseFactory??Te)(n),i=await pt(n,r,e.authHost,e.signal,e.styleNonce,t.reviewerToken);for(;;)try{await dt(t.acceptInvitation,{refreshToken:i.refresh_token,invitationId:e.invitationId,suggestedDisplayName:Et(i.email),authHost:e.authHost,signal:e.signal,styleNonce:e.styleNonce});break}catch(a){if(!Dt(a,`INVITATION_EMAIL_MISMATCH`))throw a;await yt(e.authHost,e.signal,e.styleNonce),i=await mt(n,r,i,e.authHost,e.signal,e.styleNonce,t.reviewerToken)}let{data:a}=await r.queryOnce({reviewers:{$:{where:{authUserId:i.id}}},reviewPages:{$:{where:{stagingOrigin:e.location.origin}}},invitations:{}}),o=ft(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 G(o.code,jt(o.code));let s={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`},c=o.page.id,l=e.collaboration?.scopeId,u=e.collaboration?.cursors===!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,d=u&&t.resolveCollaborationRoom?{...u,refresh:async()=>{let n=await r.getAuth();if(!(!n?.refresh_token||l===void 0))return t.resolveCollaborationRoom?.({refreshToken:n.refresh_token,invitationId:e.invitationId,pageId:c,scopeId:l,signal:new AbortController().signal})}}:void 0,f=0;return{pageId:o.page.id,resolvePage:async n=>{let i=++f,a=()=>i!==f||n.signal.aborted,o=(e,t,n)=>({pageId:e,...n&&d?{collaborationRoomId:n.id}:{},commit:()=>{a()||(c=e,t!==void 0&&(l=t),!(!n||!d)&&(d.id=n.id,d.expiresAt=n.expiresAt))}});if(n.location.origin!==e.location.origin)throw new G(`PAGE_NOT_FOUND`,jt(`PAGE_NOT_FOUND`));let{data:u}=await r.queryOnce({reviewPages:{$:{where:{stagingOrigin:n.location.origin}}}});if(a())throw Ot();let p=Z(n.location.pathname),m=u.reviewPages.find(e=>e.projectId===s.projectId&&e.stagingOrigin===n.location.origin&&Z(e.pathname)===p);if(!m)throw new G(`PAGE_NOT_FOUND`,jt(`PAGE_NOT_FOUND`));if(n.collaboration?.cursors!==!0||d===void 0||t.resolveCollaborationRoom===void 0||!n.collaboration)return o(m.id,n.collaboration?.scopeId);let h=await r.getAuth();if(a())throw Ot();if(!h?.refresh_token)throw Error(`리뷰 세션이 만료되어 실시간 협업 연결을 갱신할 수 없습니다.`);let g=await t.resolveCollaborationRoom({refreshToken:h.refresh_token,invitationId:e.invitationId,pageId:m.id,scopeId:n.collaboration.scopeId,signal:n.signal});if(a())throw Ot();return o(m.id,n.collaboration.scopeId,g)},session:s,repository:new Ee(r,s,{...t.serviceUrl===void 0?{}:{commentTransport:ct(t.serviceUrl,async()=>(await r.getAuth())?.refresh_token??null)}}),...e.collaboration?.cursors===!0||e.collaboration?.chat===!0?{collaborationRepository:new Ue(r,s,d)}:{},...e.collaboration?.cursors===!0&&d?{collaborationRoomId:d.id}:{},allowedStagingHosts:[e.location.hostname]}}}}async function dt(e,t){if(!e)return;let n={refreshToken:t.refreshToken,invitationId:t.invitationId,signal:t.signal};try{await e(n);return}catch(e){if(!Dt(e,`DISPLAY_NAME_REQUIRED`))throw e}let r=await vt(t.authHost,t.signal,t.suggestedDisplayName,t.styleNonce);await e({...n,displayName:r})}function ft(e,t,n,r){let i=e.find(({id:e})=>e===r.invitationId);if(!i||At(i.email)!==At(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=Z(r.location.pathname),s=n.find(e=>e.projectId===i.projectId&&e.stagingOrigin===r.location.origin&&Z(e.pathname)===o);return s?{ok:!0,invitation:i,reviewer:a,page:s}:{ok:!1,code:`PAGE_NOT_FOUND`}}async function pt(e,t,n,r,i,a){let o=await q(r,()=>t.getAuth());if(o)return o;let s=await ht(e,r);try{return await q(r,()=>t.getAuth())||(a?await q(r,async()=>{await t.auth.signInWithToken(a);let e=await t.getAuth();if(!e)throw Error(`Reviewer session did not establish`);return e}):await _t(t,n,r,i))}finally{s()}}async function mt(e,t,n,r,i,a,o){J(i);let s=await ht(e,i);try{let e=await q(i,()=>t.getAuth());return e&&e.id!==n.id?e:(e&&await q(i,async()=>{await t.auth.signOut()}),J(i),o?await q(i,async()=>{await t.auth.signInWithToken(o);let e=await t.getAuth();if(!e)throw Error(`Reviewer session did not establish`);return e}):await _t(t,r,i,a))}finally{s()}}async function ht(e,t){J(t);let n=K.get(e);return n||(n={held:!1,waiters:[]},K.set(e,n)),n.held?new Promise((e,r)=>{let i,a=()=>{let e=n.waiters.indexOf(i);e<0||(n.waiters.splice(e,1),t.removeEventListener(`abort`,a),r(X()))};i={signal:t,resolve:e,reject:r,abort:a},n.waiters.push(i),t.addEventListener(`abort`,a,{once:!0}),t.aborted&&a()}):(n.held=!0,gt(e,n))}function gt(e,t){let n=!1;return()=>{if(!n){for(n=!0;t.waiters.length>0;){let n=t.waiters.shift();if(n.signal.removeEventListener(`abort`,n.abort),n.signal.aborted){n.reject(X());continue}n.resolve(gt(e,t));return}t.held=!1,K.get(e)===t&&K.delete(e)}}}async function q(e,t){J(e);let n=await t();return J(e),n}function J(e){if(e.aborted)throw X()}function _t(e,t,n,r){try{J(n)}catch(e){return Promise.reject(e)}let i=xt(t);i.replaceChildren(St(i,r),Ct(i));let a=i.querySelector(`form`),o=i.querySelector(`h2`),s=i.querySelector(`.description`),c=a.elements.namedItem(`email`),l=a.elements.namedItem(`code`),u=i.querySelector(`.code`),d=i.querySelector(`button`),f=i.querySelector(`.error`),p=!1;return new Promise((t,r)=>{let m=!1,h=!1,g=!1,ee=()=>{n.removeEventListener(`abort`,v),i.removeEventListener(`keydown`,te),a.removeEventListener(`submit`,ne)},_=e=>{m||(m=!0,ee(),e())},v=()=>{m||(g=!0,ee(),h||_(()=>r(X())))},te=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),v()):t.key===`Tab`&&kt(i,t)},ne=async i=>{if(i.preventDefault(),!(m||h)){h=!0,f.textContent=``,d.disabled=!0,a.setAttribute(`aria-busy`,`true`);try{if(J(n),!p){let t=c.value.trim();await q(n,async()=>{await e.auth.sendMagicCode({email:t})}),p=!0,c.readOnly=!0,u.hidden=!1,l.required=!0,o.textContent=`인증 코드`,s.textContent=`${t}로 보낸 코드를 입력하세요.`,d.textContent=`확인`,l.focus();return}await q(n,async()=>{await e.auth.signInWithMagicCode({email:c.value.trim(),code:l.value.trim()})});let r=await q(n,()=>e.getAuth());if(!r)throw Error(`인증 세션을 확인할 수 없습니다.`);_(()=>t(r))}catch(e){if(m||n.aborted||Dt(e,`AUTH_CANCELLED`))return;f.textContent=bt(e)}finally{h=!1,!m&&(g||n.aborted)?_(()=>r(X())):m||(d.disabled=!1,a.removeAttribute(`aria-busy`))}}};n.addEventListener(`abort`,v,{once:!0}),i.addEventListener(`keydown`,te),a.addEventListener(`submit`,ne)})}function vt(e,t,n=``,r){if(t.aborted)return Promise.reject(X());let i=xt(e);i.replaceChildren(St(i,r),wt(i));let a=i.querySelector(`form`),o=a.elements.namedItem(`displayName`),s=i.querySelector(`button`),c=i.querySelector(`.error`);return o.value=n.trim().slice(0,120),o.focus(),o.select(),new Promise((e,n)=>{let r=!1,l=e=>{r||(r=!0,t.removeEventListener(`abort`,u),i.removeEventListener(`keydown`,p),a.removeEventListener(`submit`,f),e())},u=()=>l(()=>n(X())),d=()=>{let t=o.value.trim();if(!t){c.textContent=`이름을 입력해 주세요.`,o.focus();return}if(t.length>120){c.textContent=`이름은 120자 이하여야 합니다.`,o.focus();return}c.textContent=``,s.disabled=!0,a.setAttribute(`aria-busy`,`true`),l(()=>e(t))},f=e=>{e.preventDefault(),d()},p=e=>{let t=e;if(t.key===`Escape`){t.preventDefault(),u();return}if(t.key===`Tab`){kt(i,t);return}t.key===`Enter`&&!t.isComposing&&t.target===o&&(t.preventDefault(),d())};t.addEventListener(`abort`,u,{once:!0}),i.addEventListener(`keydown`,p),a.addEventListener(`submit`,f)})}function yt(e,t,n){if(t.aborted)return Promise.reject(X());let r=xt(e);r.replaceChildren(St(r,n),Tt(r));let i=r.querySelector(`form`),a=r.querySelector(`button`);return a.focus(),new Promise((e,n)=>{let o=!1,s=e=>{o||(o=!0,t.removeEventListener(`abort`,c),r.removeEventListener(`keydown`,u),i.removeEventListener(`submit`,l),e())},c=()=>s(()=>n(X())),l=t=>{t.preventDefault(),a.disabled=!0,i.setAttribute(`aria-busy`,`true`),s(e)},u=e=>{let t=e;t.key===`Escape`?(t.preventDefault(),c()):t.key===`Tab`&&kt(r,t)};t.addEventListener(`abort`,c,{once:!0}),r.addEventListener(`keydown`,u),i.addEventListener(`submit`,l)})}function bt(e){if(e&&typeof e==`object`&&`body`in e){let t=e.body;if(t?.message)return t.message}return e instanceof Error?e.message:`로그인에 실패했습니다. 다시 시도해 주세요.`}function xt(e){return e.shadowRoot??e.attachShadow({mode:`open`})}function St(e,t){let n=e.ownerDocument.createElement(`style`);return t&&(n.nonce=t),n.textContent=Mt,n}function Ct(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-login-title`),n.setAttribute(`aria-describedby`,`visual-review-login-description`);let r=Y(t,`p`,`01.works`,`eyebrow`),i=Y(t,`h2`,`Visual Review`);i.id=`visual-review-login-title`;let a=Y(t,`p`,`초대받은 이메일로 계속하세요.`,`description`);a.id=`visual-review-login-description`;let o=t.createElement(`label`);o.append(`이메일`);let s=t.createElement(`input`);s.name=`email`,s.type=`email`,s.autocomplete=`email`,s.required=!0,o.append(s);let c=t.createElement(`label`);c.className=`code`,c.hidden=!0,c.append(`인증 코드`);let l=t.createElement(`input`);l.name=`code`,l.inputMode=`numeric`,l.autocomplete=`one-time-code`,c.append(l);let u=Y(t,`p`,``,`error`);u.setAttribute(`role`,`alert`);let d=Y(t,`button`,`코드 받기`);return d.type=`submit`,n.append(r,i,a,o,c,u,d),n}function wt(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=Y(t,`p`,`01.works`,`eyebrow`),i=Y(t,`h2`,`표시 이름`);i.id=`visual-review-name-title`;let a=Y(t,`p`,`피드백에 이 이름이 표시됩니다.`,`description`);a.id=`visual-review-name-description`;let o=Y(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=Y(t,`p`,``,`error`);c.setAttribute(`role`,`alert`);let l=Y(t,`button`,`리뷰 시작`);return l.type=`submit`,n.append(r,i,a,o,s,c,l),n}function Tt(e){let t=e.ownerDocument,n=t.createElement(`form`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`true`),n.setAttribute(`aria-labelledby`,`visual-review-account-title`),n.setAttribute(`aria-describedby`,`visual-review-account-description`);let r=Y(t,`p`,`01.works`,`eyebrow`),i=Y(t,`h2`,`계정 확인`);i.id=`visual-review-account-title`;let a=Y(t,`p`,`이 초대는 현재 로그인한 계정과 일치하지 않습니다.`,`description`);a.id=`visual-review-account-description`;let o=Y(t,`button`,`다른 계정으로 계속`);return o.type=`submit`,n.append(r,i,a,o),n}function Y(e,t,n,r){let i=e.createElement(t);return i.textContent=n,r&&(i.className=r),i}function Et(e){return e?.split(`@`)[0]?.trim().slice(0,120)??``}function Dt(e,t){return!!(e&&typeof e==`object`&&`code`in e&&e.code===t)}function Ot(){return new DOMException(`Aborted`,`AbortError`)}function X(){return new G(`AUTH_CANCELLED`,`리뷰 로그인이 취소되었습니다.`)}function kt(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())}function Z(e){return e.replace(/\/+$/,``)||`/`}function At(e){return e.trim().toLowerCase()}function jt(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`이 페이지는 아직 리뷰 대상이 아닙니다. 담당자에게 문의하세요.`}}const Mt=`
|
|
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
|
+
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
|
+
`;var Q=class extends Error{code;status;constructor(e,t,n){super(t),this.code=e,this.status=n,this.name=`HostedBootstrapError`}};function Nt(e={}){return Pt(e)}function Pt(t={},n={}){let r=e(t.serviceUrl??`https://visual-review.01.works`),i=t.fetch??globalThis.fetch,a=n.instantRuntimeFactory??ut,o=n.now??Date.now;if(typeof i!=`function`)throw Error(`Visual Review bootstrap requires the Fetch API`);return{async resolve(e){let t=It(e),n=await Rt(r,i,t,e.signal),s=o(),c=await a(n.instantAppId,{serviceUrl:r,...n.reviewer?{reviewerToken:n.reviewer.token}:{},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 Bt(t);if(!t.ok)throw new Q($(n,`error`)??`INVITATION_ACCEPT_FAILED`,$(n,`message`)??`리뷰 초대 수락에 실패했습니다.`,t.status)},...e.collaboration?.cursors===!0?{resolveCollaborationRoom:e=>Lt(r,i,e)}:{}}).resolve(e),l=c.resolvePage;if(!l)return c;let u=async t=>{await Rt(r,i,{invitationId:e.invitationId,origin:t.location.origin,pathname:t.location.pathname},t.signal),s=o()};return{...c,resolvePage:async e=>{o()-s>=3e5&&await u(e);try{return await l(e)}catch(t){if(!Ft(t,`PAGE_NOT_FOUND`))throw t;return await u(e),await l(e)}}}}}}function Ft(e,t){return!!(e&&typeof e==`object`&&`code`in e&&e.code===t)}function It(e){return{invitationId:e.invitationId,origin:e.location.origin,pathname:e.location.pathname}}async function Lt(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 Bt(r);if(!r.ok)throw new Q($(i,`error`)??`COLLABORATION_ROOM_FAILED`,$(i,`message`)??`실시간 협업 연결에 실패했습니다.`,r.status);if(!i||typeof i!=`object`||Array.isArray(i))throw new Q(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);let a=i,o=$(a,`id`);if(!o||!/^vr_[A-Za-z0-9_-]{43}$/u.test(o)||!Number.isSafeInteger(a.expiresAt))throw new Q(`INVALID_COLLABORATION_ROOM`,`잘못된 협업 응답입니다.`,502);return{id:o,expiresAt:a.expiresAt}}async function Rt(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 Bt(i);if(!i.ok)throw new Q($(a,`error`)??`BOOTSTRAP_FAILED`,$(a,`message`)??`Visual Review bootstrap에 실패했습니다.`,i.status);return zt(a)}function zt(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new Q(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);let t=e,n=$(t,`instantAppId`);if(t.apiVersion!==1||!n||!Vt(n))throw new Q(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);let r=t.reviewer;if(r==null)return{apiVersion:1,instantAppId:n};let i=$(r,`token`),a=$(r,`email`);if(!i||!a)throw new Q(`INVALID_BOOTSTRAP_RESPONSE`,`잘못된 bootstrap 응답입니다.`,502);return{apiVersion:1,instantAppId:n,reviewer:{token:i,email:a}}}async function Bt(e){try{return await e.json()}catch{throw new Q(`INVALID_BOOTSTRAP_RESPONSE`,`JSON bootstrap 응답이 필요합니다.`,502)}}function $(e,t){if(!e||typeof e!=`object`||Array.isArray(e))return null;let n=e[t];return typeof n==`string`&&n.trim()?n.trim():null}function Vt(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{Nt as createHostedReviewRuntime};
|
package/dist/index.d.ts
CHANGED
|
@@ -111,6 +111,12 @@ export interface VisualReviewSnapshot {
|
|
|
111
111
|
replies: VisualReviewReply[];
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
export interface VisualReviewProjectSnapshot {
|
|
115
|
+
projectId: string;
|
|
116
|
+
comments: VisualReviewComment[];
|
|
117
|
+
replies: VisualReviewReply[];
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
export interface VisualReviewPresence {
|
|
115
121
|
peerId: string;
|
|
116
122
|
reviewerId: string;
|
|
@@ -164,11 +170,17 @@ export type VisualReviewSubscriptionFailure =
|
|
|
164
170
|
|
|
165
171
|
export interface VisualReviewRepository {
|
|
166
172
|
getSnapshot(pageId: string): Promise<VisualReviewSnapshot>;
|
|
173
|
+
getProjectSnapshot?(projectId: string): Promise<VisualReviewProjectSnapshot>;
|
|
167
174
|
subscribe(
|
|
168
175
|
pageId: string,
|
|
169
176
|
listener: (snapshot: VisualReviewSnapshot) => void,
|
|
170
177
|
onFailure?: (failure: VisualReviewSubscriptionFailure) => void,
|
|
171
178
|
): () => void;
|
|
179
|
+
subscribeProject?(
|
|
180
|
+
projectId: string,
|
|
181
|
+
listener: (snapshot: VisualReviewProjectSnapshot) => void,
|
|
182
|
+
onFailure?: (failure: VisualReviewSubscriptionFailure) => void,
|
|
183
|
+
): () => void;
|
|
172
184
|
mutate(pageId: string, mutation: VisualReviewMutation): Promise<void>;
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -201,6 +213,31 @@ export interface ReviewRuntimeRequest {
|
|
|
201
213
|
};
|
|
202
214
|
}
|
|
203
215
|
|
|
216
|
+
/** The route-specific half of a mounted review session. */
|
|
217
|
+
export interface ReviewPageRequest {
|
|
218
|
+
location: Location;
|
|
219
|
+
signal: AbortSignal;
|
|
220
|
+
collaboration?: {
|
|
221
|
+
cursors: boolean;
|
|
222
|
+
chat: boolean;
|
|
223
|
+
scopeId: string;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The page state returned when an existing session moves to another route. */
|
|
228
|
+
export interface ReviewPageContext {
|
|
229
|
+
pageId: string;
|
|
230
|
+
collaborationRoomId?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A resolved page that has not become the active route yet. The installation
|
|
235
|
+
* commits only the winning swap immediately before handing it to the widget.
|
|
236
|
+
*/
|
|
237
|
+
export interface PreparedReviewPage extends ReviewPageContext {
|
|
238
|
+
commit?(): void;
|
|
239
|
+
}
|
|
240
|
+
|
|
204
241
|
/** Advanced runtime seam intended for self-hosted development and tests. */
|
|
205
242
|
export interface ReviewRuntimeContext {
|
|
206
243
|
pageId: string;
|
|
@@ -209,6 +246,11 @@ export interface ReviewRuntimeContext {
|
|
|
209
246
|
collaborationRepository?: VisualReviewCollaborationRepository;
|
|
210
247
|
collaborationRoomId?: string;
|
|
211
248
|
allowedStagingHosts: string[];
|
|
249
|
+
/**
|
|
250
|
+
* Re-resolve only the page-specific context after a client-side route change.
|
|
251
|
+
* Omit this method to make the installation fall back to a full restart.
|
|
252
|
+
*/
|
|
253
|
+
resolvePage?(request: ReviewPageRequest): Promise<PreparedReviewPage>;
|
|
212
254
|
}
|
|
213
255
|
|
|
214
256
|
export interface VisualReviewRuntime {
|
|
@@ -236,6 +278,8 @@ export interface InstallVisualReviewOptions {
|
|
|
236
278
|
/** CSP nonce for review-owned styles. Defaults to a nonce found on the host document. */
|
|
237
279
|
styleNonce?: string;
|
|
238
280
|
onError?: (error: Error) => void;
|
|
281
|
+
/** Called once for each successful mount owned by this installation. */
|
|
282
|
+
onReady?: (widget: HTMLElement) => void;
|
|
239
283
|
}
|
|
240
284
|
|
|
241
285
|
export interface VisualReviewInstallation {
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./install-
|
|
1
|
+
import{t as e}from"./install-CIPBUJ9O.js";export{e as installVisualReview};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=`https://visual-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;function o(e={}){if(e.enabled!==!0||typeof window>`u`||typeof document>`u`)return n();let r=!1,i=0,a=null,o=null,p=null,h=null,g=!1,_=null,S=null,E=document.baseURI,j=new WeakMap,N=new Map,P=G(window),F=ee(e.styleNonce,document),I=e.activationParam??`visual-review`,L=v(window.location,I),R=m(window,e=>{let t=v(e,I);if(t===L)return;let n=O(I,e.href);n&&N.set(e.origin,n),L=t,B(e)}),z=e=>{r||!h||e.target!==h||(L=v(window.location,I),W())};return window.addEventListener(`visual-review:reauthenticate-required`,z),{ready:W(),refresh(){return r?Promise.resolve(null):(L=v(window.location,I),B(y(window.location),!0))},dispose(){r||(r=!0,i+=1,H(),h?.remove(),h=null,S=null,_?.remove(),_=null,N.clear(),R.unsubscribe(),window.removeEventListener(`visual-review:reauthenticate-required`,z))}};function B(e,t=!1){return!h||!S||S.origin!==e.origin||O(I,e.href)?W():V(h,S,e,t)}function V(t,n,r,c){let l=++i;a!==o&&a?.abort();let u=new AbortController;a=u;let d=s(e.build,r),f=(async()=>{try{let i=await A(n.resolvePage({location:r,signal:u.signal,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:d}}),u.signal);return Q(l,u)?(i.commit?.(),t.setPage({pageId:i.pageId,collaboration:{roomId:i.collaborationRoomId,scopeId:d}},{reload:c}),t.isConnected||document.body?.append(t),t):$(l)}catch{return Q(l,u)?W():$(l)}})();return p=f,f}function H(){a?.abort(),o?.abort(),a=null,o=null}function W(){let t=++i;H(),h?.remove(),h=null,S=null,_?.remove();let n=new AbortController,r=document.createElement(`div`);r.dataset.agencyReviewRoot=``,a=n,o=n,_=r;let s=q(t,n,r);return p=s,s.then(r=>{r&&Q(t,n)&&h===r&&e.onReady?.(r)}),s}async function q(t,n,i){try{let e=document.body??await M(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(d(a,`AUTH_CANCELLED`))return i.remove(),_===i&&(_=null),null;let o=j.get(i)?.[3];Z(a,i);let s=X(a,j.get(i)?.[2]);return o!==`storage`||g||!f(a)?Y(i,s.message,F,()=>{r||_!==i||(L=v(window.location,I),W())}):(i.remove(),_===i&&(_=null)),e.onError?.(s),window.dispatchEvent(new CustomEvent(`visual-review:error`,{detail:s})),null}}async function J(n,r,i){let a=t(e.serviceUrl??`https://visual-review.01.works`),o=y(window.location),d=D(a,I,o,P),f=b(N.get(o.origin)),p=x(C(a,o.origin)),m=d?.source===`url`?d:f??p??d;if(!m)return j.delete(i),i.remove(),_===i&&(_=null),null;let O=m.invitationId;j.set(i,[a,o.origin,O,m.source]),m.source===`url`?(K(a,o.origin,P),N.set(o.origin,O),w(a,o.origin,O),R.suppress(()=>k(I))):m.source===`handoff`&&N.set(o.origin,O),L=v(window.location,I);let M=await A((e.runtime??await c(a)).resolve({invitationId:O,location:o,signal:r.signal,authHost:i,styleNonce:F,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:s(e.build,o)}}),r.signal);if(!Q(n,r))return null;T(a,o.origin,O),U(a,O,o,P),N.set(o.origin,O);let z=e.sourceContextResolver??await u(e.build,E,r.signal);if(!Q(n,r))return null;let{mountReviewWidget:B}=await l(r.signal);return Q(n,r)?(h=B({...M,collaboration:{cursors:e.collaboration?.cursors===!0&&!!(M.collaborationRepository&&M.collaborationRoomId),chat:e.collaboration?.chat===!0&&!!M.collaborationRepository,repository:M.collaborationRepository,roomId:M.collaborationRoomId,scopeId:s(e.build,o)},developerTools:e.developerTools,sourceContextResolver:z,styleNonce:F}),S=M.resolvePage?{origin:o.origin,resolvePage:e=>M.resolvePage(e)}:null,i.remove(),_===i&&(_=null),g=!0,window.dispatchEvent(new CustomEvent(`visual-review:ready`,{detail:h})),h):null}function Z(e,t){if(!f(e))return;let n=j.get(t);if(!n)return;let[r,i,a]=n;T(r,i,a),N.get(i)===a&&N.delete(i),K(r,i,P,a)}function Q(e,t){return!r&&i===e&&a===t&&!t.signal.aborted}function $(e){return!r&&i!==e?p:null}}function s(e,t){return JSON.stringify([N(e).buildId,t.pathname,t.search])}async function c(e){let{createHostedReviewRuntime:t}=await import(`./hosted-runtime-T7DtCpZJ.js`);return t({serviceUrl:e})}async function l(e){return j(import(`./src-DIG2uBK3.js`),e,a,`리뷰 위젯을 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`)}async function u(e,t,n){let r=N(e),{createGeneratedSourceContextResolver:i}=await j(import(`./generated-source-context-BZb4U5wW.js`),n,a,`리뷰 소스 정보를 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`);return i({build:r})}function d(e,t){return e instanceof Error&&`code`in e&&e.code===t}function f(e){let t=e instanceof Error&&`code`in e?e.code:void 0;return typeof t==`string`&&/^INVITATION_(?:EXPIRED|NOT_FOUND|REVOKED)$/.test(t)}const p=new WeakMap;function m(e,t){let n=p.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),h(e)},patchedReplaceState:function(t,n,r){i.call(this,t,n,r),h(e)},popstateListener:()=>h(e),subscribers:a,suppressionDepth:0},p.set(e,n),g(t,`pushState`,n.patchedPushState),g(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),_(n.history,`pushState`,n.patchedPushState,n.originalPushStateDescriptor),_(n.history,`replaceState`,n.patchedReplaceState,n.originalReplaceStateDescriptor),p.delete(e)))}}}function h(e){let t=p.get(e);if(!t||t.suppressionDepth>0)return;let n=y(e.location);[...t.subscribers].forEach(e=>e(n))}function g(e,t,n){Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:!0,value:n})}function _(e,t,n,r){if(Object.getOwnPropertyDescriptor(e,t)?.value===n){if(r){Object.defineProperty(e,t,r);return}delete e[t]}}function v(e,t){return`${e.origin}\n${e.pathname}\n${O(t,e.href)??``}`}function y(e){return new URL(e.href)}function b(e){return e?{invitationId:e,source:`memory`}:null}function x(e){return e?{invitationId:e,source:`handoff`}:null}function S(e,t){return JSON.stringify([e,t])}function C(e,t,n=Date.now()){let i=S(e,t),a=r.get(i);if(a){if(a.expiresAt>n)return a.invitationId;clearTimeout(a.timeout),r.delete(i)}}function w(e,t,n,a=Date.now()){E(a);let o=S(e,t),s=r.get(o);for(s&&(clearTimeout(s.timeout),r.delete(o));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 c=a+i,l=setTimeout(()=>{r.get(o)?.expiresAt===c&&r.delete(o)},i);r.set(o,{invitationId:n,expiresAt:c,timeout:l})}function T(e,t,n){let i=S(e,t),a=r.get(i);a?.invitationId===n&&(clearTimeout(a.timeout),r.delete(i))}function E(e){for(let[t,n]of r)n.expiresAt>e||(clearTimeout(n.timeout),r.delete(t))}function D(e,t,n,r){let i=O(t,n.href);if(i)return{invitationId:i,source:`url`};let a=W(r,q(e,n.origin));return a?{invitationId:a,source:`storage`}:J(n.hostname)?{invitationId:`local-preview`,source:`local-preview`}:null}function O(e,t){let n=new URL(t);return(n.searchParams.get(e)??n.searchParams.get(`agency-review`)??n.searchParams.get(`agency-review-invite`))?.trim()||null}function k(e){let t=new URL(window.location.href),n=new Set([e,`visual-review`,`agency-review`,`agency-review-invite`]),r=!1;n.forEach(e=>{t.searchParams.has(e)&&(t.searchParams.delete(e),r=!0)}),r&&window.history.replaceState(window.history.state,``,`${t.pathname}${t.search}${t.hash}`)}function A(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 j(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 M(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 N(e){let t=document.querySelector(`meta[name="visual-review-build"]`),n=document.querySelector(`meta[name="visual-review-commit"]`),r=H(I()),i=H(L());return{buildId:V(e?.buildId)??B(t)??r??`unversioned`,gitCommit:R(e,n,i),generatedAt:e?.generatedAt,mode:e?.mode??F(P())??(J(window.location.hostname)?`development`:`production`)}}function P(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_MODE}catch{return}}function F(e){return e===`development`||e===`preview`||e===`production`?e:void 0}function I(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_ID}catch{return}}function L(){try{return process.env.VISUAL_REVIEW_PUBLIC_GIT_COMMIT}catch{return}}function R(e,t,n){return e?.gitCommit===null?null:typeof e?.gitCommit==`string`?z(e.gitCommit):z(t?.content)??z(n)}function z(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 B(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 U(e,t,n,r){try{r?.setItem(q(e,n.origin),t)}catch{}}function W(e,t){try{return e?.getItem(t)??null}catch{return null}}function G(e){try{return e.localStorage}catch{return null}}function K(e,t,n,r){try{if(!n)return;let i=q(e,t),a=n.getItem(i);if(a===null||r!==void 0&&a!==r)return;n.removeItem(i)}catch{}}function q(e,t){return`visual-review:${e}:${t}:invitation`}function J(e){return e===`localhost`||e===`127.0.0.1`||e.endsWith(`.test`)||e.endsWith(`.local`)}function Y(e,t,n,r){let i=e.shadowRoot??e.attachShadow({mode:`open`}),a=i.ownerDocument,o=a.createElement(`style`);n&&(o.nonce=n),o.textContent=te;let s=a.createElement(`strong`);s.textContent=`Visual Review를 시작할 수 없습니다`;let c=a.createElement(`span`);c.textContent=t;let l=a.createElement(`button`);l.type=`button`,l.textContent=`다시 시도`,l.addEventListener(`click`,()=>{l.disabled=!0,r?.()},{once:!0}),i.replaceChildren(o,s,c,l)}function X(e,t){let n=e instanceof Error?e.message:String(e),r=Error(Z(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:Z(n,t)}),typeof i==`number`&&Number.isFinite(i)&&Object.defineProperty(r,"status",{configurable:!0,enumerable:!0,value:i})}return r}function Z(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 ee(e,t){if(e!==void 0)return $(e);let n=t.currentScript,r=n?Q(n):void 0;if(r)return r;try{for(let e of t.querySelectorAll(`script[nonce],style[nonce]`)){let t=Q(e);if(t)return t}}catch{}}function Q(e){let t=`nonce`in e?e.nonce:void 0;return typeof t==`string`?$(t):void 0}function $(e){return e.trim()||void 0}const te=`:host{all:initial;position:fixed;right:18px;bottom:18px;z-index:2147483646;max-width:360px;padding:16px 18px;border:1px solid #efc4bd;border-radius:14px;background:#fff8f6;box-shadow:0 12px 40px rgba(0,0,0,.14);color:#7a271a;font:13px/1.5 Inter,ui-sans-serif,system-ui,sans-serif}strong{display:block;margin-bottom:4px;color:#53150d}span{display:block}button{margin-top:12px;padding:7px 10px;border:1px solid #d9a79f;border-radius:8px;background:#fff;color:#7a271a;font:650 12px/1 Inter,ui-sans-serif,system-ui,sans-serif;cursor:pointer}button:focus-visible{outline:2px solid #7a271a;outline-offset:2px}button:disabled{opacity:.55;cursor:wait}`;export{e as n,t as r,o as t};
|
|
1
|
+
const e=`https://visual-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;function o(e={}){if(e.enabled!==!0||typeof window>`u`||typeof document>`u`)return n();let r=!1,i=0,a=null,o=null,p=null,h=null,g=!1,_=null,S=null,E=document.baseURI,j=new WeakMap,N=new Map,P=G(window),F=ee(e.styleNonce,document),I=e.activationParam??`visual-review`,L=v(window.location,I),R=m(window,e=>{let t=v(e,I);if(t===L)return;let n=O(I,e.href);n&&N.set(e.origin,n),L=t,B(e)}),z=e=>{r||!h||e.target!==h||(L=v(window.location,I),W())};return window.addEventListener(`visual-review:reauthenticate-required`,z),{ready:W(),refresh(){return r?Promise.resolve(null):(L=v(window.location,I),B(y(window.location),!0))},dispose(){r||(r=!0,i+=1,H(),h?.remove(),h=null,S=null,_?.remove(),_=null,N.clear(),R.unsubscribe(),window.removeEventListener(`visual-review:reauthenticate-required`,z))}};function B(e,t=!1){return!h||!S||S.origin!==e.origin||O(I,e.href)?W():V(h,S,e,t)}function V(t,n,r,c){let l=++i;a!==o&&a?.abort();let u=new AbortController;a=u;let d=s(e.build,r),f=(async()=>{try{let i=await A(n.resolvePage({location:r,signal:u.signal,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:d}}),u.signal);return Q(l,u)?(i.commit?.(),t.setPage({pageId:i.pageId,collaboration:{roomId:i.collaborationRoomId,scopeId:d}},{reload:c}),t.isConnected||document.body?.append(t),t):$(l)}catch{return Q(l,u)?W():$(l)}})();return p=f,f}function H(){a?.abort(),o?.abort(),a=null,o=null}function W(){let t=++i;H(),h?.remove(),h=null,S=null,_?.remove();let n=new AbortController,r=document.createElement(`div`);r.dataset.agencyReviewRoot=``,a=n,o=n,_=r;let s=q(t,n,r);return p=s,s.then(r=>{r&&Q(t,n)&&h===r&&e.onReady?.(r)}),s}async function q(t,n,i){try{let e=document.body??await M(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(d(a,`AUTH_CANCELLED`))return i.remove(),_===i&&(_=null),null;let o=j.get(i)?.[3];Z(a,i);let s=X(a,j.get(i)?.[2]);return o!==`storage`||g||!f(a)?Y(i,s.message,F,()=>{r||_!==i||(L=v(window.location,I),W())}):(i.remove(),_===i&&(_=null)),e.onError?.(s),window.dispatchEvent(new CustomEvent(`visual-review:error`,{detail:s})),null}}async function J(n,r,i){let a=t(e.serviceUrl??`https://visual-review.01.works`),o=y(window.location),d=D(a,I,o,P),f=b(N.get(o.origin)),p=x(C(a,o.origin)),m=d?.source===`url`?d:f??p??d;if(!m)return j.delete(i),i.remove(),_===i&&(_=null),null;let O=m.invitationId;j.set(i,[a,o.origin,O,m.source]),m.source===`url`?(K(a,o.origin,P),N.set(o.origin,O),w(a,o.origin,O),R.suppress(()=>k(I))):m.source===`handoff`&&N.set(o.origin,O),L=v(window.location,I);let M=await A((e.runtime??await c(a)).resolve({invitationId:O,location:o,signal:r.signal,authHost:i,styleNonce:F,collaboration:{cursors:e.collaboration?.cursors===!0,chat:e.collaboration?.chat===!0,scopeId:s(e.build,o)}}),r.signal);if(!Q(n,r))return null;T(a,o.origin,O),U(a,O,o,P),N.set(o.origin,O);let z=e.sourceContextResolver??await u(e.build,E,r.signal);if(!Q(n,r))return null;let{mountReviewWidget:B}=await l(r.signal);return Q(n,r)?(h=B({...M,collaboration:{cursors:e.collaboration?.cursors===!0&&!!(M.collaborationRepository&&M.collaborationRoomId),chat:e.collaboration?.chat===!0&&!!M.collaborationRepository,repository:M.collaborationRepository,roomId:M.collaborationRoomId,scopeId:s(e.build,o)},developerTools:e.developerTools,sourceContextResolver:z,styleNonce:F}),S=M.resolvePage?{origin:o.origin,resolvePage:e=>M.resolvePage(e)}:null,i.remove(),_===i&&(_=null),g=!0,window.dispatchEvent(new CustomEvent(`visual-review:ready`,{detail:h})),h):null}function Z(e,t){if(!f(e))return;let n=j.get(t);if(!n)return;let[r,i,a]=n;T(r,i,a),N.get(i)===a&&N.delete(i),K(r,i,P,a)}function Q(e,t){return!r&&i===e&&a===t&&!t.signal.aborted}function $(e){return!r&&i!==e?p:null}}function s(e,t){return JSON.stringify([N(e).buildId,t.pathname,t.search])}async function c(e){let{createHostedReviewRuntime:t}=await import(`./hosted-runtime-omn7O2tq.js`);return t({serviceUrl:e})}async function l(e){return j(import(`./src-CKG_8hE0.js`),e,a,`리뷰 위젯을 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`)}async function u(e,t,n){let r=N(e),{createGeneratedSourceContextResolver:i}=await j(import(`./generated-source-context-BZb4U5wW.js`),n,a,`리뷰 소스 정보를 불러오는 시간이 초과되었습니다. 다시 시도해 주세요.`);return i({build:r})}function d(e,t){return e instanceof Error&&`code`in e&&e.code===t}function f(e){let t=e instanceof Error&&`code`in e?e.code:void 0;return typeof t==`string`&&/^INVITATION_(?:EXPIRED|NOT_FOUND|REVOKED)$/.test(t)}const p=new WeakMap;function m(e,t){let n=p.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),h(e)},patchedReplaceState:function(t,n,r){i.call(this,t,n,r),h(e)},popstateListener:()=>h(e),subscribers:a,suppressionDepth:0},p.set(e,n),g(t,`pushState`,n.patchedPushState),g(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),_(n.history,`pushState`,n.patchedPushState,n.originalPushStateDescriptor),_(n.history,`replaceState`,n.patchedReplaceState,n.originalReplaceStateDescriptor),p.delete(e)))}}}function h(e){let t=p.get(e);if(!t||t.suppressionDepth>0)return;let n=y(e.location);[...t.subscribers].forEach(e=>e(n))}function g(e,t,n){Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:!0,value:n})}function _(e,t,n,r){if(Object.getOwnPropertyDescriptor(e,t)?.value===n){if(r){Object.defineProperty(e,t,r);return}delete e[t]}}function v(e,t){return`${e.origin}\n${e.pathname}\n${O(t,e.href)??``}`}function y(e){return new URL(e.href)}function b(e){return e?{invitationId:e,source:`memory`}:null}function x(e){return e?{invitationId:e,source:`handoff`}:null}function S(e,t){return JSON.stringify([e,t])}function C(e,t,n=Date.now()){let i=S(e,t),a=r.get(i);if(a){if(a.expiresAt>n)return a.invitationId;clearTimeout(a.timeout),r.delete(i)}}function w(e,t,n,a=Date.now()){E(a);let o=S(e,t),s=r.get(o);for(s&&(clearTimeout(s.timeout),r.delete(o));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 c=a+i,l=setTimeout(()=>{r.get(o)?.expiresAt===c&&r.delete(o)},i);r.set(o,{invitationId:n,expiresAt:c,timeout:l})}function T(e,t,n){let i=S(e,t),a=r.get(i);a?.invitationId===n&&(clearTimeout(a.timeout),r.delete(i))}function E(e){for(let[t,n]of r)n.expiresAt>e||(clearTimeout(n.timeout),r.delete(t))}function D(e,t,n,r){let i=O(t,n.href);if(i)return{invitationId:i,source:`url`};let a=W(r,q(e,n.origin));return a?{invitationId:a,source:`storage`}:J(n.hostname)?{invitationId:`local-preview`,source:`local-preview`}:null}function O(e,t){let n=new URL(t);return(n.searchParams.get(e)??n.searchParams.get(`agency-review`)??n.searchParams.get(`agency-review-invite`))?.trim()||null}function k(e){let t=new URL(window.location.href),n=new Set([e,`visual-review`,`agency-review`,`agency-review-invite`]),r=!1;n.forEach(e=>{t.searchParams.has(e)&&(t.searchParams.delete(e),r=!0)}),r&&window.history.replaceState(window.history.state,``,`${t.pathname}${t.search}${t.hash}`)}function A(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 j(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 M(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 N(e){let t=document.querySelector(`meta[name="visual-review-build"]`),n=document.querySelector(`meta[name="visual-review-commit"]`),r=H(I()),i=H(L());return{buildId:V(e?.buildId)??B(t)??r??`unversioned`,gitCommit:R(e,n,i),generatedAt:e?.generatedAt,mode:e?.mode??F(P())??(J(window.location.hostname)?`development`:`production`)}}function P(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_MODE}catch{return}}function F(e){return e===`development`||e===`preview`||e===`production`?e:void 0}function I(){try{return process.env.VISUAL_REVIEW_PUBLIC_BUILD_ID}catch{return}}function L(){try{return process.env.VISUAL_REVIEW_PUBLIC_GIT_COMMIT}catch{return}}function R(e,t,n){return e?.gitCommit===null?null:typeof e?.gitCommit==`string`?z(e.gitCommit):z(t?.content)??z(n)}function z(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 B(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 U(e,t,n,r){try{r?.setItem(q(e,n.origin),t)}catch{}}function W(e,t){try{return e?.getItem(t)??null}catch{return null}}function G(e){try{return e.localStorage}catch{return null}}function K(e,t,n,r){try{if(!n)return;let i=q(e,t),a=n.getItem(i);if(a===null||r!==void 0&&a!==r)return;n.removeItem(i)}catch{}}function q(e,t){return`visual-review:${e}:${t}:invitation`}function J(e){return e===`localhost`||e===`127.0.0.1`||e.endsWith(`.test`)||e.endsWith(`.local`)}function Y(e,t,n,r){let i=e.shadowRoot??e.attachShadow({mode:`open`}),a=i.ownerDocument,o=a.createElement(`style`);n&&(o.nonce=n),o.textContent=te;let s=a.createElement(`strong`);s.textContent=`Visual Review를 시작할 수 없습니다`;let c=a.createElement(`span`);c.textContent=t;let l=a.createElement(`button`);l.type=`button`,l.textContent=`다시 시도`,l.addEventListener(`click`,()=>{l.disabled=!0,r?.()},{once:!0}),i.replaceChildren(o,s,c,l)}function X(e,t){let n=e instanceof Error?e.message:String(e),r=Error(Z(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:Z(n,t)}),typeof i==`number`&&Number.isFinite(i)&&Object.defineProperty(r,"status",{configurable:!0,enumerable:!0,value:i})}return r}function Z(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 ee(e,t){if(e!==void 0)return $(e);let n=t.currentScript,r=n?Q(n):void 0;if(r)return r;try{for(let e of t.querySelectorAll(`script[nonce],style[nonce]`)){let t=Q(e);if(t)return t}}catch{}}function Q(e){let t=`nonce`in e?e.nonce:void 0;return typeof t==`string`?$(t):void 0}function $(e){return e.trim()||void 0}const te=`:host{all:initial;position:fixed;right:18px;bottom:18px;z-index:2147483646;max-width:360px;padding:16px 18px;border:1px solid #efc4bd;border-radius:14px;background:#fff8f6;box-shadow:0 12px 40px rgba(0,0,0,.14);color:#7a271a;font:13px/1.5 Inter,ui-sans-serif,system-ui,sans-serif}strong{display:block;margin-bottom:4px;color:#53150d}span{display:block}button{margin-top:12px;padding:7px 10px;border:1px solid #d9a79f;border-radius:8px;background:#fff;color:#7a271a;font:650 12px/1 Inter,ui-sans-serif,system-ui,sans-serif;cursor:pointer}button:focus-visible{outline:2px solid #7a271a;outline-offset:2px}button:disabled{opacity:.55;cursor:wait}`;export{e as n,t as r,o as t};
|
package/dist/next-config.js
CHANGED
|
@@ -16,10 +16,9 @@ const VISUAL_REVIEW_CONFIG = Symbol.for("01works.visual-review.next-config");
|
|
|
16
16
|
/**
|
|
17
17
|
* Next evaluates next.config again in compiler workers. A Git fallback contains
|
|
18
18
|
* a per-build public nonce, so recomputing it in each process would make the
|
|
19
|
-
* browser literal disagree with the main process
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* are deliberately outside this record.
|
|
19
|
+
* browser literal disagree with the main process identity. Next 15/16 copy the
|
|
20
|
+
* main process environment into those workers; hand off only the already public
|
|
21
|
+
* identity and validate it before reuse.
|
|
23
22
|
*/
|
|
24
23
|
function resolveNextVisualReviewBuildIdentity(options, env) {
|
|
25
24
|
const privateBuildWorker = env.NEXT_PRIVATE_BUILD_WORKER === "1";
|
package/dist/react.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./install-
|
|
1
|
+
import{t as e}from"./install-CIPBUJ9O.js";import{useEffect as t,useRef as n}from"react";function r({activationParam:r,build:i,collaboration:a,developerTools:o,enabled:s,onError:c,onReady:l,runtime:u,serviceUrl:d,sourceContextResolver:f,styleNonce:p}){let m=n(c),h=n(l),g=i?.buildId,_=i?.gitCommit,v=i?.generatedAt,y=i?.mode,b=o?.reactGrab===!0,x=a?.cursors===!0,S=a?.chat===!0;return m.current=c,h.current=l,t(()=>{if(s!==!0)return;let t=e({activationParam:r,build:g===void 0&&_===void 0&&v===void 0&&y===void 0?void 0:{buildId:g,gitCommit:_,generatedAt:v,mode:y},collaboration:x||S?{cursors:x,chat:S}:void 0,developerTools:b?{reactGrab:!0}:void 0,enabled:s,onError:e=>m.current?.(e),onReady:e=>h.current?.(e),runtime:u,serviceUrl:d,sourceContextResolver:f,styleNonce:p});return()=>t.dispose()},[r,g,y,S,x,b,s,v,_,u,d,f,p]),null}r.displayName=`VisualReview`;export{r as VisualReview};
|