@tinycloud/share-sdk 1.0.0-beta.6 → 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 +79 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +77 -32
- 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");
|
|
@@ -6509,12 +6526,6 @@ var base58flickr2 = baseX2({
|
|
|
6509
6526
|
});
|
|
6510
6527
|
|
|
6511
6528
|
// src/targets.ts
|
|
6512
|
-
function validEmail(value) {
|
|
6513
|
-
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);
|
|
6514
|
-
}
|
|
6515
|
-
function validDomain(value) {
|
|
6516
|
-
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);
|
|
6517
|
-
}
|
|
6518
6529
|
function validRecipientDid(value) {
|
|
6519
6530
|
if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
|
|
6520
6531
|
const parts = value.split(":");
|
|
@@ -6544,12 +6555,13 @@ function normalizeShareTarget(target) {
|
|
|
6544
6555
|
return { kind: target.kind, did: target.did };
|
|
6545
6556
|
}
|
|
6546
6557
|
if (target.kind === "email") {
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
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 };
|
|
6550
6561
|
}
|
|
6551
|
-
|
|
6552
|
-
|
|
6562
|
+
const domain = target.domain.toLowerCase();
|
|
6563
|
+
if (!isCanonicalEmailDomain(domain)) throw new TypeError("recipient email domain is invalid");
|
|
6564
|
+
return { kind: target.kind, domain };
|
|
6553
6565
|
}
|
|
6554
6566
|
function targetAuthorizationMethod(target) {
|
|
6555
6567
|
return authorizationMethodForTarget(target);
|
|
@@ -6636,9 +6648,7 @@ function targetKind(target) {
|
|
|
6636
6648
|
}
|
|
6637
6649
|
function assertSafeInput(input) {
|
|
6638
6650
|
if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
|
|
6639
|
-
if (input.filename.length === 0 || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) throw new TypeError("addressed filename is invalid");
|
|
6640
6651
|
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
|
|
6641
|
-
if (input.actions.length === 0 || input.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
|
|
6642
6652
|
if (!Number.isFinite(input.expiresAt.getTime()) || input.expiresAt.getTime() <= Date.now()) throw new TypeError("addressed share expiry must be in the future");
|
|
6643
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");
|
|
6644
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");
|
|
@@ -6731,23 +6741,55 @@ function publicationResult(input) {
|
|
|
6731
6741
|
Object.defineProperty(result, "deliveryMaterial", { enumerable: false, value: input.deliveryMaterial });
|
|
6732
6742
|
return result;
|
|
6733
6743
|
}
|
|
6744
|
+
var EMAIL_PROFILE = { id: "tinycloud.email-proof/v1", version: 1 };
|
|
6734
6745
|
var EMAIL_DOMAIN_PROFILE = { id: "tinycloud.email-domain-proof/v1", version: 1 };
|
|
6735
6746
|
var EMAIL_CREDENTIAL_TYPE = { id: "opencredentials.email/v1", version: 1 };
|
|
6747
|
+
var EMAIL_DESCRIPTOR_DIGEST = "1tg-qphmKBVtNwzVg9xyz-xxqt_xtMXAsQyXw46m8S0";
|
|
6736
6748
|
var EMAIL_DOMAIN_DESCRIPTOR_DIGEST = "33X5mAkZZgApdD3xh_T-3KS5moop0J2Nloi2nqWsdWY";
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
const
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
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"}`);
|
|
6745
6788
|
}
|
|
6746
6789
|
async function publishAddressedShare(options) {
|
|
6747
6790
|
assertSafeInput(options);
|
|
6748
|
-
const target =
|
|
6749
|
-
if (target.kind
|
|
6750
|
-
if (target.kind === "emailDomain") assertEmailDomainPolicy(options, target.domain);
|
|
6791
|
+
const { target } = prepareAddressedShare(options);
|
|
6792
|
+
if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
|
|
6751
6793
|
const expiry = rfc3339Seconds(options.expiresAt);
|
|
6752
6794
|
const matcher = targetMatcher(target);
|
|
6753
6795
|
const capabilities = sortCanonical([
|
|
@@ -7858,17 +7900,20 @@ var ShareNotifyError = class extends Error {
|
|
|
7858
7900
|
};
|
|
7859
7901
|
function recipientMatchesShareRecord(record, recipient) {
|
|
7860
7902
|
const matcher = record.recipientMatcher;
|
|
7861
|
-
|
|
7862
|
-
if (
|
|
7863
|
-
|
|
7864
|
-
|
|
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;
|
|
7865
7908
|
}
|
|
7866
7909
|
async function notifyShare(input) {
|
|
7867
|
-
|
|
7868
|
-
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)) {
|
|
7869
7914
|
throw new ShareNotifyError("recipient does not match the stored share target");
|
|
7870
7915
|
}
|
|
7871
|
-
const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId,
|
|
7916
|
+
const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId, recipient);
|
|
7872
7917
|
const attemptsLimit = input.maxAttempts ?? 3;
|
|
7873
7918
|
if (!Number.isSafeInteger(attemptsLimit) || attemptsLimit < 1 || attemptsLimit > 8) throw new ShareNotifyError("maxAttempts is invalid");
|
|
7874
7919
|
let attempts = 0;
|
|
@@ -7877,7 +7922,7 @@ async function notifyShare(input) {
|
|
|
7877
7922
|
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
7878
7923
|
attempts += 1;
|
|
7879
7924
|
try {
|
|
7880
|
-
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 } });
|
|
7881
7926
|
return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state, idempotencyKey, attempts };
|
|
7882
7927
|
} catch (error) {
|
|
7883
7928
|
lastError = error;
|
|
@@ -8606,6 +8651,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8606
8651
|
SharePublishError,
|
|
8607
8652
|
ShareReceiveError,
|
|
8608
8653
|
ShareRecipientClient,
|
|
8654
|
+
addressedCredentialRequirement,
|
|
8609
8655
|
authorizationMethodForTarget,
|
|
8610
8656
|
authorizeShare,
|
|
8611
8657
|
canonicalOwnerSharePolicy,
|
|
@@ -8628,6 +8674,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8628
8674
|
openNativeShare,
|
|
8629
8675
|
parseAddressedEnvelope,
|
|
8630
8676
|
parseNativeShareUrl,
|
|
8677
|
+
prepareAddressedShare,
|
|
8631
8678
|
publishAddressedShare,
|
|
8632
8679
|
publishPolicyShare,
|
|
8633
8680
|
publishTargetShare,
|