@bardway/trpg-notebook-native 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +10 -0
- package/README.md +33 -2
- package/android/build.gradle +22 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/notebookstorage/NotebookStorageModule.kt +51 -0
- package/android/src/main/java/expo/modules/notebookstorage/NotebookStore.kt +650 -0
- package/app.plugin.cjs +69 -0
- package/dist/AppMarkdown.d.ts +10 -0
- package/dist/AppMarkdownSections.d.ts +23 -0
- package/dist/NotebookPage.d.ts +8 -0
- package/dist/NotebookReaderFrame.d.ts +14 -0
- package/dist/chunked-secure.d.ts +19 -0
- package/dist/chunked-secure.js +180 -0
- package/dist/chunked-secure.js.map +7 -0
- package/dist/importPicker.d.ts +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1094 -0
- package/dist/index.js.map +7 -0
- package/dist/reader/MarkdownDocumentView.d.ts +16 -0
- package/dist/reader/MarkdownView.d.ts +15 -0
- package/dist/reader/ReaderHost.d.ts +15 -0
- package/dist/reader/ReaderNativeConfig.d.ts +4 -0
- package/dist/storage-drivers.d.ts +28 -0
- package/dist/storage-drivers.js +35 -0
- package/dist/storage-drivers.js.map +7 -0
- package/dist/storage.d.ts +3 -0
- package/expo-module.config.json +4 -0
- package/native/TrpgMarkdownWebViewManager.kt +149 -0
- package/package.json +76 -4
package/app.plugin.cjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const {
|
|
4
|
+
withDangerousMod,
|
|
5
|
+
withMainApplication,
|
|
6
|
+
withAppBuildGradle,
|
|
7
|
+
} = require('@expo/config-plugins');
|
|
8
|
+
module.exports = (config, options = {}) => {
|
|
9
|
+
const fonts = options.fontResources || {};
|
|
10
|
+
for (const [key, value] of Object.entries(fonts)) {
|
|
11
|
+
if (
|
|
12
|
+
!/^(narrative-(400|700)|ui-(400|700)|data-400)\.ttf$/.test(key) ||
|
|
13
|
+
typeof value !== 'string' ||
|
|
14
|
+
!/^[_a-z][a-z0-9_]*$/.test(value)
|
|
15
|
+
)
|
|
16
|
+
throw new Error('Invalid reader font resource mapping');
|
|
17
|
+
}
|
|
18
|
+
config = withAppBuildGradle(config, (mod) => {
|
|
19
|
+
if (!mod.modResults.contents.includes('androidx.webkit:webkit:1.14.0'))
|
|
20
|
+
mod.modResults.contents = mod.modResults.contents.replace(
|
|
21
|
+
'dependencies {',
|
|
22
|
+
'dependencies {\n implementation("androidx.webkit:webkit:1.14.0")',
|
|
23
|
+
);
|
|
24
|
+
return mod;
|
|
25
|
+
});
|
|
26
|
+
config = withMainApplication(config, (mod) => {
|
|
27
|
+
const registration = 'add(org.bardway.trpg.reader.TrpgMarkdownWebViewPackage())';
|
|
28
|
+
if (!mod.modResults.contents.includes(registration)) {
|
|
29
|
+
const marker = 'PackageList(this).packages.apply {';
|
|
30
|
+
if (!mod.modResults.contents.includes(marker))
|
|
31
|
+
throw new Error('Unsupported MainApplication package registration');
|
|
32
|
+
mod.modResults.contents = mod.modResults.contents.replace(
|
|
33
|
+
marker,
|
|
34
|
+
marker + '\n ' + registration,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return mod;
|
|
38
|
+
});
|
|
39
|
+
return withDangerousMod(config, [
|
|
40
|
+
'android',
|
|
41
|
+
async (mod) => {
|
|
42
|
+
const project = mod.modRequest.platformProjectRoot;
|
|
43
|
+
const destination = path.join(project, 'app/src/main/java/org/bardway/trpg/reader');
|
|
44
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
45
|
+
fs.copyFileSync(
|
|
46
|
+
path.join(__dirname, 'native/TrpgMarkdownWebViewManager.kt'),
|
|
47
|
+
path.join(destination, 'TrpgMarkdownWebViewManager.kt'),
|
|
48
|
+
);
|
|
49
|
+
const pairs = Object.entries(fonts).map(
|
|
50
|
+
([key, value]) => JSON.stringify(key) + ' to ' + JSON.stringify(value),
|
|
51
|
+
);
|
|
52
|
+
fs.writeFileSync(
|
|
53
|
+
path.join(destination, 'TrpgReaderFontResources.kt'),
|
|
54
|
+
'package org.bardway.trpg.reader\ninternal object TrpgReaderFontResources { val fonts:Map<String,String> = mapOf(' +
|
|
55
|
+
pairs.join(',') +
|
|
56
|
+
') }\n',
|
|
57
|
+
);
|
|
58
|
+
const readerRoot = path.dirname(require.resolve('@bardway/trpg-markdown/package.json'));
|
|
59
|
+
const assetDir = path.join(project, 'app/src/main/assets/markdown-reader');
|
|
60
|
+
fs.mkdirSync(assetDir, { recursive: true });
|
|
61
|
+
for (const file of ['reader.js', 'reader.css', 'index.html'])
|
|
62
|
+
fs.copyFileSync(path.join(readerRoot, 'reader', file), path.join(assetDir, file));
|
|
63
|
+
const notices = path.join(readerRoot, 'reader/third-party-licenses');
|
|
64
|
+
if (fs.existsSync(notices))
|
|
65
|
+
fs.cpSync(notices, path.join(assetDir, 'third-party-licenses'), { recursive: true });
|
|
66
|
+
return mod;
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MarkdownVariant } from '@bardway/trpg-markdown/protocol';
|
|
3
|
+
interface AppMarkdownProps {
|
|
4
|
+
children: string;
|
|
5
|
+
variant?: MarkdownVariant;
|
|
6
|
+
documentKey?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Compatibility entry for a single body section inside a native scroll owner. */
|
|
9
|
+
export declare const AppMarkdown: React.NamedExoticComponent<AppMarkdownProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import type { MarkdownVariant } from '@bardway/trpg-markdown/protocol';
|
|
4
|
+
export interface AppMarkdownSection {
|
|
5
|
+
id: string;
|
|
6
|
+
markdown: string;
|
|
7
|
+
header?: React.ReactNode;
|
|
8
|
+
headerHeight?: number;
|
|
9
|
+
leading?: React.ReactNode;
|
|
10
|
+
variant?: MarkdownVariant;
|
|
11
|
+
fragments?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
interface AppMarkdownSectionsProps {
|
|
14
|
+
documentKey: string;
|
|
15
|
+
sections: readonly AppMarkdownSection[];
|
|
16
|
+
variant?: MarkdownVariant;
|
|
17
|
+
leadingWidth?: number;
|
|
18
|
+
columns?: number;
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
}
|
|
21
|
+
/** One reader per native panel, with native labels in measured reserved spaces. */
|
|
22
|
+
export declare const AppMarkdownSections: React.NamedExoticComponent<AppMarkdownSectionsProps>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type WorkspaceInput } from '@bardway/trpg-notebook/react';
|
|
3
|
+
export interface NotebookPageProps extends WorkspaceInput {
|
|
4
|
+
accountId: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const NotebookPage: React.NamedExoticComponent<NotebookPageProps>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type MarkdownDocumentViewProps } from './reader/MarkdownDocumentView.js';
|
|
3
|
+
interface NotebookReaderFrameProps {
|
|
4
|
+
scopeKey: string;
|
|
5
|
+
readerProps: MarkdownDocumentViewProps | null;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
active: boolean;
|
|
8
|
+
loadingColor: string;
|
|
9
|
+
loadingBackground: string;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/** A session keeps its one reader even while the next document is being read. */
|
|
13
|
+
export declare function NotebookReaderFrame({ scopeKey, readerProps, loading, active, loadingColor, loadingBackground, children, }: NotebookReaderFrameProps): React.JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { KeyValueDriver, AsyncKeyValueDriver } from './storage-drivers.js';
|
|
2
|
+
export declare class SupersededStorageOperationError extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
export interface ChunkedSecureStorageOptions {
|
|
6
|
+
logger: {
|
|
7
|
+
warn(...values: unknown[]): void;
|
|
8
|
+
};
|
|
9
|
+
now: () => number;
|
|
10
|
+
legacy: KeyValueDriver;
|
|
11
|
+
secure: AsyncKeyValueDriver;
|
|
12
|
+
legacyKeys?: (key: string) => readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/** SecureStore commits a manifest only after every chunk is written and read back. */
|
|
15
|
+
export declare const createChunkedSecureStorage: (options: ChunkedSecureStorageOptions) => {
|
|
16
|
+
getItem: (key: string, isCurrent?: () => boolean) => Promise<string | null>;
|
|
17
|
+
setItem: (key: string, value: string, isCurrent?: () => boolean) => Promise<void>;
|
|
18
|
+
removeItem: (key: string, isCurrent?: () => boolean) => Promise<void>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/chunked-secure.ts
|
|
2
|
+
var SupersededStorageOperationError = class extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("Storage operation superseded");
|
|
5
|
+
this.name = "SupersededStorageOperationError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var CHUNK_BYTE_LIMIT = 1800;
|
|
9
|
+
var MAX_AUTH_BYTES = 1048576;
|
|
10
|
+
var MAX_CHUNKS = 1024;
|
|
11
|
+
var manifestKey = (key) => `${key}.secure.v1`;
|
|
12
|
+
var chunkKey = (key, manifest, index) => `${key}.secure.${manifest.generation}.${index}`;
|
|
13
|
+
var parseManifest = (raw) => {
|
|
14
|
+
const value = JSON.parse(raw);
|
|
15
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !("version" in value) || value.version !== 1 || !("generation" in value) || typeof value.generation !== "string" || !/^[a-z0-9-]+$/i.test(value.generation) || !("chunks" in value) || typeof value.chunks !== "number" || !Number.isInteger(value.chunks) || value.chunks < 0 || value.chunks > MAX_CHUNKS || !("length" in value) || typeof value.length !== "number" || !Number.isInteger(value.length) || value.length < 0 || value.length > MAX_AUTH_BYTES || value.chunks === 0 && value.length !== 0) {
|
|
16
|
+
throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002");
|
|
17
|
+
}
|
|
18
|
+
return { version: 1, generation: value.generation, chunks: value.chunks, length: value.length };
|
|
19
|
+
};
|
|
20
|
+
var splitValue = (value) => {
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let chunk = "";
|
|
23
|
+
let chunkBytes = 0;
|
|
24
|
+
let totalBytes = 0;
|
|
25
|
+
for (const character of value) {
|
|
26
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
27
|
+
const bytes = codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
|
|
28
|
+
totalBytes += bytes;
|
|
29
|
+
if (totalBytes > MAX_AUTH_BYTES) throw new Error("\u767B\u5F55\u51ED\u636E\u8FC7\u5927\uFF0C\u65E0\u6CD5\u5B89\u5168\u4FDD\u5B58\u3002");
|
|
30
|
+
if (chunkBytes + bytes > CHUNK_BYTE_LIMIT) {
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
chunk = "";
|
|
33
|
+
chunkBytes = 0;
|
|
34
|
+
}
|
|
35
|
+
chunk += character;
|
|
36
|
+
chunkBytes += bytes;
|
|
37
|
+
}
|
|
38
|
+
chunks.push(chunk);
|
|
39
|
+
return chunks;
|
|
40
|
+
};
|
|
41
|
+
var createChunkedSecureStorage = (options) => {
|
|
42
|
+
const logger = options.logger;
|
|
43
|
+
const legacy = options.legacy;
|
|
44
|
+
const secure = options.secure;
|
|
45
|
+
let pending = Promise.resolve();
|
|
46
|
+
let sequence = 0;
|
|
47
|
+
const serialize = (action) => {
|
|
48
|
+
const operation = pending.then(action);
|
|
49
|
+
pending = operation.then(
|
|
50
|
+
() => void 0,
|
|
51
|
+
() => void 0
|
|
52
|
+
);
|
|
53
|
+
return operation;
|
|
54
|
+
};
|
|
55
|
+
const oldKeys = (key) => [key, ...options.legacyKeys?.(key) ?? []];
|
|
56
|
+
const clearLegacy = (key) => oldKeys(key).forEach((oldKey) => legacy.delete(oldKey));
|
|
57
|
+
const assertCurrent = (isCurrent) => {
|
|
58
|
+
if (!isCurrent()) throw new SupersededStorageOperationError();
|
|
59
|
+
};
|
|
60
|
+
const readManifest = async (key, isCurrent) => {
|
|
61
|
+
const raw = await secure.getString(manifestKey(key));
|
|
62
|
+
assertCurrent(isCurrent);
|
|
63
|
+
return raw === null ? null : parseManifest(raw);
|
|
64
|
+
};
|
|
65
|
+
const readPreviousManifest = async (key, isCurrent) => {
|
|
66
|
+
const raw = await secure.getString(manifestKey(key));
|
|
67
|
+
assertCurrent(isCurrent);
|
|
68
|
+
if (raw === null) return null;
|
|
69
|
+
try {
|
|
70
|
+
return parseManifest(raw);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const readChunks = async (key, manifest, isCurrent) => {
|
|
76
|
+
if (manifest.chunks === 0) return null;
|
|
77
|
+
const chunks = [];
|
|
78
|
+
for (let index = 0; index < manifest.chunks; index += 1) {
|
|
79
|
+
const chunk = await secure.getString(chunkKey(key, manifest, index));
|
|
80
|
+
assertCurrent(isCurrent);
|
|
81
|
+
if (chunk === null) throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002");
|
|
82
|
+
chunks.push(chunk);
|
|
83
|
+
}
|
|
84
|
+
const value = chunks.join("");
|
|
85
|
+
if (value.length !== manifest.length) throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002");
|
|
86
|
+
return value;
|
|
87
|
+
};
|
|
88
|
+
const removeChunks = async (key, manifest) => {
|
|
89
|
+
if (!manifest) return;
|
|
90
|
+
for (let index = 0; index < manifest.chunks; index += 1) {
|
|
91
|
+
await secure.delete(chunkKey(key, manifest, index));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const storeValue = async (key, value, previous, isCurrent) => {
|
|
95
|
+
const chunks = splitValue(value);
|
|
96
|
+
let generation = `${options.now().toString(36)}-${(++sequence).toString(36)}`;
|
|
97
|
+
if (generation === previous?.generation) generation += `-${(++sequence).toString(36)}`;
|
|
98
|
+
const manifest = {
|
|
99
|
+
version: 1,
|
|
100
|
+
generation,
|
|
101
|
+
chunks: chunks.length,
|
|
102
|
+
length: value.length
|
|
103
|
+
};
|
|
104
|
+
let committing = false;
|
|
105
|
+
try {
|
|
106
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
107
|
+
await secure.set(chunkKey(key, manifest, index), chunks[index]);
|
|
108
|
+
assertCurrent(isCurrent);
|
|
109
|
+
}
|
|
110
|
+
if (await readChunks(key, manifest, isCurrent) !== value)
|
|
111
|
+
throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u5199\u5165\u6821\u9A8C\u5931\u8D25\u3002");
|
|
112
|
+
assertCurrent(isCurrent);
|
|
113
|
+
committing = true;
|
|
114
|
+
await secure.set(manifestKey(key), JSON.stringify(manifest));
|
|
115
|
+
const committed = await readManifest(key, isCurrent);
|
|
116
|
+
if (!committed || committed.generation !== generation || await readChunks(key, committed, isCurrent) !== value) {
|
|
117
|
+
throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u5199\u5165\u6821\u9A8C\u5931\u8D25\u3002");
|
|
118
|
+
}
|
|
119
|
+
assertCurrent(isCurrent);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
try {
|
|
122
|
+
if (committing) {
|
|
123
|
+
if (previous) await secure.set(manifestKey(key), JSON.stringify(previous));
|
|
124
|
+
else await secure.delete(manifestKey(key));
|
|
125
|
+
}
|
|
126
|
+
await removeChunks(key, manifest);
|
|
127
|
+
} catch {
|
|
128
|
+
logger.warn("[AuthStorage] incomplete secure chunks could not be removed");
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
clearLegacy(key);
|
|
133
|
+
try {
|
|
134
|
+
await removeChunks(key, previous);
|
|
135
|
+
} catch {
|
|
136
|
+
logger.warn("[AuthStorage] superseded secure chunks could not be removed");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
getItem: (key, isCurrent = () => true) => serialize(async () => {
|
|
141
|
+
assertCurrent(isCurrent);
|
|
142
|
+
const manifest = await readManifest(key, isCurrent);
|
|
143
|
+
if (manifest) {
|
|
144
|
+
const value2 = await readChunks(key, manifest, isCurrent);
|
|
145
|
+
assertCurrent(isCurrent);
|
|
146
|
+
clearLegacy(key);
|
|
147
|
+
return value2;
|
|
148
|
+
}
|
|
149
|
+
const value = oldKeys(key).map((oldKey) => legacy.getString(oldKey)).find((entry) => entry !== void 0);
|
|
150
|
+
if (value === void 0) return null;
|
|
151
|
+
await storeValue(key, value, null, isCurrent);
|
|
152
|
+
return value;
|
|
153
|
+
}),
|
|
154
|
+
setItem: (key, value, isCurrent = () => true) => serialize(async () => {
|
|
155
|
+
assertCurrent(isCurrent);
|
|
156
|
+
await storeValue(key, value, await readPreviousManifest(key, isCurrent), isCurrent);
|
|
157
|
+
}),
|
|
158
|
+
removeItem: (key, isCurrent = () => true) => serialize(async () => {
|
|
159
|
+
assertCurrent(isCurrent);
|
|
160
|
+
const previous = await readPreviousManifest(key, isCurrent);
|
|
161
|
+
assertCurrent(isCurrent);
|
|
162
|
+
await secure.set(
|
|
163
|
+
manifestKey(key),
|
|
164
|
+
JSON.stringify({ version: 1, generation: "deleted", chunks: 0, length: 0 })
|
|
165
|
+
);
|
|
166
|
+
const tombstone = await readManifest(key, isCurrent);
|
|
167
|
+
if (tombstone?.chunks !== 0 || tombstone.generation !== "deleted") {
|
|
168
|
+
throw new Error("\u5B89\u5168\u767B\u5F55\u51ED\u636E\u6E05\u7406\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
|
|
169
|
+
}
|
|
170
|
+
assertCurrent(isCurrent);
|
|
171
|
+
clearLegacy(key);
|
|
172
|
+
await removeChunks(key, previous);
|
|
173
|
+
})
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
export {
|
|
177
|
+
SupersededStorageOperationError,
|
|
178
|
+
createChunkedSecureStorage
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=chunked-secure.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/chunked-secure.ts"],
|
|
4
|
+
"sourcesContent": ["import type { KeyValueDriver, AsyncKeyValueDriver } from './storage-drivers.js';\nexport class SupersededStorageOperationError extends Error {\n constructor() {\n super('Storage operation superseded');\n this.name = 'SupersededStorageOperationError';\n }\n}\n\nconst CHUNK_BYTE_LIMIT = 1800;\nconst MAX_AUTH_BYTES = 1_048_576;\nconst MAX_CHUNKS = 1024;\n\ninterface SecureAuthManifest {\n version: 1;\n generation: string;\n chunks: number;\n length: number;\n}\n\nexport interface ChunkedSecureStorageOptions {\n logger: { warn(...values: unknown[]): void };\n now: () => number;\n legacy: KeyValueDriver;\n secure: AsyncKeyValueDriver;\n legacyKeys?: (key: string) => readonly string[];\n}\n\nconst manifestKey = (key: string): string => `${key}.secure.v1`;\nconst chunkKey = (key: string, manifest: SecureAuthManifest, index: number): string =>\n `${key}.secure.${manifest.generation}.${index}`;\n\nconst parseManifest = (raw: string): SecureAuthManifest => {\n const value: unknown = JSON.parse(raw);\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n !('version' in value) ||\n value.version !== 1 ||\n !('generation' in value) ||\n typeof value.generation !== 'string' ||\n !/^[a-z0-9-]+$/i.test(value.generation) ||\n !('chunks' in value) ||\n typeof value.chunks !== 'number' ||\n !Number.isInteger(value.chunks) ||\n value.chunks < 0 ||\n value.chunks > MAX_CHUNKS ||\n !('length' in value) ||\n typeof value.length !== 'number' ||\n !Number.isInteger(value.length) ||\n value.length < 0 ||\n value.length > MAX_AUTH_BYTES ||\n (value.chunks === 0 && value.length !== 0)\n ) {\n throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002');\n }\n return { version: 1, generation: value.generation, chunks: value.chunks, length: value.length };\n};\n\nconst splitValue = (value: string): string[] => {\n const chunks: string[] = [];\n let chunk = '';\n let chunkBytes = 0;\n let totalBytes = 0;\n for (const character of value) {\n const codePoint = character.codePointAt(0) ?? 0;\n const bytes = codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;\n totalBytes += bytes;\n if (totalBytes > MAX_AUTH_BYTES) throw new Error('\u767B\u5F55\u51ED\u636E\u8FC7\u5927\uFF0C\u65E0\u6CD5\u5B89\u5168\u4FDD\u5B58\u3002');\n if (chunkBytes + bytes > CHUNK_BYTE_LIMIT) {\n chunks.push(chunk);\n chunk = '';\n chunkBytes = 0;\n }\n chunk += character;\n chunkBytes += bytes;\n }\n chunks.push(chunk);\n return chunks;\n};\n\n/** SecureStore commits a manifest only after every chunk is written and read back. */\nexport const createChunkedSecureStorage = (options: ChunkedSecureStorageOptions) => {\n const logger = options.logger;\n const legacy = options.legacy;\n const secure = options.secure;\n let pending: Promise<void> = Promise.resolve();\n let sequence = 0;\n\n const serialize = <T>(action: () => Promise<T>): Promise<T> => {\n const operation = pending.then(action);\n pending = operation.then(\n () => undefined,\n () => undefined,\n );\n return operation;\n };\n const oldKeys = (key: string): readonly string[] => [key, ...(options.legacyKeys?.(key) ?? [])];\n const clearLegacy = (key: string): void =>\n oldKeys(key).forEach((oldKey) => legacy.delete(oldKey));\n const assertCurrent = (isCurrent: () => boolean): void => {\n if (!isCurrent()) throw new SupersededStorageOperationError();\n };\n const readManifest = async (\n key: string,\n isCurrent: () => boolean,\n ): Promise<SecureAuthManifest | null> => {\n const raw = await secure.getString(manifestKey(key));\n assertCurrent(isCurrent);\n return raw === null ? null : parseManifest(raw);\n };\n const readPreviousManifest = async (\n key: string,\n isCurrent: () => boolean,\n ): Promise<SecureAuthManifest | null> => {\n const raw = await secure.getString(manifestKey(key));\n assertCurrent(isCurrent);\n if (raw === null) return null;\n try {\n return parseManifest(raw);\n } catch {\n return null;\n } // Login/logout may replace corrupt metadata; reads still reject it.\n };\n const readChunks = async (\n key: string,\n manifest: SecureAuthManifest,\n isCurrent: () => boolean,\n ): Promise<string | null> => {\n if (manifest.chunks === 0) return null;\n const chunks: string[] = [];\n for (let index = 0; index < manifest.chunks; index += 1) {\n const chunk = await secure.getString(chunkKey(key, manifest, index));\n assertCurrent(isCurrent);\n if (chunk === null) throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002');\n chunks.push(chunk);\n }\n const value = chunks.join('');\n if (value.length !== manifest.length) throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u3002');\n return value;\n };\n const removeChunks = async (key: string, manifest: SecureAuthManifest | null): Promise<void> => {\n if (!manifest) return;\n for (let index = 0; index < manifest.chunks; index += 1) {\n await secure.delete(chunkKey(key, manifest, index));\n }\n };\n const storeValue = async (\n key: string,\n value: string,\n previous: SecureAuthManifest | null,\n isCurrent: () => boolean,\n ): Promise<void> => {\n const chunks = splitValue(value);\n let generation = `${options.now().toString(36)}-${(++sequence).toString(36)}`;\n if (generation === previous?.generation) generation += `-${(++sequence).toString(36)}`;\n const manifest: SecureAuthManifest = {\n version: 1,\n generation,\n chunks: chunks.length,\n length: value.length,\n };\n let committing = false;\n try {\n for (let index = 0; index < chunks.length; index += 1) {\n await secure.set(chunkKey(key, manifest, index), chunks[index]);\n assertCurrent(isCurrent);\n }\n if ((await readChunks(key, manifest, isCurrent)) !== value)\n throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u5199\u5165\u6821\u9A8C\u5931\u8D25\u3002');\n assertCurrent(isCurrent);\n committing = true;\n await secure.set(manifestKey(key), JSON.stringify(manifest));\n const committed = await readManifest(key, isCurrent);\n if (\n !committed ||\n committed.generation !== generation ||\n (await readChunks(key, committed, isCurrent)) !== value\n ) {\n throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u5199\u5165\u6821\u9A8C\u5931\u8D25\u3002');\n }\n assertCurrent(isCurrent);\n } catch (error) {\n // A failed commit must leave the prior generation usable and retain plaintext migration input.\n try {\n if (committing) {\n if (previous) await secure.set(manifestKey(key), JSON.stringify(previous));\n else await secure.delete(manifestKey(key));\n }\n await removeChunks(key, manifest);\n } catch {\n logger.warn('[AuthStorage] incomplete secure chunks could not be removed');\n }\n throw error;\n }\n clearLegacy(key);\n try {\n await removeChunks(key, previous);\n } catch {\n logger.warn('[AuthStorage] superseded secure chunks could not be removed');\n }\n };\n\n return {\n getItem: (key: string, isCurrent: () => boolean = () => true): Promise<string | null> =>\n serialize(async () => {\n assertCurrent(isCurrent);\n const manifest = await readManifest(key, isCurrent);\n if (manifest) {\n const value = await readChunks(key, manifest, isCurrent);\n assertCurrent(isCurrent);\n clearLegacy(key);\n return value;\n }\n const value = oldKeys(key)\n .map((oldKey) => legacy.getString(oldKey))\n .find((entry) => entry !== undefined);\n if (value === undefined) return null;\n await storeValue(key, value, null, isCurrent);\n return value;\n }),\n setItem: (key: string, value: string, isCurrent: () => boolean = () => true): Promise<void> =>\n serialize(async () => {\n assertCurrent(isCurrent);\n await storeValue(key, value, await readPreviousManifest(key, isCurrent), isCurrent);\n }),\n removeItem: (key: string, isCurrent: () => boolean = () => true): Promise<void> =>\n serialize(async () => {\n assertCurrent(isCurrent);\n const previous = await readPreviousManifest(key, isCurrent);\n assertCurrent(isCurrent);\n // The tombstone also prevents an interrupted legacy cleanup from restoring an old session.\n await secure.set(\n manifestKey(key),\n JSON.stringify({ version: 1, generation: 'deleted', chunks: 0, length: 0 }),\n );\n const tombstone = await readManifest(key, isCurrent);\n if (tombstone?.chunks !== 0 || tombstone.generation !== 'deleted') {\n throw new Error('\u5B89\u5168\u767B\u5F55\u51ED\u636E\u6E05\u7406\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002');\n }\n assertCurrent(isCurrent);\n clearLegacy(key);\n await removeChunks(key, previous);\n }),\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["value"]
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './storage.js';
|
|
2
|
+
export * from './importPicker.js';
|
|
3
|
+
export * from './reader/ReaderHost.js';
|
|
4
|
+
export * from './reader/MarkdownView.js';
|
|
5
|
+
export * from './reader/MarkdownDocumentView.js';
|
|
6
|
+
export * from './NotebookReaderFrame.js';
|
|
7
|
+
export * from './NotebookPage.js';
|
|
8
|
+
export * from './AppMarkdown.js';
|
|
9
|
+
export * from './AppMarkdownSections.js';
|