@tinycloud/share-sdk 1.0.0-beta.5 → 1.0.0-beta.7
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/dist/index.cjs +86 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -9
- package/dist/index.d.ts +40 -9
- package/dist/index.js +84 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
SharePublishError: () => SharePublishError,
|
|
42
42
|
ShareReceiveError: () => ShareReceiveError,
|
|
43
43
|
ShareRecipientClient: () => ShareRecipientClient,
|
|
44
|
+
addressedCredentialRequirement: () => addressedCredentialRequirement,
|
|
44
45
|
authorizationMethodForTarget: () => authorizationMethodForTarget,
|
|
45
46
|
authorizeShare: () => authorizeShare,
|
|
46
47
|
canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
|
|
@@ -63,6 +64,7 @@ __export(index_exports, {
|
|
|
63
64
|
openNativeShare: () => openNativeShare,
|
|
64
65
|
parseAddressedEnvelope: () => parseAddressedEnvelope,
|
|
65
66
|
parseNativeShareUrl: () => parseNativeShareUrl,
|
|
67
|
+
prepareAddressedShare: () => prepareAddressedShare,
|
|
66
68
|
publishAddressedShare: () => publishAddressedShare,
|
|
67
69
|
publishPolicyShare: () => publishPolicyShare,
|
|
68
70
|
publishTargetShare: () => publishTargetShare,
|
|
@@ -5764,6 +5766,21 @@ function serialize(value) {
|
|
|
5764
5766
|
function canonicalize(value) {
|
|
5765
5767
|
return serialize(value);
|
|
5766
5768
|
}
|
|
5769
|
+
var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
5770
|
+
function isCanonicalEmailDomain(value) {
|
|
5771
|
+
if (value.length === 0 || value.length > 253) return false;
|
|
5772
|
+
const labels = value.split(".");
|
|
5773
|
+
return labels.length >= 2 && labels.every((label) => LABEL.test(label)) && !/^[0-9]+$/.test(labels.at(-1));
|
|
5774
|
+
}
|
|
5775
|
+
function canonicalMailbox(value) {
|
|
5776
|
+
const email = value.trim().toLowerCase();
|
|
5777
|
+
const at = email.indexOf("@");
|
|
5778
|
+
if (at <= 0 || at !== email.lastIndexOf("@")) return void 0;
|
|
5779
|
+
const local = email.slice(0, at);
|
|
5780
|
+
const domain = email.slice(at + 1);
|
|
5781
|
+
if (local.length > 64 || !/^[a-z0-9#$&'*+/=?^_`{|}~-]+(?:\.[a-z0-9#$&'*+/=?^_`{|}~-]+)*$/.test(local) || !isCanonicalEmailDomain(domain)) return void 0;
|
|
5782
|
+
return Object.freeze({ email, domain });
|
|
5783
|
+
}
|
|
5767
5784
|
async function signCompactUcanAuthorization(input) {
|
|
5768
5785
|
if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60) {
|
|
5769
5786
|
throw new TypeError("compact invocation lifetime must be between one and 60 seconds");
|
|
@@ -6099,6 +6116,7 @@ function redactPublishedShare(result) {
|
|
|
6099
6116
|
target: { ...result.metadata.target },
|
|
6100
6117
|
resource: { ...result.metadata.resource },
|
|
6101
6118
|
actions: [...result.metadata.actions],
|
|
6119
|
+
...result.metadata.expiryClamped === void 0 ? {} : { expiryClamped: result.metadata.expiryClamped },
|
|
6102
6120
|
expiresAt: result.metadata.expiresAt,
|
|
6103
6121
|
display: { ...result.metadata.display }
|
|
6104
6122
|
}
|
|
@@ -6508,12 +6526,6 @@ var base58flickr2 = baseX2({
|
|
|
6508
6526
|
});
|
|
6509
6527
|
|
|
6510
6528
|
// src/targets.ts
|
|
6511
|
-
function validEmail(value) {
|
|
6512
|
-
return /^[^@\s]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(value);
|
|
6513
|
-
}
|
|
6514
|
-
function validDomain(value) {
|
|
6515
|
-
return /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))+$/i.test(value);
|
|
6516
|
-
}
|
|
6517
6529
|
function validRecipientDid(value) {
|
|
6518
6530
|
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
6519
6531
|
const parts = value.split(":");
|
|
@@ -6543,12 +6555,13 @@ function normalizeShareTarget(target) {
|
|
|
6543
6555
|
return { kind: target.kind, did: target.did };
|
|
6544
6556
|
}
|
|
6545
6557
|
if (target.kind === "email") {
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
return { kind: target.kind, address:
|
|
6558
|
+
const mailbox = canonicalMailbox(target.address);
|
|
6559
|
+
if (mailbox === void 0) throw new TypeError("recipient email is invalid");
|
|
6560
|
+
return { kind: target.kind, address: mailbox.email };
|
|
6549
6561
|
}
|
|
6550
|
-
|
|
6551
|
-
|
|
6562
|
+
const domain = target.domain.toLowerCase();
|
|
6563
|
+
if (!isCanonicalEmailDomain(domain)) throw new TypeError("recipient email domain is invalid");
|
|
6564
|
+
return { kind: target.kind, domain };
|
|
6552
6565
|
}
|
|
6553
6566
|
function targetAuthorizationMethod(target) {
|
|
6554
6567
|
return authorizationMethodForTarget(target);
|
|
@@ -6611,6 +6624,7 @@ async function publishTargetShare(input) {
|
|
|
6611
6624
|
...input.actions === void 0 ? {} : { actions: input.actions },
|
|
6612
6625
|
target,
|
|
6613
6626
|
expiresAt,
|
|
6627
|
+
expiryWasExplicit: input.expiryWasExplicit ?? input.expiresAt !== void 0,
|
|
6614
6628
|
origin: input.origin,
|
|
6615
6629
|
...input.notify === void 0 ? {} : { notify: input.notify }
|
|
6616
6630
|
});
|
|
@@ -6634,9 +6648,7 @@ function targetKind(target) {
|
|
|
6634
6648
|
}
|
|
6635
6649
|
function assertSafeInput(input) {
|
|
6636
6650
|
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
6637
|
-
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
6638
6651
|
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
6639
|
-
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6640
6652
|
if (!Number.isFinite(input.expiresAt.getTime()) || input.expiresAt.getTime() <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
6641
6653
|
if (input.artifact === "html" && (input.resource.kind !== "prefix" || !input.actions.includes("read") || !input.actions.includes("list"))) throw new TypeError("html artifacts require a readable prefix");
|
|
6642
6654
|
if (input.contentSource.shareId !== input.shareId || input.contentSource.selector !== input.resource.kind) throw new TypeError("addressed content source is not bound to the share");
|
|
@@ -6729,23 +6741,55 @@ function publicationResult(input) {
|
|
|
6729
6741
|
Object.defineProperty(result, "deliveryMaterial", { enumerable: false, value: input.deliveryMaterial });
|
|
6730
6742
|
return result;
|
|
6731
6743
|
}
|
|
6744
|
+
var EMAIL_PROFILE = { id: "tinycloud.email-proof/v1", version: 1 };
|
|
6732
6745
|
var EMAIL_DOMAIN_PROFILE = { id: "tinycloud.email-domain-proof/v1", version: 1 };
|
|
6733
6746
|
var EMAIL_CREDENTIAL_TYPE = { id: "opencredentials.email/v1", version: 1 };
|
|
6747
|
+
var EMAIL_DESCRIPTOR_DIGEST = "1tg-qphmKBVtNwzVg9xyz-xxqt_xtMXAsQyXw46m8S0";
|
|
6734
6748
|
var EMAIL_DOMAIN_DESCRIPTOR_DIGEST = "33X5mAkZZgApdD3xh_T-3KS5moop0J2Nloi2nqWsdWY";
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
const
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6749
|
+
var EMAIL_CREDENTIAL_ISSUER_DID = "did:web:issuer.credentials.org";
|
|
6750
|
+
var EMAIL_CREDENTIAL_ISSUER_KID = `${EMAIL_CREDENTIAL_ISSUER_DID}#controller`;
|
|
6751
|
+
function mailboxCredentialCommitment(target) {
|
|
6752
|
+
const profile = target.kind === "email" ? EMAIL_PROFILE : EMAIL_DOMAIN_PROFILE;
|
|
6753
|
+
const requirement = target.kind === "email" ? { type: "TinyCloudCredentialRequirement", version: 1, profile, credentialType: EMAIL_CREDENTIAL_TYPE, claims: { email: target.address }, maxAgeSeconds: 3600 } : { type: "TinyCloudCredentialRequirement", version: 1, profile, credentialType: EMAIL_CREDENTIAL_TYPE, claims: { emailDomain: target.domain }, maxAgeSeconds: 300 };
|
|
6754
|
+
return {
|
|
6755
|
+
type: "TinyCloudPolicyCredentialRequirement",
|
|
6756
|
+
version: 1,
|
|
6757
|
+
requirementDigest: toBase64Url((0, import_sha2562.sha256)(textEncoder.encode(canonicalize(requirement)))),
|
|
6758
|
+
descriptorDigest: target.kind === "email" ? EMAIL_DESCRIPTOR_DIGEST : EMAIL_DOMAIN_DESCRIPTOR_DIGEST,
|
|
6759
|
+
issuerDid: EMAIL_CREDENTIAL_ISSUER_DID,
|
|
6760
|
+
issuerKid: EMAIL_CREDENTIAL_ISSUER_KID,
|
|
6761
|
+
profile,
|
|
6762
|
+
credentialType: EMAIL_CREDENTIAL_TYPE
|
|
6763
|
+
};
|
|
6764
|
+
}
|
|
6765
|
+
function addressedCredentialRequirement(target) {
|
|
6766
|
+
const normalized = normalizeShareTarget(target);
|
|
6767
|
+
if (normalized.kind !== "email" && normalized.kind !== "emailDomain") throw new TypeError("a mailbox target is required");
|
|
6768
|
+
return mailboxCredentialCommitment(normalized);
|
|
6769
|
+
}
|
|
6770
|
+
function prepareAddressedShare(request) {
|
|
6771
|
+
if (request.filename.length === 0 || request.filename === "." || request.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(request.filename)) throw new TypeError("addressed filename is invalid");
|
|
6772
|
+
if (request.actions.length === 0 || request.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6773
|
+
const target = normalizeShareTarget(request.target);
|
|
6774
|
+
if (target.kind === "bearer") throw new TypeError("addressed target is required");
|
|
6775
|
+
if (target.kind === "recipientDid") return { target };
|
|
6776
|
+
if (target.kind === "emailDomain") {
|
|
6777
|
+
if (!request.actions.every((action) => action === "read" || action === "list") || !request.policyActions.every((action) => action === "tinycloud.kv/get" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata")) throw new TypeError("email-domain shares are view-only");
|
|
6778
|
+
if (request.deliveryEmail !== void 0) throw new TypeError("email-domain shares are not emailed");
|
|
6779
|
+
}
|
|
6780
|
+
return { target, credentialRequirement: mailboxCredentialCommitment(target) };
|
|
6781
|
+
}
|
|
6782
|
+
function assertMailboxCommitment(commitment, target) {
|
|
6783
|
+
const label = target.kind === "email" ? "email" : "email-domain";
|
|
6784
|
+
if (commitment === void 0) throw new TypeError(`${label} shares require a credential requirement`);
|
|
6785
|
+
const expected = mailboxCredentialCommitment(target);
|
|
6786
|
+
if (canonicalize(commitment.profile) !== canonicalize(expected.profile) || canonicalize(commitment.credentialType) !== canonicalize(expected.credentialType) || commitment.descriptorDigest !== expected.descriptorDigest || commitment.issuerDid !== expected.issuerDid || commitment.issuerKid !== expected.issuerKid) throw new TypeError(`${label} shares require the ${label} credential profile`);
|
|
6787
|
+
if (commitment.requirementDigest !== expected.requirementDigest) throw new TypeError(`credential requirement is not bound to the email ${target.kind === "email" ? "address" : "domain"}`);
|
|
6743
6788
|
}
|
|
6744
6789
|
async function publishAddressedShare(options) {
|
|
6745
6790
|
assertSafeInput(options);
|
|
6746
|
-
const target =
|
|
6747
|
-
if (target.kind
|
|
6748
|
-
if (target.kind === "emailDomain") assertEmailDomainPolicy(options, target.domain);
|
|
6791
|
+
const { target } = prepareAddressedShare(options);
|
|
6792
|
+
if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
|
|
6749
6793
|
const expiry = rfc3339Seconds(options.expiresAt);
|
|
6750
6794
|
const matcher = targetMatcher(target);
|
|
6751
6795
|
const capabilities = sortCanonical([
|
|
@@ -7856,17 +7900,20 @@ var ShareNotifyError = class extends Error {
|
|
|
7856
7900
|
};
|
|
7857
7901
|
function recipientMatchesShareRecord(record, recipient) {
|
|
7858
7902
|
const matcher = record.recipientMatcher;
|
|
7859
|
-
|
|
7860
|
-
if (
|
|
7861
|
-
|
|
7862
|
-
|
|
7903
|
+
const mailbox = canonicalMailbox(recipient);
|
|
7904
|
+
if (mailbox === void 0) return false;
|
|
7905
|
+
if (matcher.kind === "exactEmail") return canonicalMailbox(matcher.value)?.email === mailbox.email;
|
|
7906
|
+
if (matcher.kind === "emailDomain") return mailbox.domain === matcher.value;
|
|
7907
|
+
return false;
|
|
7863
7908
|
}
|
|
7864
7909
|
async function notifyShare(input) {
|
|
7865
|
-
|
|
7866
|
-
if (input.
|
|
7910
|
+
const mailbox = canonicalMailbox(input.recipient);
|
|
7911
|
+
if (!input.shareId || mailbox === void 0) throw new ShareNotifyError("recipient is invalid");
|
|
7912
|
+
const recipient = mailbox.email;
|
|
7913
|
+
if (input.record !== void 0 && !recipientMatchesShareRecord(input.record, recipient)) {
|
|
7867
7914
|
throw new ShareNotifyError("recipient does not match the stored share target");
|
|
7868
7915
|
}
|
|
7869
|
-
const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId,
|
|
7916
|
+
const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId, recipient);
|
|
7870
7917
|
const attemptsLimit = input.maxAttempts ?? 3;
|
|
7871
7918
|
if (!Number.isSafeInteger(attemptsLimit) || attemptsLimit < 1 || attemptsLimit > 8) throw new ShareNotifyError("maxAttempts is invalid");
|
|
7872
7919
|
let attempts = 0;
|
|
@@ -7875,7 +7922,7 @@ async function notifyShare(input) {
|
|
|
7875
7922
|
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
7876
7923
|
attempts += 1;
|
|
7877
7924
|
try {
|
|
7878
|
-
const state = await input.adapter.deliver({ shareId: input.shareId, recipient
|
|
7925
|
+
const state = await input.adapter.deliver({ shareId: input.shareId, recipient, idempotencyKey, ...input.record === void 0 ? {} : { record: input.record }, ...input.signal === void 0 ? {} : { signal: input.signal } });
|
|
7879
7926
|
return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state, idempotencyKey, attempts };
|
|
7880
7927
|
} catch (error) {
|
|
7881
7928
|
lastError = error;
|
|
@@ -8558,7 +8605,11 @@ function canonicalViewerUrl(viewerOrigin) {
|
|
|
8558
8605
|
async function createNativeShare(sharing, input) {
|
|
8559
8606
|
if (!input.path || input.path.startsWith("/") || input.path.endsWith("/") || input.path.includes("..") || input.path.includes("//")) throw new TypeError("native share path must be one canonical TinyCloud KV key");
|
|
8560
8607
|
const generated = await sharing.generate({ path: input.path, actions: ["tinycloud.kv/get"], expiry: input.expiresAt });
|
|
8561
|
-
if (generated.ok !== true)
|
|
8608
|
+
if (generated.ok !== true) {
|
|
8609
|
+
const error = new Error(typeof generated.error?.message === "string" ? generated.error.message : "TinyCloud sharing service rejected delegation generation");
|
|
8610
|
+
if (typeof generated.error?.code === "string") Object.assign(error, { code: generated.error.code });
|
|
8611
|
+
throw error;
|
|
8612
|
+
}
|
|
8562
8613
|
if (typeof generated.data?.token !== "string" || typeof generated.data.delegation?.cid !== "string" || !(generated.data.expiresAt instanceof Date)) throw new Error("TinyCloud sharing service returned incomplete delegation metadata");
|
|
8563
8614
|
const delegation = sharing.decodeLink(generated.data.token);
|
|
8564
8615
|
if (typeof delegation.spaceId !== "string" || delegation.spaceId.length === 0 || typeof delegation.path !== "string" || delegation.path.length === 0) throw new Error("TinyCloud sharing service returned a non-canonical delegation authority");
|
|
@@ -8600,6 +8651,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8600
8651
|
SharePublishError,
|
|
8601
8652
|
ShareReceiveError,
|
|
8602
8653
|
ShareRecipientClient,
|
|
8654
|
+
addressedCredentialRequirement,
|
|
8603
8655
|
authorizationMethodForTarget,
|
|
8604
8656
|
authorizeShare,
|
|
8605
8657
|
canonicalOwnerSharePolicy,
|
|
@@ -8622,6 +8674,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8622
8674
|
openNativeShare,
|
|
8623
8675
|
parseAddressedEnvelope,
|
|
8624
8676
|
parseNativeShareUrl,
|
|
8677
|
+
prepareAddressedShare,
|
|
8625
8678
|
publishAddressedShare,
|
|
8626
8679
|
publishPolicyShare,
|
|
8627
8680
|
publishTargetShare,
|