@parlor/capabilities 0.0.0-stage → 0.2.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/README.md +15 -2
- package/dist/adapters/capacitor.d.ts +3 -0
- package/dist/adapters/capacitor.d.ts.map +1 -0
- package/dist/adapters/capacitor.js +24 -0
- package/dist/adapters/capacitor.js.map +1 -0
- package/dist/adapters/tauri.d.ts +3 -0
- package/dist/adapters/tauri.d.ts.map +1 -0
- package/dist/adapters/tauri.js +24 -0
- package/dist/adapters/tauri.js.map +1 -0
- package/dist/adapters/types.d.ts +18 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/web.d.ts +3 -0
- package/dist/adapters/web.d.ts.map +1 -0
- package/dist/adapters/web.js +17 -0
- package/dist/adapters/web.js.map +1 -0
- package/dist/auth-browser/capacitor.d.ts +15 -0
- package/dist/auth-browser/capacitor.d.ts.map +1 -0
- package/dist/auth-browser/capacitor.js +16 -0
- package/dist/auth-browser/capacitor.js.map +1 -0
- package/dist/auth-browser/index.d.ts +7 -0
- package/dist/auth-browser/index.d.ts.map +1 -0
- package/dist/auth-browser/index.js +10 -0
- package/dist/auth-browser/index.js.map +1 -0
- package/dist/auth-browser/interface.d.ts +9 -0
- package/dist/auth-browser/interface.d.ts.map +1 -0
- package/dist/auth-browser/interface.js +2 -0
- package/dist/auth-browser/interface.js.map +1 -0
- package/dist/auth-browser/scheme.d.ts +2 -0
- package/dist/auth-browser/scheme.d.ts.map +1 -0
- package/dist/auth-browser/scheme.js +4 -0
- package/dist/auth-browser/scheme.js.map +1 -0
- package/dist/auth-browser/tauri.d.ts +6 -0
- package/dist/auth-browser/tauri.d.ts.map +1 -0
- package/dist/auth-browser/tauri.js +20 -0
- package/dist/auth-browser/tauri.js.map +1 -0
- package/dist/auth-browser/web.d.ts +5 -0
- package/dist/auth-browser/web.d.ts.map +1 -0
- package/dist/auth-browser/web.js +15 -0
- package/dist/auth-browser/web.js.map +1 -0
- package/dist/capability.d.ts +21 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +21 -0
- package/dist/capability.js.map +1 -0
- package/dist/deep-link/capacitor.d.ts +16 -0
- package/dist/deep-link/capacitor.d.ts.map +1 -0
- package/dist/deep-link/capacitor.js +28 -0
- package/dist/deep-link/capacitor.js.map +1 -0
- package/dist/deep-link/index.d.ts +7 -0
- package/dist/deep-link/index.d.ts.map +1 -0
- package/dist/deep-link/index.js +10 -0
- package/dist/deep-link/index.js.map +1 -0
- package/dist/deep-link/interface.d.ts +5 -0
- package/dist/deep-link/interface.d.ts.map +1 -0
- package/dist/deep-link/interface.js +2 -0
- package/dist/deep-link/interface.js.map +1 -0
- package/dist/deep-link/tauri.d.ts +8 -0
- package/dist/deep-link/tauri.d.ts.map +1 -0
- package/dist/deep-link/tauri.js +28 -0
- package/dist/deep-link/tauri.js.map +1 -0
- package/dist/deep-link/web.d.ts +4 -0
- package/dist/deep-link/web.d.ts.map +1 -0
- package/dist/deep-link/web.js +20 -0
- package/dist/deep-link/web.js.map +1 -0
- package/dist/device-key/capacitor.d.ts +32 -0
- package/dist/device-key/capacitor.d.ts.map +1 -0
- package/dist/device-key/capacitor.js +9 -0
- package/dist/device-key/capacitor.js.map +1 -0
- package/dist/device-key/encoding.d.ts +12 -0
- package/dist/device-key/encoding.d.ts.map +1 -0
- package/dist/device-key/encoding.js +45 -0
- package/dist/device-key/encoding.js.map +1 -0
- package/dist/device-key/index.d.ts +8 -0
- package/dist/device-key/index.d.ts.map +1 -0
- package/dist/device-key/index.js +11 -0
- package/dist/device-key/index.js.map +1 -0
- package/dist/device-key/interface.d.ts +74 -0
- package/dist/device-key/interface.d.ts.map +1 -0
- package/dist/device-key/interface.js +7 -0
- package/dist/device-key/interface.js.map +1 -0
- package/dist/device-key/native.d.ts +38 -0
- package/dist/device-key/native.d.ts.map +1 -0
- package/dist/device-key/native.js +67 -0
- package/dist/device-key/native.js.map +1 -0
- package/dist/device-key/tauri.d.ts +8 -0
- package/dist/device-key/tauri.d.ts.map +1 -0
- package/dist/device-key/tauri.js +16 -0
- package/dist/device-key/tauri.js.map +1 -0
- package/dist/device-key/web.d.ts +11 -0
- package/dist/device-key/web.d.ts.map +1 -0
- package/dist/device-key/web.js +210 -0
- package/dist/device-key/web.js.map +1 -0
- package/dist/files/capacitor.d.ts +19 -0
- package/dist/files/capacitor.d.ts.map +1 -0
- package/dist/files/capacitor.js +47 -0
- package/dist/files/capacitor.js.map +1 -0
- package/dist/files/index.d.ts +7 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +10 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/interface.d.ts +29 -0
- package/dist/files/interface.d.ts.map +1 -0
- package/dist/files/interface.js +2 -0
- package/dist/files/interface.js.map +1 -0
- package/dist/files/mime.d.ts +11 -0
- package/dist/files/mime.d.ts.map +1 -0
- package/dist/files/mime.js +56 -0
- package/dist/files/mime.js.map +1 -0
- package/dist/files/tauri.d.ts +27 -0
- package/dist/files/tauri.d.ts.map +1 -0
- package/dist/files/tauri.js +46 -0
- package/dist/files/tauri.js.map +1 -0
- package/dist/files/web.d.ts +21 -0
- package/dist/files/web.d.ts.map +1 -0
- package/dist/files/web.js +82 -0
- package/dist/files/web.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/local-agent-host/capacitor.d.ts +5 -0
- package/dist/local-agent-host/capacitor.d.ts.map +1 -0
- package/dist/local-agent-host/capacitor.js +11 -0
- package/dist/local-agent-host/capacitor.js.map +1 -0
- package/dist/local-agent-host/index.d.ts +7 -0
- package/dist/local-agent-host/index.d.ts.map +1 -0
- package/dist/local-agent-host/index.js +10 -0
- package/dist/local-agent-host/index.js.map +1 -0
- package/dist/local-agent-host/interface.d.ts +62 -0
- package/dist/local-agent-host/interface.d.ts.map +1 -0
- package/dist/local-agent-host/interface.js +2 -0
- package/dist/local-agent-host/interface.js.map +1 -0
- package/dist/local-agent-host/tauri.d.ts +10 -0
- package/dist/local-agent-host/tauri.d.ts.map +1 -0
- package/dist/local-agent-host/tauri.js +38 -0
- package/dist/local-agent-host/tauri.js.map +1 -0
- package/dist/local-agent-host/web.d.ts +5 -0
- package/dist/local-agent-host/web.d.ts.map +1 -0
- package/dist/local-agent-host/web.js +11 -0
- package/dist/local-agent-host/web.js.map +1 -0
- package/dist/notifications/capacitor.d.ts +28 -0
- package/dist/notifications/capacitor.d.ts.map +1 -0
- package/dist/notifications/capacitor.js +39 -0
- package/dist/notifications/capacitor.js.map +1 -0
- package/dist/notifications/index.d.ts +7 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +10 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/interface.d.ts +14 -0
- package/dist/notifications/interface.d.ts.map +1 -0
- package/dist/notifications/interface.js +2 -0
- package/dist/notifications/interface.js.map +1 -0
- package/dist/notifications/tauri.d.ts +12 -0
- package/dist/notifications/tauri.d.ts.map +1 -0
- package/dist/notifications/tauri.js +28 -0
- package/dist/notifications/tauri.js.map +1 -0
- package/dist/notifications/web.d.ts +15 -0
- package/dist/notifications/web.d.ts.map +1 -0
- package/dist/notifications/web.js +23 -0
- package/dist/notifications/web.js.map +1 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +9 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-store/capacitor.d.ts +17 -0
- package/dist/session-store/capacitor.d.ts.map +1 -0
- package/dist/session-store/capacitor.js +21 -0
- package/dist/session-store/capacitor.js.map +1 -0
- package/dist/session-store/index.d.ts +8 -0
- package/dist/session-store/index.d.ts.map +1 -0
- package/dist/session-store/index.js +11 -0
- package/dist/session-store/index.js.map +1 -0
- package/dist/session-store/interface.d.ts +9 -0
- package/dist/session-store/interface.d.ts.map +1 -0
- package/dist/session-store/interface.js +2 -0
- package/dist/session-store/interface.js.map +1 -0
- package/dist/session-store/keychain.d.ts +13 -0
- package/dist/session-store/keychain.d.ts.map +1 -0
- package/dist/session-store/keychain.js +36 -0
- package/dist/session-store/keychain.js.map +1 -0
- package/dist/session-store/local.d.ts +10 -0
- package/dist/session-store/local.d.ts.map +1 -0
- package/dist/session-store/local.js +15 -0
- package/dist/session-store/local.js.map +1 -0
- package/dist/session-store/tauri.d.ts +10 -0
- package/dist/session-store/tauri.d.ts.map +1 -0
- package/dist/session-store/tauri.js +24 -0
- package/dist/session-store/tauri.js.map +1 -0
- package/dist/session-store/web.d.ts +5 -0
- package/dist/session-store/web.d.ts.map +1 -0
- package/dist/session-store/web.js +11 -0
- package/dist/session-store/web.js.map +1 -0
- package/package.json +95 -3
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { decodeBase64url, encodeBase64url, enrollmentBytes, publicPoint, } from "./encoding.js";
|
|
2
|
+
import { PresenceSetupCancelled, } from "./interface.js";
|
|
3
|
+
const hasPlatformAuthenticator = async () => {
|
|
4
|
+
try {
|
|
5
|
+
return ((await globalThis.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable?.()) ??
|
|
6
|
+
false);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const DATABASE = "parlor";
|
|
13
|
+
const STORE = "parlor-device";
|
|
14
|
+
const SIGNING = "signing";
|
|
15
|
+
const PRESENCE = "presence";
|
|
16
|
+
const CEREMONY_TIMEOUT_MS = 60_000;
|
|
17
|
+
const COSE_ES256 = -7;
|
|
18
|
+
const P256 = { name: "ECDSA", namedCurve: "P-256" };
|
|
19
|
+
const ES256 = { name: "ECDSA", hash: "SHA-256" };
|
|
20
|
+
function settled(request) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
request.onsuccess = () => resolve(request.result);
|
|
23
|
+
request.onerror = () => reject(request.error);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function openKeyStore(factory) {
|
|
27
|
+
const database = new Promise((resolve, reject) => {
|
|
28
|
+
const request = factory.open(DATABASE, 1);
|
|
29
|
+
request.onupgradeneeded = () => void request.result.createObjectStore(STORE);
|
|
30
|
+
request.onsuccess = () => resolve(request.result);
|
|
31
|
+
request.onerror = () => reject(request.error);
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
async read(key) {
|
|
35
|
+
const tx = (await database).transaction(STORE, "readonly");
|
|
36
|
+
return (await settled(tx.objectStore(STORE).get(key)));
|
|
37
|
+
},
|
|
38
|
+
async keepFirst(key, value) {
|
|
39
|
+
const tx = (await database).transaction(STORE, "readwrite");
|
|
40
|
+
const store = tx.objectStore(STORE);
|
|
41
|
+
const existing = (await settled(store.get(key)));
|
|
42
|
+
if (existing !== undefined)
|
|
43
|
+
return existing;
|
|
44
|
+
await settled(store.put(value, key));
|
|
45
|
+
return value;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const isPublicKeyCredential = (credential) => credential?.type === "public-key";
|
|
50
|
+
const isAttestation = (response) => "attestationObject" in response;
|
|
51
|
+
const isAssertion = (response) => "signature" in response;
|
|
52
|
+
const NO_PRESENCE = { unavailable: true };
|
|
53
|
+
function createWebDeviceKey(subtle, factory, credentials, platformAuthenticator) {
|
|
54
|
+
const store = openKeyStore(factory);
|
|
55
|
+
const thumbprint = async (key) => {
|
|
56
|
+
const { crv, kty, x, y } = key;
|
|
57
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify({ crv, kty, x, y })));
|
|
58
|
+
return encodeBase64url(new Uint8Array(digest));
|
|
59
|
+
};
|
|
60
|
+
const signingIdentity = async () => {
|
|
61
|
+
const stored = await store.read(SIGNING);
|
|
62
|
+
const pair = stored ??
|
|
63
|
+
(await store.keepFirst(SIGNING, await subtle.generateKey(P256, false, ["sign", "verify"])));
|
|
64
|
+
const publicKey = publicPoint(await subtle.exportKey("jwk", pair.publicKey));
|
|
65
|
+
return { privateKey: pair.privateKey, publicKey, keyId: await thumbprint(publicKey) };
|
|
66
|
+
};
|
|
67
|
+
const signRaw = async (payload) => {
|
|
68
|
+
const { privateKey, keyId } = await signingIdentity();
|
|
69
|
+
const signature = await subtle.sign(ES256, privateKey, new Uint8Array(payload));
|
|
70
|
+
return {
|
|
71
|
+
keyId,
|
|
72
|
+
presence: "none",
|
|
73
|
+
format: "es256-raw",
|
|
74
|
+
signature: encodeBase64url(new Uint8Array(signature)),
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const createPresence = async (container, enrollment) => {
|
|
78
|
+
try {
|
|
79
|
+
const credential = await container.create({
|
|
80
|
+
publicKey: {
|
|
81
|
+
rp: { id: enrollment.rpId, name: enrollment.rpId },
|
|
82
|
+
user: {
|
|
83
|
+
id: new TextEncoder().encode(enrollment.userId),
|
|
84
|
+
name: enrollment.userName,
|
|
85
|
+
displayName: enrollment.userName,
|
|
86
|
+
},
|
|
87
|
+
challenge: new Uint8Array(enrollment.challenge),
|
|
88
|
+
pubKeyCredParams: [{ type: "public-key", alg: COSE_ES256 }],
|
|
89
|
+
authenticatorSelection: {
|
|
90
|
+
authenticatorAttachment: "platform",
|
|
91
|
+
residentKey: "preferred",
|
|
92
|
+
userVerification: "required",
|
|
93
|
+
},
|
|
94
|
+
attestation: "none",
|
|
95
|
+
timeout: CEREMONY_TIMEOUT_MS,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (!isPublicKeyCredential(credential) || !isAttestation(credential.response)) {
|
|
99
|
+
return { unavailable: true };
|
|
100
|
+
}
|
|
101
|
+
const spki = credential.response.getPublicKey();
|
|
102
|
+
if (spki === null)
|
|
103
|
+
return { unavailable: true };
|
|
104
|
+
const key = await subtle.importKey("spki", spki, P256, true, ["verify"]);
|
|
105
|
+
return {
|
|
106
|
+
credentialId: encodeBase64url(new Uint8Array(credential.rawId)),
|
|
107
|
+
rpId: enrollment.rpId,
|
|
108
|
+
publicKey: publicPoint(await subtle.exportKey("jwk", key)),
|
|
109
|
+
attestation: encodeBase64url(new Uint8Array(credential.response.attestationObject)),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof Error && error.name === "NotAllowedError") {
|
|
114
|
+
throw new PresenceSetupCancelled();
|
|
115
|
+
}
|
|
116
|
+
return NO_PRESENCE;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const canCreatePresence = async () => credentials !== undefined && (await platformAuthenticator());
|
|
120
|
+
const presenceOf = async (enrollment, wanted) => {
|
|
121
|
+
const stored = (await store.read(PRESENCE)) ??
|
|
122
|
+
(await store.keepFirst(PRESENCE, credentials && wanted && (await canCreatePresence())
|
|
123
|
+
? await createPresence(credentials, enrollment)
|
|
124
|
+
: NO_PRESENCE));
|
|
125
|
+
if ("unavailable" in stored)
|
|
126
|
+
return undefined;
|
|
127
|
+
return {
|
|
128
|
+
format: "webauthn",
|
|
129
|
+
credentialId: stored.credentialId,
|
|
130
|
+
publicKey: stored.publicKey,
|
|
131
|
+
attestation: stored.attestation,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const signPresence = async (payload) => {
|
|
135
|
+
const stored = await store.read(PRESENCE);
|
|
136
|
+
if (!credentials || !stored || "unavailable" in stored) {
|
|
137
|
+
throw new Error("This device has no presence key.");
|
|
138
|
+
}
|
|
139
|
+
const challenge = new Uint8Array(await subtle.digest("SHA-256", new Uint8Array(payload)));
|
|
140
|
+
const assertion = await credentials.get({
|
|
141
|
+
publicKey: {
|
|
142
|
+
challenge,
|
|
143
|
+
rpId: stored.rpId,
|
|
144
|
+
allowCredentials: [
|
|
145
|
+
{ type: "public-key", id: decodeBase64url(stored.credentialId) },
|
|
146
|
+
],
|
|
147
|
+
userVerification: "required",
|
|
148
|
+
timeout: CEREMONY_TIMEOUT_MS,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
if (!isPublicKeyCredential(assertion) || !isAssertion(assertion.response)) {
|
|
152
|
+
throw new Error("The presence check did not complete.");
|
|
153
|
+
}
|
|
154
|
+
const { authenticatorData, clientDataJSON, signature } = assertion.response;
|
|
155
|
+
return {
|
|
156
|
+
keyId: stored.credentialId,
|
|
157
|
+
presence: "user",
|
|
158
|
+
format: "webauthn",
|
|
159
|
+
signature: encodeBase64url(new Uint8Array(signature)),
|
|
160
|
+
authenticatorData: encodeBase64url(new Uint8Array(authenticatorData)),
|
|
161
|
+
clientDataJSON: encodeBase64url(new Uint8Array(clientDataJSON)),
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
async function sign(payload, options) {
|
|
165
|
+
return options.presence ? signPresence(payload) : signRaw(payload);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
async publicKeys(enrollment, options) {
|
|
169
|
+
const { publicKey, keyId } = await signingIdentity();
|
|
170
|
+
const presence = await presenceOf(enrollment, options?.presence ?? true);
|
|
171
|
+
return {
|
|
172
|
+
signing: { keyId, publicKey },
|
|
173
|
+
presence,
|
|
174
|
+
proof: await signRaw(enrollmentBytes({
|
|
175
|
+
challenge: enrollment.challenge,
|
|
176
|
+
signing: publicKey,
|
|
177
|
+
presence,
|
|
178
|
+
})),
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
sign,
|
|
182
|
+
async presenceSetup() {
|
|
183
|
+
if (await store.read(PRESENCE))
|
|
184
|
+
return "done";
|
|
185
|
+
return (await canCreatePresence()) ? "needed" : "done";
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export function webDeviceKey(apis = () => ({
|
|
190
|
+
subtle: globalThis.crypto?.subtle,
|
|
191
|
+
indexedDB: globalThis.indexedDB,
|
|
192
|
+
credentials: globalThis.navigator?.credentials,
|
|
193
|
+
platformAuthenticator: hasPlatformAuthenticator,
|
|
194
|
+
})) {
|
|
195
|
+
return {
|
|
196
|
+
platform: "web",
|
|
197
|
+
available: () => {
|
|
198
|
+
const { subtle, indexedDB } = apis();
|
|
199
|
+
return subtle !== undefined && indexedDB !== undefined;
|
|
200
|
+
},
|
|
201
|
+
create: () => {
|
|
202
|
+
const { subtle, indexedDB, credentials, platformAuthenticator } = apis();
|
|
203
|
+
if (!subtle || !indexedDB) {
|
|
204
|
+
throw new Error("This browser cannot keep a device key.");
|
|
205
|
+
}
|
|
206
|
+
return createWebDeviceKey(subtle, indexedDB, credentials, platformAuthenticator ?? hasPlatformAuthenticator);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/device-key/web.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CapabilityAdapter } from \"../capability.js\";\nimport {\n decodeBase64url,\n encodeBase64url,\n enrollmentBytes,\n publicPoint,\n} from \"./encoding.js\";\nimport {\n type DeviceKey,\n type DevicePublicKeys,\n type DeviceSignature,\n type PresenceEnrollment,\n type PresencePublicKey,\n PresenceSetupCancelled,\n type PresenceSignature,\n} from \"./interface.js\";\n\nexport interface WebDeviceKeyApis {\n readonly subtle: SubtleCrypto | undefined;\n readonly indexedDB: IDBFactory | undefined;\n readonly credentials: CredentialsContainer | undefined;\n /** Whether a platform authenticator (Touch ID, Windows Hello) can make the passkey. */\n readonly platformAuthenticator?: (() => Promise<boolean>) | undefined;\n}\n\nconst hasPlatformAuthenticator = async (): Promise<boolean> => {\n try {\n return (\n (await globalThis.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable?.()) ??\n false\n );\n } catch {\n return false;\n }\n};\n\nconst DATABASE = \"parlor\";\nconst STORE = \"parlor-device\";\nconst SIGNING = \"signing\";\nconst PRESENCE = \"presence\";\nconst CEREMONY_TIMEOUT_MS = 60_000;\nconst COSE_ES256 = -7;\nconst P256 = { name: \"ECDSA\", namedCurve: \"P-256\" } as const;\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\n\ntype StoredPresence =\n | {\n readonly credentialId: string;\n readonly rpId: string;\n readonly publicKey: JsonWebKey;\n readonly attestation: string;\n }\n | { readonly unavailable: true };\n\ninterface KeyStore {\n read<T>(key: string): Promise<T | undefined>;\n keepFirst<T>(key: string, value: T): Promise<T>;\n}\n\nfunction settled<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nfunction openKeyStore(factory: IDBFactory): KeyStore {\n const database = new Promise<IDBDatabase>((resolve, reject) => {\n const request = factory.open(DATABASE, 1);\n request.onupgradeneeded = () => void request.result.createObjectStore(STORE);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n return {\n async read<T>(key: string) {\n const tx = (await database).transaction(STORE, \"readonly\");\n return (await settled(tx.objectStore(STORE).get(key))) as T | undefined;\n },\n async keepFirst<T>(key: string, value: T) {\n const tx = (await database).transaction(STORE, \"readwrite\");\n const store = tx.objectStore(STORE);\n const existing = (await settled(store.get(key))) as T | undefined;\n if (existing !== undefined) return existing;\n await settled(store.put(value, key));\n return value;\n },\n };\n}\n\nconst isPublicKeyCredential = (\n credential: Credential | null,\n): credential is PublicKeyCredential => credential?.type === \"public-key\";\n\nconst isAttestation = (\n response: AuthenticatorResponse,\n): response is AuthenticatorAttestationResponse => \"attestationObject\" in response;\n\nconst isAssertion = (\n response: AuthenticatorResponse,\n): response is AuthenticatorAssertionResponse => \"signature\" in response;\n\nconst NO_PRESENCE: StoredPresence = { unavailable: true };\n\nfunction createWebDeviceKey(\n subtle: SubtleCrypto,\n factory: IDBFactory,\n credentials: CredentialsContainer | undefined,\n platformAuthenticator: () => Promise<boolean>,\n): DeviceKey {\n const store = openKeyStore(factory);\n\n const thumbprint = async (key: JsonWebKey) => {\n const { crv, kty, x, y } = key;\n const digest = await subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(JSON.stringify({ crv, kty, x, y })),\n );\n return encodeBase64url(new Uint8Array(digest));\n };\n\n const signingIdentity = async () => {\n const stored = await store.read<CryptoKeyPair>(SIGNING);\n const pair =\n stored ??\n (await store.keepFirst<CryptoKeyPair>(\n SIGNING,\n await subtle.generateKey(P256, false, [\"sign\", \"verify\"]),\n ));\n const publicKey = publicPoint(await subtle.exportKey(\"jwk\", pair.publicKey));\n return { privateKey: pair.privateKey, publicKey, keyId: await thumbprint(publicKey) };\n };\n\n const signRaw = async (payload: Uint8Array): Promise<DeviceSignature> => {\n const { privateKey, keyId } = await signingIdentity();\n const signature = await subtle.sign(ES256, privateKey, new Uint8Array(payload));\n return {\n keyId,\n presence: \"none\",\n format: \"es256-raw\",\n signature: encodeBase64url(new Uint8Array(signature)),\n };\n };\n\n const createPresence = async (\n container: CredentialsContainer,\n enrollment: PresenceEnrollment,\n ): Promise<StoredPresence> => {\n try {\n const credential = await container.create({\n publicKey: {\n rp: { id: enrollment.rpId, name: enrollment.rpId },\n user: {\n id: new TextEncoder().encode(enrollment.userId),\n name: enrollment.userName,\n displayName: enrollment.userName,\n },\n challenge: new Uint8Array(enrollment.challenge),\n pubKeyCredParams: [{ type: \"public-key\", alg: COSE_ES256 }],\n authenticatorSelection: {\n authenticatorAttachment: \"platform\",\n residentKey: \"preferred\",\n userVerification: \"required\",\n },\n attestation: \"none\",\n timeout: CEREMONY_TIMEOUT_MS,\n },\n });\n if (!isPublicKeyCredential(credential) || !isAttestation(credential.response)) {\n return { unavailable: true };\n }\n const spki = credential.response.getPublicKey();\n if (spki === null) return { unavailable: true };\n const key = await subtle.importKey(\"spki\", spki, P256, true, [\"verify\"]);\n return {\n credentialId: encodeBase64url(new Uint8Array(credential.rawId)),\n rpId: enrollment.rpId,\n publicKey: publicPoint(await subtle.exportKey(\"jwk\", key)),\n attestation: encodeBase64url(\n new Uint8Array(credential.response.attestationObject),\n ),\n };\n } catch (error) {\n if (error instanceof Error && error.name === \"NotAllowedError\") {\n throw new PresenceSetupCancelled();\n }\n return NO_PRESENCE;\n }\n };\n\n const canCreatePresence = async () =>\n credentials !== undefined && (await platformAuthenticator());\n\n const presenceOf = async (\n enrollment: PresenceEnrollment,\n wanted: boolean,\n ): Promise<PresencePublicKey | undefined> => {\n const stored =\n (await store.read<StoredPresence>(PRESENCE)) ??\n (await store.keepFirst(\n PRESENCE,\n credentials && wanted && (await canCreatePresence())\n ? await createPresence(credentials, enrollment)\n : NO_PRESENCE,\n ));\n if (\"unavailable\" in stored) return undefined;\n return {\n format: \"webauthn\",\n credentialId: stored.credentialId,\n publicKey: stored.publicKey,\n attestation: stored.attestation,\n };\n };\n\n const signPresence = async (payload: Uint8Array): Promise<PresenceSignature> => {\n const stored = await store.read<StoredPresence>(PRESENCE);\n if (!credentials || !stored || \"unavailable\" in stored) {\n throw new Error(\"This device has no presence key.\");\n }\n const challenge = new Uint8Array(\n await subtle.digest(\"SHA-256\", new Uint8Array(payload)),\n );\n const assertion = await credentials.get({\n publicKey: {\n challenge,\n rpId: stored.rpId,\n allowCredentials: [\n { type: \"public-key\", id: decodeBase64url(stored.credentialId) },\n ],\n userVerification: \"required\",\n timeout: CEREMONY_TIMEOUT_MS,\n },\n });\n if (!isPublicKeyCredential(assertion) || !isAssertion(assertion.response)) {\n throw new Error(\"The presence check did not complete.\");\n }\n const { authenticatorData, clientDataJSON, signature } = assertion.response;\n return {\n keyId: stored.credentialId,\n presence: \"user\",\n format: \"webauthn\",\n signature: encodeBase64url(new Uint8Array(signature)),\n authenticatorData: encodeBase64url(new Uint8Array(authenticatorData)),\n clientDataJSON: encodeBase64url(new Uint8Array(clientDataJSON)),\n } as PresenceSignature;\n };\n\n async function sign(\n payload: Uint8Array,\n options: { readonly presence: false },\n ): Promise<DeviceSignature>;\n async function sign(\n payload: Uint8Array,\n options: { readonly presence: true },\n ): Promise<PresenceSignature>;\n async function sign(payload: Uint8Array, options: { readonly presence: boolean }) {\n return options.presence ? signPresence(payload) : signRaw(payload);\n }\n\n return {\n async publicKeys(enrollment, options): Promise<DevicePublicKeys> {\n const { publicKey, keyId } = await signingIdentity();\n const presence = await presenceOf(enrollment, options?.presence ?? true);\n return {\n signing: { keyId, publicKey },\n presence,\n proof: await signRaw(\n enrollmentBytes({\n challenge: enrollment.challenge,\n signing: publicKey,\n presence,\n }),\n ),\n };\n },\n sign,\n async presenceSetup() {\n if (await store.read<StoredPresence>(PRESENCE)) return \"done\";\n return (await canCreatePresence()) ? \"needed\" : \"done\";\n },\n };\n}\n\nexport function webDeviceKey(\n apis: () => WebDeviceKeyApis = () => ({\n subtle: globalThis.crypto?.subtle,\n indexedDB: globalThis.indexedDB,\n credentials: globalThis.navigator?.credentials,\n platformAuthenticator: hasPlatformAuthenticator,\n }),\n): CapabilityAdapter<DeviceKey> {\n return {\n platform: \"web\",\n available: () => {\n const { subtle, indexedDB } = apis();\n return subtle !== undefined && indexedDB !== undefined;\n },\n create: () => {\n const { subtle, indexedDB, credentials, platformAuthenticator } = apis();\n if (!subtle || !indexedDB) {\n throw new Error(\"This browser cannot keep a device key.\");\n }\n return createWebDeviceKey(\n subtle,\n indexedDB,\n credentials,\n platformAuthenticator ?? hasPlatformAuthenticator,\n );\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CapabilityAdapter } from "../capability.js";
|
|
2
|
+
import type { Files } from "./interface.js";
|
|
3
|
+
/** `@capacitor/filesystem`, narrowed to writing base64 into the app's cache. */
|
|
4
|
+
export interface CapacitorCacheWriter {
|
|
5
|
+
writeToCache(path: string, base64: string): Promise<{
|
|
6
|
+
uri: string;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export interface CapacitorShareModule {
|
|
10
|
+
Share: {
|
|
11
|
+
share(options: {
|
|
12
|
+
title?: string;
|
|
13
|
+
files: string[];
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Picks with the webview's own file input; saves through the share sheet ("Save to Files"). */
|
|
18
|
+
export declare function capacitorFiles(loadCache?: () => Promise<CapacitorCacheWriter>, loadShare?: () => Promise<CapacitorShareModule>, getDocument?: () => Document | undefined): CapabilityAdapter<Files>;
|
|
19
|
+
//# sourceMappingURL=capacitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capacitor.d.ts","sourceRoot":"","sources":["../../src/files/capacitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAI5C,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAUD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE;QAAE,KAAK,CAAC,OAAO,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,EAAE,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CAClF;AAcD,gGAAgG;AAChG,wBAAgB,cAAc,CAC5B,SAAS,GAAE,MAAM,OAAO,CAAC,oBAAoB,CAAmB,EAChE,SAAS,GAAE,MAAM,OAAO,CAAC,oBAAoB,CAAoC,EACjF,WAAW,GAAE,MAAM,QAAQ,GAAG,SAAqC,GAClE,iBAAiB,CAAC,KAAK,CAAC,CA0B1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { detectPlatform } from "../runtime.js";
|
|
2
|
+
import { bytesOf } from "./mime.js";
|
|
3
|
+
import { pickWithInput } from "./web.js";
|
|
4
|
+
const filesystemCache = async () => {
|
|
5
|
+
const { Filesystem, Directory } = await import("@capacitor/filesystem");
|
|
6
|
+
return {
|
|
7
|
+
writeToCache: (path, data) => Filesystem.writeFile({ path, data, directory: Directory.Cache, recursive: true }),
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
function base64Of(bytes) {
|
|
11
|
+
let binary = "";
|
|
12
|
+
for (let at = 0; at < bytes.length; at += 0x8000) {
|
|
13
|
+
binary += String.fromCharCode(...bytes.subarray(at, at + 0x8000));
|
|
14
|
+
}
|
|
15
|
+
return btoa(binary);
|
|
16
|
+
}
|
|
17
|
+
/** The share sheet reports a dismissal as an error whose message says it was canceled. */
|
|
18
|
+
const wasCancelled = (error) => error instanceof Error && /cancel/i.test(error.message);
|
|
19
|
+
/** Picks with the webview's own file input; saves through the share sheet ("Save to Files"). */
|
|
20
|
+
export function capacitorFiles(loadCache = filesystemCache, loadShare = () => import("@capacitor/share"), getDocument = () => globalThis.document) {
|
|
21
|
+
return {
|
|
22
|
+
platform: "capacitor",
|
|
23
|
+
available: () => detectPlatform() === "capacitor" && getDocument() !== undefined,
|
|
24
|
+
create: async () => {
|
|
25
|
+
const document = getDocument();
|
|
26
|
+
if (!document)
|
|
27
|
+
throw new Error("Files need a document");
|
|
28
|
+
const [cache, { Share }] = await Promise.all([loadCache(), loadShare()]);
|
|
29
|
+
return {
|
|
30
|
+
pick: (options) => pickWithInput(document, options),
|
|
31
|
+
save: async (data, options) => {
|
|
32
|
+
const { uri } = await cache.writeToCache(`parlor-save/${options.name}`, base64Of(await bytesOf(data)));
|
|
33
|
+
try {
|
|
34
|
+
await Share.share({ title: options.name, files: [uri] });
|
|
35
|
+
return "saved";
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (wasCancelled(error))
|
|
39
|
+
return "cancelled";
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=capacitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capacitor.js","sourceRoot":"","sources":["../../src/files/capacitor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAOzC,MAAM,eAAe,GAAG,KAAK,IAAmC,EAAE;IAChE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IACxE,OAAO;QACL,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAC3B,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KACpF,CAAC;AACJ,CAAC,CAAC;AAMF,SAAS,QAAQ,CAAC,KAAiB;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,MAAM,EAAE,CAAC;QACjD,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,0FAA0F;AAC1F,MAAM,YAAY,GAAG,CAAC,KAAc,EAAE,EAAE,CACtC,KAAK,YAAY,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAE1D,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAC5B,SAAS,GAAwC,eAAe,EAChE,SAAS,GAAwC,GAAG,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,EACjF,WAAW,GAA+B,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ;IAEnE,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE,KAAK,WAAW,IAAI,WAAW,EAAE,KAAK,SAAS;QAChF,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;YACxD,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YACzE,OAAO;gBACL,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC;gBACnD,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;oBAC5B,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,KAAK,CAAC,YAAY,CACtC,eAAe,OAAO,CAAC,IAAI,EAAE,EAC7B,QAAQ,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAC9B,CAAC;oBACF,IAAI,CAAC;wBACH,MAAM,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;wBACzD,OAAO,OAAO,CAAC;oBACjB,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,IAAI,YAAY,CAAC,KAAK,CAAC;4BAAE,OAAO,WAAW,CAAC;wBAC5C,MAAM,KAAK,CAAC;oBACd,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { CapabilityAdapter } from \"../capability.js\";\nimport { detectPlatform } from \"../runtime.js\";\nimport type { Files } from \"./interface.js\";\nimport { bytesOf } from \"./mime.js\";\nimport { pickWithInput } from \"./web.js\";\n\n/** `@capacitor/filesystem`, narrowed to writing base64 into the app's cache. */\nexport interface CapacitorCacheWriter {\n writeToCache(path: string, base64: string): Promise<{ uri: string }>;\n}\n\nconst filesystemCache = async (): Promise<CapacitorCacheWriter> => {\n const { Filesystem, Directory } = await import(\"@capacitor/filesystem\");\n return {\n writeToCache: (path, data) =>\n Filesystem.writeFile({ path, data, directory: Directory.Cache, recursive: true }),\n };\n};\n\nexport interface CapacitorShareModule {\n Share: { share(options: { title?: string; files: string[] }): Promise<unknown> };\n}\n\nfunction base64Of(bytes: Uint8Array): string {\n let binary = \"\";\n for (let at = 0; at < bytes.length; at += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(at, at + 0x8000));\n }\n return btoa(binary);\n}\n\n/** The share sheet reports a dismissal as an error whose message says it was canceled. */\nconst wasCancelled = (error: unknown) =>\n error instanceof Error && /cancel/i.test(error.message);\n\n/** Picks with the webview's own file input; saves through the share sheet (\"Save to Files\"). */\nexport function capacitorFiles(\n loadCache: () => Promise<CapacitorCacheWriter> = filesystemCache,\n loadShare: () => Promise<CapacitorShareModule> = () => import(\"@capacitor/share\"),\n getDocument: () => Document | undefined = () => globalThis.document,\n): CapabilityAdapter<Files> {\n return {\n platform: \"capacitor\",\n available: () => detectPlatform() === \"capacitor\" && getDocument() !== undefined,\n create: async () => {\n const document = getDocument();\n if (!document) throw new Error(\"Files need a document\");\n const [cache, { Share }] = await Promise.all([loadCache(), loadShare()]);\n return {\n pick: (options) => pickWithInput(document, options),\n save: async (data, options) => {\n const { uri } = await cache.writeToCache(\n `parlor-save/${options.name}`,\n base64Of(await bytesOf(data)),\n );\n try {\n await Share.share({ title: options.name, files: [uri] });\n return \"saved\";\n } catch (error) {\n if (wasCancelled(error)) return \"cancelled\";\n throw error;\n }\n },\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Files } from "./interface.js";
|
|
2
|
+
export type { Files, PickedFile, PickOptions, SaveOptions, SaveOutcome, } from "./interface.js";
|
|
3
|
+
export { capacitorFiles, type CapacitorCacheWriter, type CapacitorShareModule, } from "./capacitor.js";
|
|
4
|
+
export { tauriFiles, type TauriDialogModule, type TauriFsModule } from "./tauri.js";
|
|
5
|
+
export { webFiles, type WebFilesApis } from "./web.js";
|
|
6
|
+
export declare const files: import("../capability.js").CapabilityDefinition<Files>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,YAAY,EACV,KAAK,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvD,eAAO,MAAM,KAAK,wDAGhB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { platformAdapters } from "#adapters";
|
|
2
|
+
import { defineCapability } from "../capability.js";
|
|
3
|
+
export { capacitorFiles, } from "./capacitor.js";
|
|
4
|
+
export { tauriFiles } from "./tauri.js";
|
|
5
|
+
export { webFiles } from "./web.js";
|
|
6
|
+
export const files = defineCapability({
|
|
7
|
+
name: "files",
|
|
8
|
+
adapters: platformAdapters.files,
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAUpD,OAAO,EACL,cAAc,GAGf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAA8C,MAAM,YAAY,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAqB,MAAM,UAAU,CAAC;AAEvD,MAAM,CAAC,MAAM,KAAK,GAAG,gBAAgB,CAAQ;IAC3C,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,gBAAgB,CAAC,KAAK;CACjC,CAAC,CAAC","sourcesContent":["import { platformAdapters } from \"#adapters\";\n\nimport { defineCapability } from \"../capability.js\";\nimport type { Files } from \"./interface.js\";\n\nexport type {\n Files,\n PickedFile,\n PickOptions,\n SaveOptions,\n SaveOutcome,\n} from \"./interface.js\";\nexport {\n capacitorFiles,\n type CapacitorCacheWriter,\n type CapacitorShareModule,\n} from \"./capacitor.js\";\nexport { tauriFiles, type TauriDialogModule, type TauriFsModule } from \"./tauri.js\";\nexport { webFiles, type WebFilesApis } from \"./web.js\";\n\nexport const files = defineCapability<Files>({\n name: \"files\",\n adapters: platformAdapters.files,\n});\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface PickOptions {
|
|
2
|
+
/** What may be chosen, as an `<input accept>` takes it: `["image/*", ".pdf"]`. */
|
|
3
|
+
readonly accept?: readonly string[] | undefined;
|
|
4
|
+
readonly multiple?: boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface PickedFile {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
/** The MIME type, or `""` when the platform cannot tell. */
|
|
9
|
+
readonly type: string;
|
|
10
|
+
readonly size: number;
|
|
11
|
+
bytes(): Promise<Uint8Array>;
|
|
12
|
+
text(): Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
export interface SaveOptions {
|
|
15
|
+
/** The file name to suggest. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly type?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export type SaveOutcome = "saved" | "cancelled";
|
|
20
|
+
export interface Files {
|
|
21
|
+
/** Asks the user for files; an empty list when they cancel. */
|
|
22
|
+
pick(options?: PickOptions): Promise<readonly PickedFile[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Asks the user where to keep `data`: a save dialog where there is one, a download in a
|
|
25
|
+
* browser without, the share sheet on a phone.
|
|
26
|
+
*/
|
|
27
|
+
save(data: Blob | Uint8Array | string, options: SaveOptions): Promise<SaveOutcome>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/files/interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhD,MAAM,WAAW,KAAK;IACpB,+DAA+D;IAC/D,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;IAC5D;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,GAAG,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACpF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/files/interface.ts"],"names":[],"mappings":"","sourcesContent":["export interface PickOptions {\n /** What may be chosen, as an `<input accept>` takes it: `[\"image/*\", \".pdf\"]`. */\n readonly accept?: readonly string[] | undefined;\n readonly multiple?: boolean | undefined;\n}\n\nexport interface PickedFile {\n readonly name: string;\n /** The MIME type, or `\"\"` when the platform cannot tell. */\n readonly type: string;\n readonly size: number;\n bytes(): Promise<Uint8Array>;\n text(): Promise<string>;\n}\n\nexport interface SaveOptions {\n /** The file name to suggest. */\n readonly name: string;\n readonly type?: string | undefined;\n}\n\nexport type SaveOutcome = \"saved\" | \"cancelled\";\n\nexport interface Files {\n /** Asks the user for files; an empty list when they cancel. */\n pick(options?: PickOptions): Promise<readonly PickedFile[]>;\n /**\n * Asks the user where to keep `data`: a save dialog where there is one, a download in a\n * browser without, the share sheet on a phone.\n */\n save(data: Blob | Uint8Array | string, options: SaveOptions): Promise<SaveOutcome>;\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A file's MIME type from its extension; `""` when unknown. */
|
|
2
|
+
export declare function typeOf(name: string): string;
|
|
3
|
+
/** The last segment of a path on any platform. */
|
|
4
|
+
export declare function nameOf(path: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* `accept` as file-dialog extensions, or undefined when some entry has no extension to
|
|
7
|
+
* name, so a dialog never hides a file the app would take.
|
|
8
|
+
*/
|
|
9
|
+
export declare function extensionsOf(accept: readonly string[] | undefined): string[] | undefined;
|
|
10
|
+
export declare function bytesOf(data: Blob | Uint8Array | string): Promise<Uint8Array>;
|
|
11
|
+
//# sourceMappingURL=mime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/files/mime.ts"],"names":[],"mappings":"AAqBA,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,kDAAkD;AAClD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GACpC,MAAM,EAAE,GAAG,SAAS,CAetB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAInF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const TYPES = {
|
|
2
|
+
csv: "text/csv",
|
|
3
|
+
gif: "image/gif",
|
|
4
|
+
heic: "image/heic",
|
|
5
|
+
html: "text/html",
|
|
6
|
+
jpeg: "image/jpeg",
|
|
7
|
+
jpg: "image/jpeg",
|
|
8
|
+
json: "application/json",
|
|
9
|
+
md: "text/markdown",
|
|
10
|
+
mp3: "audio/mpeg",
|
|
11
|
+
mp4: "video/mp4",
|
|
12
|
+
pdf: "application/pdf",
|
|
13
|
+
png: "image/png",
|
|
14
|
+
svg: "image/svg+xml",
|
|
15
|
+
txt: "text/plain",
|
|
16
|
+
webp: "image/webp",
|
|
17
|
+
zip: "application/zip",
|
|
18
|
+
};
|
|
19
|
+
const extensionOf = (name) => name.split(".").pop()?.toLowerCase() ?? "";
|
|
20
|
+
/** A file's MIME type from its extension; `""` when unknown. */
|
|
21
|
+
export function typeOf(name) {
|
|
22
|
+
return TYPES[extensionOf(name)] ?? "";
|
|
23
|
+
}
|
|
24
|
+
/** The last segment of a path on any platform. */
|
|
25
|
+
export function nameOf(path) {
|
|
26
|
+
return path.split(/[/\\]/).pop() ?? path;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `accept` as file-dialog extensions, or undefined when some entry has no extension to
|
|
30
|
+
* name, so a dialog never hides a file the app would take.
|
|
31
|
+
*/
|
|
32
|
+
export function extensionsOf(accept) {
|
|
33
|
+
if (!accept?.length)
|
|
34
|
+
return undefined;
|
|
35
|
+
const extensions = new Set();
|
|
36
|
+
for (const entry of accept.map((item) => item.trim().toLowerCase())) {
|
|
37
|
+
if (entry.startsWith(".")) {
|
|
38
|
+
extensions.add(entry.slice(1));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const known = Object.entries(TYPES).filter(([, type]) => entry.endsWith("/*") ? type.startsWith(entry.slice(0, -1)) : type === entry);
|
|
42
|
+
if (known.length === 0)
|
|
43
|
+
return undefined;
|
|
44
|
+
for (const [extension] of known)
|
|
45
|
+
extensions.add(extension);
|
|
46
|
+
}
|
|
47
|
+
return [...extensions];
|
|
48
|
+
}
|
|
49
|
+
export async function bytesOf(data) {
|
|
50
|
+
if (typeof data === "string")
|
|
51
|
+
return new TextEncoder().encode(data);
|
|
52
|
+
if (data instanceof Uint8Array)
|
|
53
|
+
return data;
|
|
54
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=mime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/files/mime.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAqC;IAC9C,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,kBAAkB;IACxB,EAAE,EAAE,eAAe;IACnB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAEjF,gEAAgE;AAChE,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AACxC,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAqC;IAErC,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,SAAS,CAAC;IACtC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QACpE,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CACtD,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAC5E,CAAC;QACF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACzC,KAAK,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK;YAAE,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAgC;IAC5D,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,IAAI,YAAY,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AAClD,CAAC","sourcesContent":["const TYPES: Readonly<Record<string, string>> = {\n csv: \"text/csv\",\n gif: \"image/gif\",\n heic: \"image/heic\",\n html: \"text/html\",\n jpeg: \"image/jpeg\",\n jpg: \"image/jpeg\",\n json: \"application/json\",\n md: \"text/markdown\",\n mp3: \"audio/mpeg\",\n mp4: \"video/mp4\",\n pdf: \"application/pdf\",\n png: \"image/png\",\n svg: \"image/svg+xml\",\n txt: \"text/plain\",\n webp: \"image/webp\",\n zip: \"application/zip\",\n};\n\nconst extensionOf = (name: string) => name.split(\".\").pop()?.toLowerCase() ?? \"\";\n\n/** A file's MIME type from its extension; `\"\"` when unknown. */\nexport function typeOf(name: string): string {\n return TYPES[extensionOf(name)] ?? \"\";\n}\n\n/** The last segment of a path on any platform. */\nexport function nameOf(path: string): string {\n return path.split(/[/\\\\]/).pop() ?? path;\n}\n\n/**\n * `accept` as file-dialog extensions, or undefined when some entry has no extension to\n * name, so a dialog never hides a file the app would take.\n */\nexport function extensionsOf(\n accept: readonly string[] | undefined,\n): string[] | undefined {\n if (!accept?.length) return undefined;\n const extensions = new Set<string>();\n for (const entry of accept.map((item) => item.trim().toLowerCase())) {\n if (entry.startsWith(\".\")) {\n extensions.add(entry.slice(1));\n continue;\n }\n const known = Object.entries(TYPES).filter(([, type]) =>\n entry.endsWith(\"/*\") ? type.startsWith(entry.slice(0, -1)) : type === entry,\n );\n if (known.length === 0) return undefined;\n for (const [extension] of known) extensions.add(extension);\n }\n return [...extensions];\n}\n\nexport async function bytesOf(data: Blob | Uint8Array | string): Promise<Uint8Array> {\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n if (data instanceof Uint8Array) return data;\n return new Uint8Array(await data.arrayBuffer());\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CapabilityAdapter } from "../capability.js";
|
|
2
|
+
import type { Files } from "./interface.js";
|
|
3
|
+
type Filters = {
|
|
4
|
+
name: string;
|
|
5
|
+
extensions: string[];
|
|
6
|
+
}[];
|
|
7
|
+
export interface TauriDialogModule {
|
|
8
|
+
open(options: {
|
|
9
|
+
multiple: boolean;
|
|
10
|
+
directory: false;
|
|
11
|
+
filters?: Filters;
|
|
12
|
+
}): Promise<string | string[] | null>;
|
|
13
|
+
save(options: {
|
|
14
|
+
defaultPath: string;
|
|
15
|
+
filters?: Filters;
|
|
16
|
+
}): Promise<string | null>;
|
|
17
|
+
}
|
|
18
|
+
export interface TauriFsModule {
|
|
19
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
20
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
21
|
+
stat(path: string): Promise<{
|
|
22
|
+
size: number;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function tauriFiles(loadDialog?: () => Promise<TauriDialogModule>, loadFs?: () => Promise<TauriFsModule>): CapabilityAdapter<Files>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=tauri.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tauri.d.ts","sourceRoot":"","sources":["../../src/files/tauri.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,gBAAgB,CAAC;AAGxD,KAAK,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,OAAO,EAAE;QACZ,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,KAAK,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAKD,wBAAgB,UAAU,CACxB,UAAU,GAAE,MAAM,OAAO,CAAC,iBAAiB,CACN,EACrC,MAAM,GAAE,MAAM,OAAO,CAAC,aAAa,CAAyC,GAC3E,iBAAiB,CAAC,KAAK,CAAC,CA0C1B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { detectPlatform } from "../runtime.js";
|
|
2
|
+
import { bytesOf, extensionsOf, nameOf, typeOf } from "./mime.js";
|
|
3
|
+
const filtersFor = (extensions) => extensions ? [{ name: "Files", extensions }] : undefined;
|
|
4
|
+
export function tauriFiles(loadDialog = () => import("@tauri-apps/plugin-dialog"), loadFs = () => import("@tauri-apps/plugin-fs")) {
|
|
5
|
+
return {
|
|
6
|
+
platform: "tauri",
|
|
7
|
+
available: () => detectPlatform() === "tauri",
|
|
8
|
+
create: async () => {
|
|
9
|
+
const [dialog, fs] = await Promise.all([loadDialog(), loadFs()]);
|
|
10
|
+
const pickedAt = async (path) => {
|
|
11
|
+
const name = nameOf(path);
|
|
12
|
+
return {
|
|
13
|
+
name,
|
|
14
|
+
type: typeOf(name),
|
|
15
|
+
size: (await fs.stat(path)).size,
|
|
16
|
+
bytes: () => fs.readFile(path),
|
|
17
|
+
text: async () => new TextDecoder().decode(await fs.readFile(path)),
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
pick: async (options = {}) => {
|
|
22
|
+
const filters = filtersFor(extensionsOf(options.accept));
|
|
23
|
+
const chosen = await dialog.open({
|
|
24
|
+
multiple: options.multiple === true,
|
|
25
|
+
directory: false,
|
|
26
|
+
...(filters ? { filters } : {}),
|
|
27
|
+
});
|
|
28
|
+
const paths = chosen === null ? [] : Array.isArray(chosen) ? chosen : [chosen];
|
|
29
|
+
return Promise.all(paths.map(pickedAt));
|
|
30
|
+
},
|
|
31
|
+
save: async (data, options) => {
|
|
32
|
+
const filters = filtersFor(extensionsOf(options.type ? [options.type] : undefined));
|
|
33
|
+
const path = await dialog.save({
|
|
34
|
+
defaultPath: options.name,
|
|
35
|
+
...(filters ? { filters } : {}),
|
|
36
|
+
});
|
|
37
|
+
if (path === null)
|
|
38
|
+
return "cancelled";
|
|
39
|
+
await fs.writeFile(path, await bytesOf(data));
|
|
40
|
+
return "saved";
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=tauri.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tauri.js","sourceRoot":"","sources":["../../src/files/tauri.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CapabilityAdapter } from \"../capability.js\";\nimport { detectPlatform } from \"../runtime.js\";\nimport type { Files, PickedFile } from \"./interface.js\";\nimport { bytesOf, extensionsOf, nameOf, typeOf } from \"./mime.js\";\n\ntype Filters = { name: string; extensions: string[] }[];\n\nexport interface TauriDialogModule {\n open(options: {\n multiple: boolean;\n directory: false;\n filters?: Filters;\n }): Promise<string | string[] | null>;\n save(options: { defaultPath: string; filters?: Filters }): Promise<string | null>;\n}\n\nexport interface TauriFsModule {\n readFile(path: string): Promise<Uint8Array>;\n writeFile(path: string, data: Uint8Array): Promise<void>;\n stat(path: string): Promise<{ size: number }>;\n}\n\nconst filtersFor = (extensions: string[] | undefined): Filters | undefined =>\n extensions ? [{ name: \"Files\", extensions }] : undefined;\n\nexport function tauriFiles(\n loadDialog: () => Promise<TauriDialogModule> = () =>\n import(\"@tauri-apps/plugin-dialog\"),\n loadFs: () => Promise<TauriFsModule> = () => import(\"@tauri-apps/plugin-fs\"),\n): CapabilityAdapter<Files> {\n return {\n platform: \"tauri\",\n available: () => detectPlatform() === \"tauri\",\n create: async () => {\n const [dialog, fs] = await Promise.all([loadDialog(), loadFs()]);\n const pickedAt = async (path: string): Promise<PickedFile> => {\n const name = nameOf(path);\n return {\n name,\n type: typeOf(name),\n size: (await fs.stat(path)).size,\n bytes: () => fs.readFile(path),\n text: async () => new TextDecoder().decode(await fs.readFile(path)),\n };\n };\n return {\n pick: async (options = {}) => {\n const filters = filtersFor(extensionsOf(options.accept));\n const chosen = await dialog.open({\n multiple: options.multiple === true,\n directory: false,\n ...(filters ? { filters } : {}),\n });\n const paths = chosen === null ? [] : Array.isArray(chosen) ? chosen : [chosen];\n return Promise.all(paths.map(pickedAt));\n },\n save: async (data, options) => {\n const filters = filtersFor(\n extensionsOf(options.type ? [options.type] : undefined),\n );\n const path = await dialog.save({\n defaultPath: options.name,\n ...(filters ? { filters } : {}),\n });\n if (path === null) return \"cancelled\";\n await fs.writeFile(path, await bytesOf(data));\n return \"saved\";\n },\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CapabilityAdapter } from "../capability.js";
|
|
2
|
+
import type { Files, PickedFile, PickOptions } from "./interface.js";
|
|
3
|
+
interface SaveFilePicker {
|
|
4
|
+
(options: {
|
|
5
|
+
suggestedName: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
createWritable(): Promise<{
|
|
8
|
+
write(data: Blob): Promise<void>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface WebFilesApis {
|
|
14
|
+
readonly document: Document | undefined;
|
|
15
|
+
readonly showSaveFilePicker?: SaveFilePicker | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** An `<input type="file">` the user answers or cancels; also what a phone's webview shows. */
|
|
18
|
+
export declare function pickWithInput(document: Document, options?: PickOptions): Promise<readonly PickedFile[]>;
|
|
19
|
+
export declare function webFiles(getApis?: () => WebFilesApis): CapabilityAdapter<Files>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/files/web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;AAGxB,UAAU,cAAc;IACtB,CAAC,OAAO,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAC5C,cAAc,IAAI,OAAO,CAAC;YACxB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;SACxB,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC1D;AAiBD,+FAA+F;AAC/F,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAkBhC;AAwCD,wBAAgB,QAAQ,CACtB,OAAO,GAAE,MAAM,YAA0B,GACxC,iBAAiB,CAAC,KAAK,CAAC,CAc1B"}
|