@workbench-kit/electron-shell 0.0.2-prototype.0.2.6 → 0.0.2-prototype.0.2.8
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/package.json +1 -1
- package/src/{root-confined-asset-protocol.ts → assets/root-confined-asset-protocol.ts} +162 -148
- package/src/index.ts +49 -49
- package/src/{encrypted-secret-vault.ts → secrets/encrypted-secret-vault.ts} +140 -123
- package/src/{open-allowlisted-external-link.ts → security/open-allowlisted-external-link.ts} +66 -66
- package/src/{require-owned-window-for-sender.ts → security/require-owned-window-for-sender.ts} +31 -31
- package/src/{wallpaper-crop.ts → wallpaper/wallpaper-crop.ts} +83 -83
- package/src/{window-controls.ts → window/window-controls.ts} +150 -150
package/package.json
CHANGED
|
@@ -1,148 +1,162 @@
|
|
|
1
|
-
export interface AssetCachePolicy {
|
|
2
|
-
/** Max age in ms; expired entries are treated as missing. */
|
|
3
|
-
readonly ttlMs: number;
|
|
4
|
-
/** Max bytes accepted for a single cached asset. */
|
|
5
|
-
readonly maxBytes: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface CachedAssetMeta {
|
|
9
|
-
readonly relativePath: string;
|
|
10
|
-
readonly contentType: string;
|
|
11
|
-
readonly fetchedAt: number;
|
|
12
|
-
readonly byteLength: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface AssetCacheStore {
|
|
16
|
-
readMeta(cacheKey: string): Promise<CachedAssetMeta | null>;
|
|
17
|
-
writeMeta(cacheKey: string, meta: CachedAssetMeta): Promise<void>;
|
|
18
|
-
readBytes(relativePath: string): Promise<Uint8Array | null>;
|
|
19
|
-
writeBytes(relativePath: string, bytes: Uint8Array): Promise<void>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FetchAllowlistedHttps {
|
|
23
|
-
(url: string): Promise<{ bytes: Uint8Array; contentType: string }>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface PrivilegedProtocolApi {
|
|
27
|
-
registerSchemesAsPrivileged?: (
|
|
28
|
-
schemes: ReadonlyArray<{ scheme: string; privileges: Record<string, boolean> }>,
|
|
29
|
-
) => void;
|
|
30
|
-
handle: (
|
|
31
|
-
scheme: string,
|
|
32
|
-
handler: (request: { url: string }) => Promise<{ data: Uint8Array; mimeType: string }>,
|
|
33
|
-
) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface PathRootHelpers {
|
|
37
|
-
/** Resolve a relative key under the cache root; must reject escapes. */
|
|
38
|
-
readonly resolveInsideRoot: (root: string, relativePath: string) => string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface RegisterRootConfinedAssetProtocolOptions extends PathRootHelpers {
|
|
42
|
-
readonly scheme: string;
|
|
43
|
-
readonly cacheRoot: string;
|
|
44
|
-
readonly protocol: PrivilegedProtocolApi;
|
|
45
|
-
readonly cache: AssetCacheStore;
|
|
46
|
-
readonly policy: AssetCachePolicy;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
options
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
await options.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
1
|
+
export interface AssetCachePolicy {
|
|
2
|
+
/** Max age in ms; expired entries are treated as missing. */
|
|
3
|
+
readonly ttlMs: number;
|
|
4
|
+
/** Max bytes accepted for a single cached asset. */
|
|
5
|
+
readonly maxBytes: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CachedAssetMeta {
|
|
9
|
+
readonly relativePath: string;
|
|
10
|
+
readonly contentType: string;
|
|
11
|
+
readonly fetchedAt: number;
|
|
12
|
+
readonly byteLength: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AssetCacheStore {
|
|
16
|
+
readMeta(cacheKey: string): Promise<CachedAssetMeta | null>;
|
|
17
|
+
writeMeta(cacheKey: string, meta: CachedAssetMeta): Promise<void>;
|
|
18
|
+
readBytes(relativePath: string): Promise<Uint8Array | null>;
|
|
19
|
+
writeBytes(relativePath: string, bytes: Uint8Array): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FetchAllowlistedHttps {
|
|
23
|
+
(url: string): Promise<{ bytes: Uint8Array; contentType: string }>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PrivilegedProtocolApi {
|
|
27
|
+
registerSchemesAsPrivileged?: (
|
|
28
|
+
schemes: ReadonlyArray<{ scheme: string; privileges: Record<string, boolean> }>,
|
|
29
|
+
) => void;
|
|
30
|
+
handle: (
|
|
31
|
+
scheme: string,
|
|
32
|
+
handler: (request: { url: string }) => Promise<{ data: Uint8Array; mimeType: string }>,
|
|
33
|
+
) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PathRootHelpers {
|
|
37
|
+
/** Resolve a relative key under the cache root; must reject escapes. */
|
|
38
|
+
readonly resolveInsideRoot: (root: string, relativePath: string) => string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RegisterRootConfinedAssetProtocolOptions extends PathRootHelpers {
|
|
42
|
+
readonly scheme: string;
|
|
43
|
+
readonly cacheRoot: string;
|
|
44
|
+
readonly protocol: PrivilegedProtocolApi;
|
|
45
|
+
readonly cache: AssetCacheStore;
|
|
46
|
+
readonly policy: AssetCachePolicy;
|
|
47
|
+
/** Enable CORS for the privileged scheme only when the host explicitly requires it. */
|
|
48
|
+
readonly corsEnabled?: boolean;
|
|
49
|
+
readonly now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function relativeAssetPath(cacheKey: string): string {
|
|
53
|
+
return `objects/${cacheKey}.bin`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cacheKeyFromProtocolUrl(requestUrl: string): string {
|
|
57
|
+
const url = new URL(requestUrl);
|
|
58
|
+
const fromHost = url.hostname.trim();
|
|
59
|
+
if (fromHost.length > 0) {
|
|
60
|
+
return decodeURIComponent(fromHost);
|
|
61
|
+
}
|
|
62
|
+
return decodeURIComponent(url.pathname.replace(/^\//, ''));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createPrivilegedSchemePrivileges(corsEnabled: boolean): Record<string, boolean> {
|
|
66
|
+
return {
|
|
67
|
+
standard: true,
|
|
68
|
+
secure: true,
|
|
69
|
+
supportFetchAPI: true,
|
|
70
|
+
corsEnabled,
|
|
71
|
+
stream: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Populate the root-confined cache from an allowlisted HTTPS response.
|
|
77
|
+
* Hosts own which URLs are fetched and how hash/TTL/size policy is chosen.
|
|
78
|
+
* Inject `resolveInsideRoot` from `@workbench-kit/platform/node` (or a test fake).
|
|
79
|
+
*/
|
|
80
|
+
export async function cacheAllowlistedHttpsAsset(
|
|
81
|
+
options: PathRootHelpers & {
|
|
82
|
+
readonly url: string;
|
|
83
|
+
readonly cacheRoot: string;
|
|
84
|
+
readonly cache: AssetCacheStore;
|
|
85
|
+
readonly policy: AssetCachePolicy;
|
|
86
|
+
readonly hashCacheKey: (url: string) => string;
|
|
87
|
+
readonly fetchHttps: FetchAllowlistedHttps;
|
|
88
|
+
readonly now?: () => number;
|
|
89
|
+
},
|
|
90
|
+
): Promise<CachedAssetMeta> {
|
|
91
|
+
const cacheKey = options.hashCacheKey(options.url);
|
|
92
|
+
const relativePath = relativeAssetPath(cacheKey);
|
|
93
|
+
options.resolveInsideRoot(options.cacheRoot, relativePath);
|
|
94
|
+
|
|
95
|
+
const response = await options.fetchHttps(options.url);
|
|
96
|
+
if (response.bytes.byteLength > options.policy.maxBytes) {
|
|
97
|
+
throw new Error('Cached asset exceeds the configured maxBytes limit.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await options.cache.writeBytes(relativePath, response.bytes);
|
|
101
|
+
const meta: CachedAssetMeta = {
|
|
102
|
+
relativePath,
|
|
103
|
+
contentType: response.contentType || 'application/octet-stream',
|
|
104
|
+
fetchedAt: (options.now ?? Date.now)(),
|
|
105
|
+
byteLength: response.bytes.byteLength,
|
|
106
|
+
};
|
|
107
|
+
await options.cache.writeMeta(cacheKey, meta);
|
|
108
|
+
return meta;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Register a privileged custom protocol that serves only files under `cacheRoot`.
|
|
113
|
+
* Unknown / expired cache keys reject; path escapes are rejected by `resolveInsideRoot`.
|
|
114
|
+
*/
|
|
115
|
+
export function registerRootConfinedAssetProtocol(
|
|
116
|
+
options: RegisterRootConfinedAssetProtocolOptions,
|
|
117
|
+
): void {
|
|
118
|
+
const {
|
|
119
|
+
scheme,
|
|
120
|
+
cacheRoot,
|
|
121
|
+
protocol,
|
|
122
|
+
cache,
|
|
123
|
+
policy,
|
|
124
|
+
resolveInsideRoot,
|
|
125
|
+
corsEnabled = false,
|
|
126
|
+
} = options;
|
|
127
|
+
const now = options.now ?? Date.now;
|
|
128
|
+
|
|
129
|
+
protocol.registerSchemesAsPrivileged?.([
|
|
130
|
+
{
|
|
131
|
+
scheme,
|
|
132
|
+
privileges: createPrivilegedSchemePrivileges(corsEnabled),
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
protocol.handle(scheme, async (request) => {
|
|
137
|
+
const cacheKey = cacheKeyFromProtocolUrl(request.url);
|
|
138
|
+
if (!cacheKey) {
|
|
139
|
+
throw new Error('Asset protocol request is missing a cache key.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const meta = await cache.readMeta(cacheKey);
|
|
143
|
+
if (meta === null) {
|
|
144
|
+
throw new Error('Cached asset is not present.');
|
|
145
|
+
}
|
|
146
|
+
if (now() - meta.fetchedAt > policy.ttlMs) {
|
|
147
|
+
throw new Error('Cached asset has expired.');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
resolveInsideRoot(cacheRoot, meta.relativePath);
|
|
151
|
+
|
|
152
|
+
const bytes = await cache.readBytes(meta.relativePath);
|
|
153
|
+
if (bytes === null) {
|
|
154
|
+
throw new Error('Cached asset bytes are missing.');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
data: bytes,
|
|
159
|
+
mimeType: meta.contentType,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
export {
|
|
2
|
-
createEncryptedSecretVault,
|
|
3
|
-
EncryptionUnavailableError,
|
|
4
|
-
type CreateEncryptedSecretVaultOptions,
|
|
5
|
-
type EncryptedSecretVault,
|
|
6
|
-
type SafeStorageCipher,
|
|
7
|
-
} from './encrypted-secret-vault.js';
|
|
8
|
-
export {
|
|
9
|
-
InvalidExternalLinkUrlError,
|
|
10
|
-
openAllowlistedExternalLink,
|
|
11
|
-
UnknownExternalLinkIdError,
|
|
12
|
-
type ExternalLinkAllowlist,
|
|
13
|
-
type OpenAllowlistedExternalLinkInput,
|
|
14
|
-
} from './open-allowlisted-external-link.js';
|
|
15
|
-
export {
|
|
16
|
-
requireOwnedWindowForSender,
|
|
17
|
-
UntrustedIpcSenderError,
|
|
18
|
-
type IpcSenderLike,
|
|
19
|
-
} from './require-owned-window-for-sender.js';
|
|
20
|
-
export {
|
|
21
|
-
cacheAllowlistedHttpsAsset,
|
|
22
|
-
registerRootConfinedAssetProtocol,
|
|
23
|
-
type AssetCachePolicy,
|
|
24
|
-
type AssetCacheStore,
|
|
25
|
-
type CachedAssetMeta,
|
|
26
|
-
type FetchAllowlistedHttps,
|
|
27
|
-
type PathRootHelpers,
|
|
28
|
-
type PrivilegedProtocolApi,
|
|
29
|
-
type RegisterRootConfinedAssetProtocolOptions,
|
|
30
|
-
} from './root-confined-asset-protocol.js';
|
|
31
|
-
export {
|
|
32
|
-
createWin32WallpaperPathResolver,
|
|
33
|
-
resolveWallpaperCropRect,
|
|
34
|
-
type RectLike,
|
|
35
|
-
type SizeLike,
|
|
36
|
-
type WallpaperPathResolver,
|
|
37
|
-
} from './wallpaper-crop.js';
|
|
38
|
-
export {
|
|
39
|
-
createWindowControlsBridge,
|
|
40
|
-
nextMaximizedState,
|
|
41
|
-
registerWindowControlIpc,
|
|
42
|
-
type CreateWindowControlsBridgeOptions,
|
|
43
|
-
type RegisterWindowControlIpcOptions,
|
|
44
|
-
type WindowControlIpcChannels,
|
|
45
|
-
type WindowControlIpcMain,
|
|
46
|
-
type WindowControlSurface,
|
|
47
|
-
type WindowControlWebContents,
|
|
48
|
-
type WindowControlsBridge,
|
|
49
|
-
} from './window-controls.js';
|
|
1
|
+
export {
|
|
2
|
+
createEncryptedSecretVault,
|
|
3
|
+
EncryptionUnavailableError,
|
|
4
|
+
type CreateEncryptedSecretVaultOptions,
|
|
5
|
+
type EncryptedSecretVault,
|
|
6
|
+
type SafeStorageCipher,
|
|
7
|
+
} from './secrets/encrypted-secret-vault.js';
|
|
8
|
+
export {
|
|
9
|
+
InvalidExternalLinkUrlError,
|
|
10
|
+
openAllowlistedExternalLink,
|
|
11
|
+
UnknownExternalLinkIdError,
|
|
12
|
+
type ExternalLinkAllowlist,
|
|
13
|
+
type OpenAllowlistedExternalLinkInput,
|
|
14
|
+
} from './security/open-allowlisted-external-link.js';
|
|
15
|
+
export {
|
|
16
|
+
requireOwnedWindowForSender,
|
|
17
|
+
UntrustedIpcSenderError,
|
|
18
|
+
type IpcSenderLike,
|
|
19
|
+
} from './security/require-owned-window-for-sender.js';
|
|
20
|
+
export {
|
|
21
|
+
cacheAllowlistedHttpsAsset,
|
|
22
|
+
registerRootConfinedAssetProtocol,
|
|
23
|
+
type AssetCachePolicy,
|
|
24
|
+
type AssetCacheStore,
|
|
25
|
+
type CachedAssetMeta,
|
|
26
|
+
type FetchAllowlistedHttps,
|
|
27
|
+
type PathRootHelpers,
|
|
28
|
+
type PrivilegedProtocolApi,
|
|
29
|
+
type RegisterRootConfinedAssetProtocolOptions,
|
|
30
|
+
} from './assets/root-confined-asset-protocol.js';
|
|
31
|
+
export {
|
|
32
|
+
createWin32WallpaperPathResolver,
|
|
33
|
+
resolveWallpaperCropRect,
|
|
34
|
+
type RectLike,
|
|
35
|
+
type SizeLike,
|
|
36
|
+
type WallpaperPathResolver,
|
|
37
|
+
} from './wallpaper/wallpaper-crop.js';
|
|
38
|
+
export {
|
|
39
|
+
createWindowControlsBridge,
|
|
40
|
+
nextMaximizedState,
|
|
41
|
+
registerWindowControlIpc,
|
|
42
|
+
type CreateWindowControlsBridgeOptions,
|
|
43
|
+
type RegisterWindowControlIpcOptions,
|
|
44
|
+
type WindowControlIpcChannels,
|
|
45
|
+
type WindowControlIpcMain,
|
|
46
|
+
type WindowControlSurface,
|
|
47
|
+
type WindowControlWebContents,
|
|
48
|
+
type WindowControlsBridge,
|
|
49
|
+
} from './window/window-controls.js';
|