@hyperframes/share 0.0.0-stage → 0.9.21
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/LICENSE +190 -0
- package/README.md +51 -2
- package/dist/appOwnedPaths.d.mts +6 -0
- package/dist/fromReview.d.mts +15 -0
- package/dist/grantRefusal.d.mts +8 -0
- package/dist/host.d.mts +29 -0
- package/dist/hosts.d.ts +39 -0
- package/dist/index.d.mts +21 -0
- package/dist/projectLock.d.mts +17 -0
- package/dist/projectSnapshot.d.mts +20 -0
- package/dist/reviewAudience.d.mts +3 -0
- package/dist/reviewClient.d.mts +122 -0
- package/dist/reviewExport.d.mts +172 -0
- package/dist/reviewFiles.d.mts +43 -0
- package/dist/reviewLinkStates.d.mts +18 -0
- package/dist/reviewOpenCalls.d.mts +16 -0
- package/dist/reviewShare.d.mts +106 -0
- package/dist/reviewSwitch.d.mts +53 -0
- package/dist/reviewUpload.d.mts +16 -0
- package/dist/reviewVersionSentences.d.mts +2 -0
- package/dist/signedInRequest.d.mts +56 -0
- package/dist/storage.d.mts +20 -0
- package/dist/types/access.d.ts +12 -0
- package/dist/types/review.d.ts +59 -0
- package/dist/types/reviewWire.d.ts +200 -0
- package/dist/types/share.d.ts +119 -0
- package/dist/types/shareOpen.d.ts +96 -0
- package/package.json +112 -3
- package/scripts/copy-types.mjs +3 -0
- package/src/appOwnedPaths.mjs +27 -0
- package/src/fromReview.mjs +27 -0
- package/src/grantRefusal.mjs +28 -0
- package/src/host.mjs +46 -0
- package/src/hosts.d.ts +39 -0
- package/src/index.mjs +82 -0
- package/src/projectLock.mjs +120 -0
- package/src/projectSnapshot.mjs +120 -0
- package/src/reviewAudience.mjs +24 -0
- package/src/reviewClient.mjs +260 -0
- package/src/reviewExport.mjs +827 -0
- package/src/reviewFiles.mjs +98 -0
- package/src/reviewLinkStates.mjs +47 -0
- package/src/reviewOpenCalls.mjs +40 -0
- package/src/reviewShare.mjs +645 -0
- package/src/reviewSwitch.mjs +430 -0
- package/src/reviewUpload.mjs +98 -0
- package/src/reviewVersionSentences.mjs +48 -0
- package/src/signedInRequest.mjs +125 -0
- package/src/storage.mjs +116 -0
- package/src/types/access.d.ts +12 -0
- package/src/types/review.d.ts +59 -0
- package/src/types/reviewWire.d.ts +200 -0
- package/src/types/share.d.ts +119 -0
- package/src/types/shareOpen.d.ts +96 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Review API request and response shapes. Nullable fields use `| null`.
|
|
2
|
+
|
|
3
|
+
import type { AccessMode, LinkRole } from "./share.js";
|
|
4
|
+
|
|
5
|
+
export interface ErrorEnvelope {
|
|
6
|
+
code: number;
|
|
7
|
+
message: string;
|
|
8
|
+
data: {
|
|
9
|
+
reason: string;
|
|
10
|
+
current_revision?: number;
|
|
11
|
+
/** On a 403 `workspace_membership_required`: the team workspace the link is for, absent when it has no name of
|
|
12
|
+
* its own to show (a personal space never has one). */
|
|
13
|
+
space?: { name: string; kind: "team" };
|
|
14
|
+
workspace_id?: string;
|
|
15
|
+
project_limit?: number;
|
|
16
|
+
projects_used_up_to_limit?: number;
|
|
17
|
+
requested_feature?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RegisterProjectRequest {
|
|
22
|
+
studio_project_id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
client_operation_id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Capabilities {
|
|
28
|
+
can_view: true;
|
|
29
|
+
can_comment: boolean;
|
|
30
|
+
can_export: boolean;
|
|
31
|
+
can_resolve: boolean;
|
|
32
|
+
can_manage_sharing: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ProjectDTO {
|
|
36
|
+
review_entitlements?: ReviewEntitlements | null;
|
|
37
|
+
project_id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
access_mode: AccessMode;
|
|
40
|
+
latest_ready_version_id: string | null;
|
|
41
|
+
review_url: string | null;
|
|
42
|
+
authenticated_review_url: string;
|
|
43
|
+
capabilities: Capabilities;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// GET /v1/hyperframes/review-spaces: personal first, then team spaces by last use. The data
|
|
47
|
+
// is `{ items: SpaceDTO[], version_sharing?: boolean, share_files?: boolean }`: the server's per-account flags for
|
|
48
|
+
// shared reviews and for files on their versions, and anything but `true` (absent from an older server too) is off.
|
|
49
|
+
export interface SpaceDTO {
|
|
50
|
+
review_entitlements?: ReviewEntitlements | null;
|
|
51
|
+
space_id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
kind: "personal" | "team";
|
|
54
|
+
can_create_review: boolean;
|
|
55
|
+
last_used_at: string | null;
|
|
56
|
+
/** The workspace this token is bound to; absent from servers before it. */
|
|
57
|
+
bound?: boolean;
|
|
58
|
+
/** Someone other than the calling user is a member; absent when the server could not count, or
|
|
59
|
+
* predates it. */
|
|
60
|
+
has_other_members?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The workspace a project was registered in.
|
|
64
|
+
interface SpaceRef {
|
|
65
|
+
space_id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
kind: SpaceDTO["kind"];
|
|
68
|
+
/** Someone other than the project's owner is a member; absent as on SpaceDTO. */
|
|
69
|
+
has_other_members?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ShareDTO {
|
|
73
|
+
review_entitlements?: ReviewEntitlements | null;
|
|
74
|
+
sharing_state?: ReviewSharingState;
|
|
75
|
+
project_id: string;
|
|
76
|
+
access_mode: AccessMode;
|
|
77
|
+
default_role: LinkRole;
|
|
78
|
+
comments_enabled: boolean;
|
|
79
|
+
revision: number;
|
|
80
|
+
link_generation: number;
|
|
81
|
+
/** Null when the link is off or the frontend base is unset. */
|
|
82
|
+
review_url: string | null;
|
|
83
|
+
authenticated_review_url: string;
|
|
84
|
+
revoked: boolean;
|
|
85
|
+
space?: SpaceRef | null;
|
|
86
|
+
/** While a moderation hold is active, link viewers and guests receive 404. */
|
|
87
|
+
moderation_hold?: boolean;
|
|
88
|
+
/** Which hold: a staff takedown admits only the owner and refuses any reshare with 409 moderation_hold. */
|
|
89
|
+
moderation_hold_kind?: "takedown" | "auto_reports" | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface UpdateProjectShareRequest {
|
|
93
|
+
access_mode: AccessMode;
|
|
94
|
+
default_role: LinkRole;
|
|
95
|
+
comments_enabled: boolean;
|
|
96
|
+
expected_revision: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ProjectOperationRequest {
|
|
100
|
+
expected_link_generation: number;
|
|
101
|
+
client_operation_id: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// GET …/feedback: only the final page carries change_cursor.
|
|
105
|
+
export interface FeedbackPage {
|
|
106
|
+
items: unknown[];
|
|
107
|
+
next_cursor: string | null;
|
|
108
|
+
change_cursor: string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// POST /v1/hyperframes/review-projects/{id}/exports with a local MP4 (contract "Export a new video version").
|
|
112
|
+
export interface ExportRequest {
|
|
113
|
+
client_operation_id: string;
|
|
114
|
+
title_snapshot: string;
|
|
115
|
+
coordinate_width: number;
|
|
116
|
+
coordinate_height: number;
|
|
117
|
+
duration_seconds: number;
|
|
118
|
+
render_settings: Record<string, never>;
|
|
119
|
+
source_revision_id: null;
|
|
120
|
+
source_fingerprint: string | null;
|
|
121
|
+
local_upload: { size_bytes: number; checksum_sha256: string; content_type: "video/mp4" };
|
|
122
|
+
/** The synced snapshot the video was rendered from, sent only while the server's `share_files` is on. The server
|
|
123
|
+
* refuses one it cannot match with 422 `snapshot_mismatch`. */
|
|
124
|
+
snapshot?: SnapshotRef;
|
|
125
|
+
/** Whether this version addresses review feedback, when the host can tell. */
|
|
126
|
+
from_review?: "yes" | "no";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A composition-sync snapshot a review version carries, named in the snapshot's own workspace, which may differ from
|
|
130
|
+
* the review project's. Also the body of PUT …/versions/{version_id}/snapshot, which attaches one later. */
|
|
131
|
+
export interface SnapshotRef {
|
|
132
|
+
space_id: string;
|
|
133
|
+
composition_id: string;
|
|
134
|
+
snapshot_id: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A version is playable only after screening completes.
|
|
138
|
+
export interface ExportDTO {
|
|
139
|
+
video_id: string;
|
|
140
|
+
version_id: string;
|
|
141
|
+
status: "upload_pending" | "processing" | "screening" | "ready" | "failed";
|
|
142
|
+
exported_at: string;
|
|
143
|
+
upload: { put_url: string; headers: Record<string, string> } | null;
|
|
144
|
+
error: { reason: string; message: string } | null;
|
|
145
|
+
/** Owner reads only (absent from an older backend); status stays "screening" for both, null when not screening.
|
|
146
|
+
* "checking" is any screening not held for a person at HeyGen. */
|
|
147
|
+
screening_state?: "checking" | "held" | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ReviewEntitlements {
|
|
151
|
+
policy_version: 1;
|
|
152
|
+
workspace_id: string;
|
|
153
|
+
plan: "free" | "subscribed";
|
|
154
|
+
project_limit: 5 | null;
|
|
155
|
+
projects_used_up_to_limit: number | null;
|
|
156
|
+
can_register_project: boolean;
|
|
157
|
+
allowed_collaborative_access_modes: Array<"public" | "team" | "invite">;
|
|
158
|
+
can_manage_email_grants: boolean;
|
|
159
|
+
/** Whether the owner's plan can give Can edit; sent only while `share_files` is on, false on Free. */
|
|
160
|
+
can_offer_edit?: boolean;
|
|
161
|
+
}
|
|
162
|
+
interface ReviewSharingState {
|
|
163
|
+
state: "available" | "closed" | "subscription_required";
|
|
164
|
+
can_close_access: boolean;
|
|
165
|
+
can_create_email_grants?: boolean;
|
|
166
|
+
can_update_email_grants?: boolean;
|
|
167
|
+
can_rotate_link?: boolean;
|
|
168
|
+
can_create_link?: boolean;
|
|
169
|
+
can_revoke_link?: boolean;
|
|
170
|
+
allowed_collaborative_access_modes?: Array<"public" | "team" | "invite">;
|
|
171
|
+
allowed_default_roles?: LinkRole[];
|
|
172
|
+
can_enable_comments?: boolean;
|
|
173
|
+
}
|
|
174
|
+
export interface AccessGrantDTO {
|
|
175
|
+
effective?: boolean;
|
|
176
|
+
can_revoke?: boolean;
|
|
177
|
+
allowed_roles?: Array<"viewer" | "commenter" | "editor">;
|
|
178
|
+
id: string;
|
|
179
|
+
email: string;
|
|
180
|
+
role: "viewer" | "commenter" | "editor";
|
|
181
|
+
revision: number;
|
|
182
|
+
revoked_at: string | null;
|
|
183
|
+
}
|
|
184
|
+
export interface CreateGrantRequest {
|
|
185
|
+
client_operation_id: string;
|
|
186
|
+
email: string;
|
|
187
|
+
role: AccessGrantDTO["role"];
|
|
188
|
+
expected_revision: number | null;
|
|
189
|
+
}
|
|
190
|
+
export interface ManagedReviewPage {
|
|
191
|
+
items: Array<{
|
|
192
|
+
project_id: string;
|
|
193
|
+
title: string;
|
|
194
|
+
created_at: string;
|
|
195
|
+
owner_user_id: string;
|
|
196
|
+
can_remove: boolean;
|
|
197
|
+
}>;
|
|
198
|
+
next_cursor: string | null;
|
|
199
|
+
review_entitlements: ReviewEntitlements | null;
|
|
200
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Who a review link lets in, and as what, as the shared-reviews server names them. */
|
|
2
|
+
export type AccessMode = "public" | "invite" | "team" | "private";
|
|
3
|
+
export type LinkRole = "viewer" | "commenter" | "editor";
|
|
4
|
+
|
|
5
|
+
/** Whether the version on the page, or the one a press would add, carries the project's synced files. `included`: it
|
|
6
|
+
* does. `not_synced`: the render is the project as it is now, but that is not what was last synced. `changed`: the
|
|
7
|
+
* project moved past the render. `other_workspace`: the project syncs to `workspace`, not the one Desktop is signed
|
|
8
|
+
* into, whose sign-in can't read its snapshots. `off`: share_files or sync is off, or there is no render to speak of. */
|
|
9
|
+
export type ReviewFiles =
|
|
10
|
+
| { at: "off" }
|
|
11
|
+
| { at: "included" }
|
|
12
|
+
| { at: "not_synced" }
|
|
13
|
+
| { at: "changed" }
|
|
14
|
+
| { at: "other_workspace"; workspace: string };
|
|
15
|
+
export type ReviewFilesState = ReviewFiles["at"];
|
|
16
|
+
|
|
17
|
+
/** An upload under way: bytes sent of the archive's total (0 while the archive is made). */
|
|
18
|
+
export interface ShareProgress {
|
|
19
|
+
sent: number;
|
|
20
|
+
total: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A project's shared link, as the Share popover reads it. */
|
|
24
|
+
export interface ShareStatus {
|
|
25
|
+
/** Null until the first share; changedFiles counts the project's files changed since that link was made. */
|
|
26
|
+
link: { url: string; sharedAt: number; changedFiles: number } | null;
|
|
27
|
+
/** The project's files in bytes, and the most a shared project may be. */
|
|
28
|
+
bytes: number;
|
|
29
|
+
limit: number;
|
|
30
|
+
publishing: ShareProgress | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A project's review link, as the server holds it. */
|
|
34
|
+
export interface ReviewShare {
|
|
35
|
+
projectId?: string;
|
|
36
|
+
entitlements?: import("./reviewWire.js").ReviewEntitlements | null | undefined;
|
|
37
|
+
sharingState?: import("./reviewWire.js").ShareDTO["sharing_state"];
|
|
38
|
+
removed?: boolean;
|
|
39
|
+
/** Null while the link is off. */
|
|
40
|
+
url: string | null;
|
|
41
|
+
access: AccessMode;
|
|
42
|
+
role: LinkRole;
|
|
43
|
+
commentsOn: boolean;
|
|
44
|
+
/** The space the link belongs to; null from a server that does not say. */
|
|
45
|
+
space: ReviewSpaceRef | null;
|
|
46
|
+
/** The link's space is not the one Desktop is signed into. Everything still works there; this only names it. */
|
|
47
|
+
elsewhere: boolean;
|
|
48
|
+
/** A report paused access through the link: only the owner, invited people and workspace members get in. */
|
|
49
|
+
held: boolean;
|
|
50
|
+
/** Staff took the review down: only the owner can open it, at `page`, and it can't be shared again. */
|
|
51
|
+
takedown?: { page: string };
|
|
52
|
+
/** Where the page's versions stand; every answer over IPC carries it. */
|
|
53
|
+
version?: ReviewVersion;
|
|
54
|
+
/** After a change of space: the old link not yet turned off, and whether retries keep failing. */
|
|
55
|
+
oldLink?: { space: string; stuck: boolean } | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** An upload to the review page in flight. `checking` is the server's screening, which can take a while. */
|
|
59
|
+
export interface ReviewExportProgress {
|
|
60
|
+
step: "preparing" | "uploading" | "processing" | "checking";
|
|
61
|
+
sent: number;
|
|
62
|
+
total: number;
|
|
63
|
+
/** Checking for over 2 minutes: staff review can take a while. */
|
|
64
|
+
slow?: boolean;
|
|
65
|
+
/** How screening stands, from a server that says: held for a person at HeyGen (no bar, polled slowly, up to a day)
|
|
66
|
+
* or checking. Absent from an older server, which leaves `slow` to say a long check. */
|
|
67
|
+
screening?: "checking" | "held";
|
|
68
|
+
/** Checking for over 15 minutes since the upload completed, by this computer's clock. */
|
|
69
|
+
late?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ReviewVersion {
|
|
73
|
+
/** The newest version this app put on the page; `current` when it is the project as it is now. */
|
|
74
|
+
last: { exportedAt: string; current: boolean } | null;
|
|
75
|
+
/** The newest export: ready to go up, none, not MP4, or older than the project now. */
|
|
76
|
+
source: "ready" | "none" | "not-mp4" | "stale";
|
|
77
|
+
exporting: ReviewExportProgress | null;
|
|
78
|
+
/** The last run's sentence when it did not go up. */
|
|
79
|
+
failure: string | null;
|
|
80
|
+
/** False when the same video would be refused again (blocked, rejected, not available): no Try again. */
|
|
81
|
+
retryable?: boolean;
|
|
82
|
+
/** With `retryable` false: a smaller or shorter export can go up, so offer Export MP4. */
|
|
83
|
+
exportHelps?: boolean;
|
|
84
|
+
/** The watch ran out while the video was held for review: not a refusal, so it is never shown as one. */
|
|
85
|
+
held?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ReviewSpaceRef {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
kind: "personal" | "team";
|
|
92
|
+
/** Someone besides the link's owner (on a space list: besides the caller) is in the space; absent when the server
|
|
93
|
+
* does not say, which Share reads as yes. */
|
|
94
|
+
hasOtherMembers?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A space the account is in. v1 shares into the signed-in one; the later workspace switch lists the rest. */
|
|
98
|
+
export interface ReviewSpace extends ReviewSpaceRef {
|
|
99
|
+
/** Existing native registration verified by reading its owner share; a retry needs no new slot. */
|
|
100
|
+
existingProjectId?: string;
|
|
101
|
+
/** A durable registration request can be replayed; the server still decides admission. */
|
|
102
|
+
resumingRegistration?: boolean;
|
|
103
|
+
sharingState?: ReviewShare["sharingState"];
|
|
104
|
+
entitlements?: import("./reviewWire.js").ReviewEntitlements | null | undefined;
|
|
105
|
+
lastUsedAt: string | null;
|
|
106
|
+
canCreate: boolean;
|
|
107
|
+
/** The workspace Desktop is signed into, where a new link goes. */
|
|
108
|
+
signedIn: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type ReviewAccessChange = { access: ReviewShare["access"] } | { role: ReviewShare["role"] };
|
|
112
|
+
|
|
113
|
+
/** A share link as Settings' Cloud list shows it (packages/agent-runtime/src/share/reviewLinkStates.mjs). */
|
|
114
|
+
export interface ReviewLinkState {
|
|
115
|
+
state: "on" | "off" | "paused" | "taken-down";
|
|
116
|
+
access: import("./share.js").AccessMode;
|
|
117
|
+
/** Why a paused link is paused: a report HyperFrames looks into, or a plan that no longer includes it. */
|
|
118
|
+
pausedFor?: "report" | "plan";
|
|
119
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Open in Studio from a share page (docs/sync-backed-sharing/contracts.md C1-C3, C7): the server's answers, and what
|
|
2
|
+
// Desktop's choice dialog is told.
|
|
3
|
+
|
|
4
|
+
/** C1: `GET /v1/hyperframes/reviews/{token}/open?version_id=`. */
|
|
5
|
+
export interface OpenSummaryDTO {
|
|
6
|
+
/** `project_id` is the review project, which Work on {owner}'s project names on composition calls (C5). */
|
|
7
|
+
project: { project_id: string; title: string; owner_name: string; space_name: string | null };
|
|
8
|
+
version: {
|
|
9
|
+
version_id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
has_files: boolean;
|
|
12
|
+
files?: { count: number; bytes: number } | null;
|
|
13
|
+
};
|
|
14
|
+
fallback_version: { version_id: string; label: string; published_at: string } | null;
|
|
15
|
+
composition: {
|
|
16
|
+
space_id: string;
|
|
17
|
+
composition_id: string;
|
|
18
|
+
latest_snapshot_id: string;
|
|
19
|
+
newer_than_version: boolean;
|
|
20
|
+
} | null;
|
|
21
|
+
can_get: boolean;
|
|
22
|
+
plan_space_id: string | null;
|
|
23
|
+
plan_space_name: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One signed read URL in a sync download batch. */
|
|
27
|
+
export interface SignedBlobDTO {
|
|
28
|
+
sha256: string;
|
|
29
|
+
size: number;
|
|
30
|
+
url: string;
|
|
31
|
+
expires_at: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** C2: `GET /v1/hyperframes/reviews/{token}/versions/{version_id}/files`, the snapshot's manifest and the first URLs
|
|
35
|
+
* (at most 500 files). More, or fresh ones, come from `POST` on the same path with `{continuation_token, sha256s}`,
|
|
36
|
+
* which answers exactly the sync `DownloadUrlsResponse`. */
|
|
37
|
+
export interface VersionFilesDTO {
|
|
38
|
+
version_id: string;
|
|
39
|
+
snapshot_id: string;
|
|
40
|
+
manifest_sha256: string;
|
|
41
|
+
manifest: { document: unknown; file_count: number; total_bytes: number };
|
|
42
|
+
continuation_token: string;
|
|
43
|
+
continuation_expires_at: string;
|
|
44
|
+
urls: SignedBlobDTO[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** C2's refresh: the sync download batch for the same snapshot. */
|
|
48
|
+
export interface FileUrlsDTO {
|
|
49
|
+
snapshot_id: string;
|
|
50
|
+
manifest_sha256: string;
|
|
51
|
+
continuation_token: string;
|
|
52
|
+
continuation_expires_at: string;
|
|
53
|
+
urls: SignedBlobDTO[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The copy card: the version's files, the earlier version that has some, or nothing to copy. */
|
|
57
|
+
export type ShareCopyCard =
|
|
58
|
+
| { at: "ready"; versionId: string; label: string; files: number; bytes: number }
|
|
59
|
+
| {
|
|
60
|
+
at: "fallback";
|
|
61
|
+
label: string;
|
|
62
|
+
fallback: { versionId: string; label: string; publishedAt: string };
|
|
63
|
+
}
|
|
64
|
+
| { at: "none"; label: string };
|
|
65
|
+
|
|
66
|
+
/** The Work on {owner}'s project card (C7): `open` to Get the owner's latest sync (`newer` when it is newer than the
|
|
67
|
+
* version the page showed); `locked` when the signed-in workspace (`planSpace`) has no paid plan; `linked` when this
|
|
68
|
+
* Mac already has a folder bound to it, with its edits not sent yet (null when they could not be counted). */
|
|
69
|
+
export type ShareWorkCard =
|
|
70
|
+
| { at: "open"; newer: boolean }
|
|
71
|
+
| { at: "locked"; planSpace: string | null }
|
|
72
|
+
| { at: "linked"; unsent: number | null };
|
|
73
|
+
|
|
74
|
+
/** The one press a failed open offers beside Close; `none` when no press here can change the answer. */
|
|
75
|
+
export type ShareOpenOffer = "try_again" | "switch_account" | "sign_in" | "open_latest" | "none";
|
|
76
|
+
|
|
77
|
+
/** What the choice dialog shows for the share link main is opening; null when none is. */
|
|
78
|
+
export type ShareOpenView =
|
|
79
|
+
| { id: string; at: "loading" }
|
|
80
|
+
| { id: string; at: "failed"; sentence: string; offers: ShareOpenOffer }
|
|
81
|
+
| {
|
|
82
|
+
id: string;
|
|
83
|
+
at: "choose";
|
|
84
|
+
/** The review project (C1), kept for Work on {owner}'s project's composition calls. */
|
|
85
|
+
projectId: string;
|
|
86
|
+
title: string;
|
|
87
|
+
owner: string;
|
|
88
|
+
space: string | null;
|
|
89
|
+
versionLabel: string;
|
|
90
|
+
/** Null when the card is hidden: the project was never synced with files, or sync is off on this Mac. */
|
|
91
|
+
work: ShareWorkCard | null;
|
|
92
|
+
copy: ShareCopyCard;
|
|
93
|
+
}
|
|
94
|
+
| { id: string; at: "copying"; title: string; label: string; fraction: number | null }
|
|
95
|
+
/** Work on {owner}'s project is getting the owner's latest sync into a new linked folder. */
|
|
96
|
+
| { id: string; at: "linking"; title: string; owner: string };
|
package/package.json
CHANGED
|
@@ -1,6 +1,115 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/share",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.9.21",
|
|
4
|
+
"description": "Shared review identity, version upload and sharing state for HyperFrames hosts",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/heygen-com/hyperframes",
|
|
9
|
+
"directory": "packages/share"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"scripts/copy-types.mjs"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./src/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.mts"
|
|
22
|
+
},
|
|
23
|
+
"./hosts": "./dist/hosts.d.ts",
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
"./appOwnedPaths.mjs": {
|
|
26
|
+
"import": "./src/appOwnedPaths.mjs",
|
|
27
|
+
"types": "./dist/appOwnedPaths.d.mts"
|
|
28
|
+
},
|
|
29
|
+
"./grantRefusal.mjs": {
|
|
30
|
+
"import": "./src/grantRefusal.mjs",
|
|
31
|
+
"types": "./dist/grantRefusal.d.mts"
|
|
32
|
+
},
|
|
33
|
+
"./host.mjs": {
|
|
34
|
+
"import": "./src/host.mjs",
|
|
35
|
+
"types": "./dist/host.d.mts"
|
|
36
|
+
},
|
|
37
|
+
"./index.mjs": {
|
|
38
|
+
"import": "./src/index.mjs",
|
|
39
|
+
"types": "./dist/index.d.mts"
|
|
40
|
+
},
|
|
41
|
+
"./projectLock.mjs": {
|
|
42
|
+
"import": "./src/projectLock.mjs",
|
|
43
|
+
"types": "./dist/projectLock.d.mts"
|
|
44
|
+
},
|
|
45
|
+
"./projectSnapshot.mjs": {
|
|
46
|
+
"import": "./src/projectSnapshot.mjs",
|
|
47
|
+
"types": "./dist/projectSnapshot.d.mts"
|
|
48
|
+
},
|
|
49
|
+
"./reviewAudience.mjs": {
|
|
50
|
+
"import": "./src/reviewAudience.mjs",
|
|
51
|
+
"types": "./dist/reviewAudience.d.mts"
|
|
52
|
+
},
|
|
53
|
+
"./reviewClient.mjs": {
|
|
54
|
+
"import": "./src/reviewClient.mjs",
|
|
55
|
+
"types": "./dist/reviewClient.d.mts"
|
|
56
|
+
},
|
|
57
|
+
"./reviewExport.mjs": {
|
|
58
|
+
"import": "./src/reviewExport.mjs",
|
|
59
|
+
"types": "./dist/reviewExport.d.mts"
|
|
60
|
+
},
|
|
61
|
+
"./reviewFiles.mjs": {
|
|
62
|
+
"import": "./src/reviewFiles.mjs",
|
|
63
|
+
"types": "./dist/reviewFiles.d.mts"
|
|
64
|
+
},
|
|
65
|
+
"./reviewLinkStates.mjs": {
|
|
66
|
+
"import": "./src/reviewLinkStates.mjs",
|
|
67
|
+
"types": "./dist/reviewLinkStates.d.mts"
|
|
68
|
+
},
|
|
69
|
+
"./reviewOpenCalls.mjs": {
|
|
70
|
+
"import": "./src/reviewOpenCalls.mjs",
|
|
71
|
+
"types": "./dist/reviewOpenCalls.d.mts"
|
|
72
|
+
},
|
|
73
|
+
"./reviewShare.mjs": {
|
|
74
|
+
"import": "./src/reviewShare.mjs",
|
|
75
|
+
"types": "./dist/reviewShare.d.mts"
|
|
76
|
+
},
|
|
77
|
+
"./reviewSwitch.mjs": {
|
|
78
|
+
"import": "./src/reviewSwitch.mjs",
|
|
79
|
+
"types": "./dist/reviewSwitch.d.mts"
|
|
80
|
+
},
|
|
81
|
+
"./reviewUpload.mjs": {
|
|
82
|
+
"import": "./src/reviewUpload.mjs",
|
|
83
|
+
"types": "./dist/reviewUpload.d.mts"
|
|
84
|
+
},
|
|
85
|
+
"./reviewVersionSentences.mjs": {
|
|
86
|
+
"import": "./src/reviewVersionSentences.mjs",
|
|
87
|
+
"types": "./dist/reviewVersionSentences.d.mts"
|
|
88
|
+
},
|
|
89
|
+
"./signedInRequest.mjs": {
|
|
90
|
+
"import": "./src/signedInRequest.mjs",
|
|
91
|
+
"types": "./dist/signedInRequest.d.mts"
|
|
92
|
+
},
|
|
93
|
+
"./storage.mjs": {
|
|
94
|
+
"import": "./src/storage.mjs",
|
|
95
|
+
"types": "./dist/storage.d.mts"
|
|
96
|
+
},
|
|
97
|
+
"./types/access": "./dist/types/access.d.ts",
|
|
98
|
+
"./types/review": "./dist/types/review.d.ts",
|
|
99
|
+
"./types/reviewWire": "./dist/types/reviewWire.d.ts",
|
|
100
|
+
"./types/share": "./dist/types/share.d.ts",
|
|
101
|
+
"./types/shareOpen": "./dist/types/shareOpen.d.ts"
|
|
102
|
+
},
|
|
103
|
+
"publishConfig": {
|
|
104
|
+
"access": "public"
|
|
105
|
+
},
|
|
106
|
+
"devDependencies": {
|
|
107
|
+
"@types/node": "^25.0.10",
|
|
108
|
+
"typescript": "^5.0.0"
|
|
109
|
+
},
|
|
110
|
+
"scripts": {
|
|
111
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-types.mjs",
|
|
112
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
113
|
+
"test": "node --test tests/*.test.mjs"
|
|
114
|
+
}
|
|
6
115
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @param {string} path */
|
|
3
|
+
const pathParts = (path) => path.split(/[\\/]/);
|
|
4
|
+
export const APP_OWNED_DIRS = [
|
|
5
|
+
".thumbnails",
|
|
6
|
+
".hyperframes",
|
|
7
|
+
".claude",
|
|
8
|
+
"renders",
|
|
9
|
+
".transcode-cache",
|
|
10
|
+
".waveform-cache",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/** True for a project-relative path that lives under one of the app's own folders. @param {string} relativePath */
|
|
14
|
+
export const isAppOwned = (relativePath) =>
|
|
15
|
+
APP_OWNED_DIRS.includes(/** @type {string} */ (pathParts(relativePath)[0]));
|
|
16
|
+
|
|
17
|
+
/** A file the film can never load, so no change report counts it: the app's own folders, anything hidden
|
|
18
|
+
* (a dot file or folder: .git, .DS_Store), installed packages, and temporary editor or download files.
|
|
19
|
+
* @param {string} relativePath */
|
|
20
|
+
export function isOutsideFilm(relativePath) {
|
|
21
|
+
const parts = pathParts(relativePath);
|
|
22
|
+
return (
|
|
23
|
+
isAppOwned(relativePath) ||
|
|
24
|
+
parts.some((part) => part.startsWith(".") || part === "node_modules") ||
|
|
25
|
+
/(?:~|\.swp|\.tmp|\.part)$/.test(parts.at(-1) ?? "")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {{ read: (dir: string) => Promise<"yes" | "no" | null>,
|
|
4
|
+
* published: (dir: string, since: number) => Promise<void>, now: () => number }} FromReview */
|
|
5
|
+
|
|
6
|
+
/** @param {FromReview} host @param {string} dir */
|
|
7
|
+
export async function readFromReview(host, dir) {
|
|
8
|
+
const since = host.now();
|
|
9
|
+
try {
|
|
10
|
+
const value = await host.read(dir);
|
|
11
|
+
return { since, ...(value && { value }) };
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error("[share] review mark not read:", error);
|
|
14
|
+
return { since };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Clear activity through the start of the successful share, keeping anything recorded later.
|
|
19
|
+
* @param {FromReview | null} host @param {string} dir @param {number | undefined} since */
|
|
20
|
+
export async function sharedFromReview(host, dir, since) {
|
|
21
|
+
if (!host || since === undefined) return;
|
|
22
|
+
try {
|
|
23
|
+
await host.published(dir, since);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("[share] review mark not cleared:", error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const PREFIX = "ReviewGrantRefusal:v1:";
|
|
3
|
+
const REASONS = new Set(["revision_conflict", "grant_exists", "operation_result_revoked"]);
|
|
4
|
+
|
|
5
|
+
/** Definitive refusals are per-call data, not the project's mutable last-issue lookup.
|
|
6
|
+
* @param {unknown} error */
|
|
7
|
+
export function rejectedGrantRequest(error) {
|
|
8
|
+
return error instanceof Error && "reason" in error && REASONS.has(String(error.reason));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** An explicit wire protocol survives Electron's Error-only serialization and the browser bridge.
|
|
12
|
+
* @param {string} reason @param {string} message */
|
|
13
|
+
export function grantRefusalMessage(reason, message) {
|
|
14
|
+
return REASONS.has(reason) ? PREFIX + JSON.stringify({ reason, message }) : message;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @param {unknown} error */
|
|
18
|
+
export function decodeGrantRefusal(error) {
|
|
19
|
+
if (!(error instanceof Error) || !error.message.startsWith(PREFIX)) return error;
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(error.message.slice(PREFIX.length));
|
|
22
|
+
if (REASONS.has(data.reason) && typeof data.message === "string")
|
|
23
|
+
return Object.assign(new Error(data.message), { reason: data.reason });
|
|
24
|
+
} catch {
|
|
25
|
+
// An invalid wire payload remains an ambiguous failure and keeps its saved request.
|
|
26
|
+
}
|
|
27
|
+
return error;
|
|
28
|
+
}
|
package/src/host.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { promisify } from "node:util";
|
|
2
|
+
import { execFile as nodeExecFile } from "node:child_process";
|
|
3
|
+
import { signedInRequest as defaultRequest } from "./signedInRequest.mjs";
|
|
4
|
+
// @ts-check
|
|
5
|
+
/** @typedef {import("./hosts.js").ShareHost} ShareHost */
|
|
6
|
+
/** @type {Partial<ShareHost>} */
|
|
7
|
+
const host = { execFile: nodeExecFile, signedInRequest: defaultRequest };
|
|
8
|
+
/** Bind the host services used by the sharing runtime. @param {Partial<ShareHost>} part */
|
|
9
|
+
export const bindShareHost = (part) => void Object.assign(host, part);
|
|
10
|
+
/** @template {keyof ShareHost} K @param {K} name @returns {ShareHost[K]} */
|
|
11
|
+
const bound = (name) => {
|
|
12
|
+
const part = host[name];
|
|
13
|
+
if (part === undefined) throw new Error(`No host is bound to share projects (${name}).`);
|
|
14
|
+
return /** @type {ShareHost[K]} */ (part);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** @type {ShareHost["normalize"]} */
|
|
18
|
+
export const normalize = (path) => bound("normalize")(path);
|
|
19
|
+
/** @type {ShareHost["assertOpenProject"]} */
|
|
20
|
+
export const assertOpenProject = (path) => bound("assertOpenProject")(path);
|
|
21
|
+
/** @type {ShareHost["resolveOpenProjectByPath"]} */
|
|
22
|
+
export const resolveOpenProjectByPath = (path) => bound("resolveOpenProjectByPath")(path);
|
|
23
|
+
/** @type {ShareHost["listOpenProjects"]} */
|
|
24
|
+
export const listOpenProjects = () => bound("listOpenProjects")();
|
|
25
|
+
/** @type {ShareHost["homeName"]} */
|
|
26
|
+
export const homeName = (path, folder) => bound("homeName")(path, folder);
|
|
27
|
+
/** @type {ShareHost["uploadWithProgress"]} */
|
|
28
|
+
export const uploadWithProgress = (url, headers, archive, onSent) =>
|
|
29
|
+
bound("uploadWithProgress")(url, headers, archive, onSent);
|
|
30
|
+
/** @type {ShareHost["signedInRequest"]} */
|
|
31
|
+
export const signedInRequest = (deps) => bound("signedInRequest")(deps);
|
|
32
|
+
/** @type {ShareHost["latestExportFile"]} */
|
|
33
|
+
export const latestExportFile = (dir) => bound("latestExportFile")(dir);
|
|
34
|
+
export const reviewUploadOrigins = () =>
|
|
35
|
+
host.reviewUploadOrigins ??
|
|
36
|
+
(process.env.HYPERFRAMES_REVIEW_UPLOAD_ORIGINS ?? "").split(",").map((origin) => origin.trim());
|
|
37
|
+
/** @type {ShareHost["projectFingerprint"]} */
|
|
38
|
+
export const projectFingerprint = (dir) => bound("projectFingerprint")(dir);
|
|
39
|
+
/** @type {ShareHost["ffBinary"]} */
|
|
40
|
+
export const ffBinary = (name) => bound("ffBinary")(name);
|
|
41
|
+
export const channelSite = () => bound("channelSite");
|
|
42
|
+
export const signinSites = () => bound("signinSites");
|
|
43
|
+
|
|
44
|
+
/** @param {string} file @param {string[]} args */
|
|
45
|
+
export const runFile = (file, args) =>
|
|
46
|
+
promisify(bound("execFile"))(file, args, { windowsHide: true });
|