@graffiti-garden/implementation-decentralized 0.0.3 → 0.0.5
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/3-protocol/3-object-encoding.d.ts.map +1 -1
- package/dist/3-protocol/4-graffiti.d.ts +2 -1
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/{style-3ALLGCD7-QNFKN6AK.js → style-RMTPI5KV-Y5KAOOZR.js} +2 -1
- package/dist/browser/style-RMTPI5KV-Y5KAOOZR.js.map +7 -0
- package/dist/cjs/3-protocol/1-sessions.js +1 -1
- package/dist/cjs/3-protocol/1-sessions.js.map +2 -2
- package/dist/cjs/3-protocol/3-object-encoding.js +3 -2
- package/dist/cjs/3-protocol/3-object-encoding.js.map +2 -2
- package/dist/cjs/3-protocol/4-graffiti.js +249 -207
- package/dist/cjs/3-protocol/4-graffiti.js.map +3 -3
- package/dist/esm/3-protocol/1-sessions.js +1 -1
- package/dist/esm/3-protocol/1-sessions.js.map +2 -2
- package/dist/esm/3-protocol/3-object-encoding.js +3 -2
- package/dist/esm/3-protocol/3-object-encoding.js.map +2 -2
- package/dist/esm/3-protocol/4-graffiti.js +249 -207
- package/dist/esm/3-protocol/4-graffiti.js.map +3 -3
- package/package.json +7 -7
- package/src/3-protocol/1-sessions.ts +1 -1
- package/src/3-protocol/3-object-encoding.ts +4 -2
- package/src/3-protocol/4-graffiti.ts +319 -265
- package/dist/browser/style-3ALLGCD7-QNFKN6AK.js.map +0 -7
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
MESSAGE_METADATA_KEY,
|
|
35
35
|
MESSAGE_OBJECT_KEY,
|
|
36
36
|
MESSAGE_TAGS_KEY,
|
|
37
|
+
type LabeledMessage,
|
|
37
38
|
type MessageStream,
|
|
38
39
|
} from "../1-services/4-inboxes";
|
|
39
40
|
|
|
@@ -131,6 +132,8 @@ export interface GraffitiDecentralizedOptions {
|
|
|
131
132
|
defaultInboxEndpoints?: string[];
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
const CONCURRENCY = 16;
|
|
136
|
+
|
|
134
137
|
export class GraffitiDecentralized implements Graffiti {
|
|
135
138
|
protected readonly dids = new DecentralizedIdentifiers();
|
|
136
139
|
protected readonly authorization = new Authorization();
|
|
@@ -705,79 +708,87 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
705
708
|
>(async (it, index) => indexedSingleEndpointQueryNext<Schema>(it, index));
|
|
706
709
|
let active = indexedIteratorNexts.length;
|
|
707
710
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
error: next.error,
|
|
717
|
-
origin: allInboxes[next.index].serviceEndpoint,
|
|
718
|
-
};
|
|
719
|
-
} else if (next.result.done) {
|
|
720
|
-
// Store the cursor for future use
|
|
721
|
-
const inbox = allInboxes[next.index];
|
|
722
|
-
cursors[inbox.serviceEndpoint] = next.result.value;
|
|
723
|
-
// Remove it from the race
|
|
724
|
-
indexedIteratorNexts[next.index] = new Promise(() => {});
|
|
725
|
-
active--;
|
|
726
|
-
} else {
|
|
727
|
-
// Re-arm the iterator
|
|
728
|
-
indexedIteratorNexts[next.index] =
|
|
729
|
-
indexedSingleEndpointQueryNext<Schema>(
|
|
730
|
-
iterators[next.index],
|
|
731
|
-
next.index,
|
|
732
|
-
);
|
|
733
|
-
const { object, tombstone, tags: receivedTags } = next.result.value;
|
|
734
|
-
if (tombstone) {
|
|
735
|
-
if (tombstones.get(object.url) === true) continue;
|
|
736
|
-
tombstones.set(object.url, true);
|
|
711
|
+
try {
|
|
712
|
+
while (active > 0) {
|
|
713
|
+
const next: IndexedSingleEndpointQueryResult<Schema> =
|
|
714
|
+
await Promise.race<any>(indexedIteratorNexts);
|
|
715
|
+
if (next.error !== undefined) {
|
|
716
|
+
// Remove it from the race
|
|
717
|
+
indexedIteratorNexts[next.index] = new Promise(() => {});
|
|
718
|
+
active--;
|
|
737
719
|
yield {
|
|
738
|
-
|
|
739
|
-
|
|
720
|
+
error: next.error,
|
|
721
|
+
origin: allInboxes[next.index].serviceEndpoint,
|
|
740
722
|
};
|
|
723
|
+
} else if (next.result.done) {
|
|
724
|
+
// Store the cursor for future use
|
|
725
|
+
const inbox = allInboxes[next.index];
|
|
726
|
+
cursors[inbox.serviceEndpoint] = next.result.value;
|
|
727
|
+
// Remove it from the race
|
|
728
|
+
indexedIteratorNexts[next.index] = new Promise(() => {});
|
|
729
|
+
active--;
|
|
741
730
|
} else {
|
|
742
|
-
//
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
731
|
+
// Re-arm the iterator
|
|
732
|
+
indexedIteratorNexts[next.index] =
|
|
733
|
+
indexedSingleEndpointQueryNext<Schema>(
|
|
734
|
+
iterators[next.index],
|
|
735
|
+
next.index,
|
|
736
|
+
);
|
|
737
|
+
const { object, tombstone, tags: receivedTags } = next.result.value;
|
|
738
|
+
if (tombstone) {
|
|
739
|
+
if (tombstones.get(object.url) === true) continue;
|
|
740
|
+
tombstones.set(object.url, true);
|
|
741
|
+
yield {
|
|
742
|
+
tombstone,
|
|
743
|
+
object: { url: object.url },
|
|
744
|
+
};
|
|
745
|
+
} else {
|
|
746
|
+
// Filter already seen
|
|
747
|
+
if (tombstones.get(object.url) === false) continue;
|
|
748
|
+
|
|
749
|
+
// Fill in the matched channels
|
|
750
|
+
const matchedTagIndices = tags.reduce<number[]>(
|
|
751
|
+
(acc, tag, tagIndex) => {
|
|
752
|
+
for (const receivedTag of receivedTags) {
|
|
753
|
+
if (
|
|
754
|
+
tag.length === receivedTag.length &&
|
|
755
|
+
tag.every((b, i) => receivedTag[i] === b)
|
|
756
|
+
) {
|
|
757
|
+
acc.push(tagIndex);
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
755
760
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
761
|
+
return acc;
|
|
762
|
+
},
|
|
763
|
+
[],
|
|
764
|
+
);
|
|
765
|
+
const matchedChannels = matchedTagIndices.map(
|
|
766
|
+
(index) => channels[index],
|
|
767
|
+
);
|
|
768
|
+
if (matchedChannels.length === 0) {
|
|
769
|
+
yield {
|
|
770
|
+
error: new Error(
|
|
771
|
+
"Inbox returned object without matching channels",
|
|
772
|
+
),
|
|
773
|
+
origin: allInboxes[next.index].serviceEndpoint,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
tombstones.set(object.url, false);
|
|
765
777
|
yield {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
778
|
+
object: {
|
|
779
|
+
...object,
|
|
780
|
+
channels: matchedChannels,
|
|
781
|
+
},
|
|
770
782
|
};
|
|
771
783
|
}
|
|
772
|
-
tombstones.set(object.url, false);
|
|
773
|
-
yield {
|
|
774
|
-
object: {
|
|
775
|
-
...object,
|
|
776
|
-
channels: matchedChannels,
|
|
777
|
-
},
|
|
778
|
-
};
|
|
779
784
|
}
|
|
780
785
|
}
|
|
786
|
+
} finally {
|
|
787
|
+
await Promise.all(
|
|
788
|
+
iterators.map<Promise<void>>(async (it) => {
|
|
789
|
+
await it.return("");
|
|
790
|
+
}),
|
|
791
|
+
);
|
|
781
792
|
}
|
|
782
793
|
|
|
783
794
|
return {
|
|
@@ -785,8 +796,6 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
785
796
|
channels,
|
|
786
797
|
cursors,
|
|
787
798
|
} satisfies infer_<typeof CursorSchema>),
|
|
788
|
-
continue: (session) =>
|
|
789
|
-
this.discoverMeta<Schema>(channels, schema, cursors, session),
|
|
790
799
|
};
|
|
791
800
|
}
|
|
792
801
|
|
|
@@ -795,6 +804,7 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
795
804
|
return this.discoverMeta<(typeof args)[1]>(channels, schema, {}, session);
|
|
796
805
|
};
|
|
797
806
|
|
|
807
|
+
// @ts-ignore
|
|
798
808
|
continueDiscover: Graffiti["continueDiscover"] = (...args) => {
|
|
799
809
|
const [cursor, session] = args;
|
|
800
810
|
// Extract the channels from the cursor
|
|
@@ -1009,237 +1019,281 @@ export class GraffitiDecentralized implements Graffiti {
|
|
|
1009
1019
|
inboxToken,
|
|
1010
1020
|
) as unknown as MessageStream<Schema>);
|
|
1011
1021
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1022
|
+
const inFlight: Promise<SingleEndpointQueryResult<Schema> | void>[] = [];
|
|
1023
|
+
let doneValue: string | null = null;
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
while (true) {
|
|
1027
|
+
while (doneValue === null && inFlight.length < CONCURRENCY) {
|
|
1028
|
+
const itResult = await iterator.next();
|
|
1029
|
+
if (itResult.done) {
|
|
1030
|
+
doneValue = itResult.value;
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const processPromise = this.processOneLabeledMessage<Schema>(
|
|
1035
|
+
inboxEndpoint,
|
|
1036
|
+
itResult.value,
|
|
1037
|
+
inboxToken,
|
|
1038
|
+
recipient,
|
|
1039
|
+
).catch((e) => {
|
|
1040
|
+
throw e;
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
inFlight.push(processPromise);
|
|
1044
|
+
}
|
|
1016
1045
|
|
|
1017
|
-
|
|
1046
|
+
const nextProcessedPromise = inFlight.shift();
|
|
1018
1047
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1048
|
+
if (!nextProcessedPromise) {
|
|
1049
|
+
if (doneValue !== null) return doneValue;
|
|
1050
|
+
|
|
1051
|
+
throw new Error("Process queue empty but no return value");
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const processed = await nextProcessedPromise;
|
|
1055
|
+
if (processed) yield processed;
|
|
1056
|
+
}
|
|
1057
|
+
} finally {
|
|
1058
|
+
await iterator.return("");
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
protected async processOneLabeledMessage<Schema extends JSONSchema>(
|
|
1063
|
+
inboxEndpoint: string,
|
|
1064
|
+
result: LabeledMessage<Schema>,
|
|
1065
|
+
inboxToken?: string | null,
|
|
1066
|
+
recipient?: string | null,
|
|
1067
|
+
): Promise<SingleEndpointQueryResult<Schema> | void> {
|
|
1068
|
+
const label = result.l;
|
|
1069
|
+
// Anything invalid or unexpected, we can skip
|
|
1070
|
+
if (
|
|
1071
|
+
label !== MESSAGE_LABEL_VALID &&
|
|
1072
|
+
label !== MESSAGE_LABEL_UNLABELED &&
|
|
1073
|
+
label !== MESSAGE_LABEL_TRASH
|
|
1074
|
+
)
|
|
1075
|
+
return;
|
|
1076
|
+
|
|
1077
|
+
const messageId = result.id;
|
|
1078
|
+
const { o: object, m: metadataBytes, t: receivedTags } = result.m;
|
|
1079
|
+
|
|
1080
|
+
let metadata: MessageMetadata;
|
|
1081
|
+
try {
|
|
1082
|
+
const metadataRaw = dagCborDecode(metadataBytes);
|
|
1083
|
+
metadata = MessageMetadataSchema.parse(metadataRaw);
|
|
1084
|
+
} catch (e) {
|
|
1085
|
+
this.inboxes.label(
|
|
1086
|
+
inboxEndpoint,
|
|
1087
|
+
messageId,
|
|
1088
|
+
MESSAGE_LABEL_INVALID,
|
|
1089
|
+
inboxToken,
|
|
1090
|
+
);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const {
|
|
1095
|
+
[MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,
|
|
1096
|
+
[MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,
|
|
1097
|
+
} = metadata;
|
|
1098
|
+
|
|
1099
|
+
const allowedTickets =
|
|
1100
|
+
MESSAGE_DATA_ALLOWED_TICKETS_KEY in metadata
|
|
1101
|
+
? metadata[MESSAGE_DATA_ALLOWED_TICKETS_KEY]
|
|
1102
|
+
: undefined;
|
|
1103
|
+
const announcements =
|
|
1104
|
+
MESSAGE_DATA_ANNOUNCEMENTS_KEY in metadata
|
|
1105
|
+
? metadata[MESSAGE_DATA_ANNOUNCEMENTS_KEY]
|
|
1106
|
+
: undefined;
|
|
1107
|
+
|
|
1108
|
+
if (label === MESSAGE_LABEL_VALID) {
|
|
1109
|
+
return {
|
|
1110
|
+
messageId,
|
|
1111
|
+
object,
|
|
1112
|
+
storageBucketKey,
|
|
1113
|
+
allowedTickets,
|
|
1114
|
+
tags: receivedTags,
|
|
1115
|
+
announcements,
|
|
1116
|
+
};
|
|
1117
|
+
} else if (label === MESSAGE_LABEL_TRASH) {
|
|
1118
|
+
// If it is simply trash, just continue.
|
|
1119
|
+
if (!tombstonedMessageId) return;
|
|
1120
|
+
|
|
1121
|
+
// Make sure the tombstone points to a real message
|
|
1122
|
+
const past = await this.inboxes.get(
|
|
1123
|
+
inboxEndpoint,
|
|
1124
|
+
tombstonedMessageId,
|
|
1125
|
+
inboxToken,
|
|
1126
|
+
);
|
|
1021
1127
|
if (
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
label !== MESSAGE_LABEL_TRASH
|
|
1128
|
+
!past ||
|
|
1129
|
+
past[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url !== object.url
|
|
1025
1130
|
)
|
|
1026
|
-
|
|
1131
|
+
return;
|
|
1027
1132
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const metadataRaw = dagCborDecode(metadataBytes);
|
|
1034
|
-
metadata = MessageMetadataSchema.parse(metadataRaw);
|
|
1035
|
-
} catch (e) {
|
|
1133
|
+
// If the referred to message isn't labeled as trash, trash it
|
|
1134
|
+
// This may happen if a trash message is processed on another
|
|
1135
|
+
// device and the device cache is out of date.
|
|
1136
|
+
if (past[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH) {
|
|
1137
|
+
// Label the message as trash
|
|
1036
1138
|
this.inboxes.label(
|
|
1037
1139
|
inboxEndpoint,
|
|
1038
|
-
|
|
1039
|
-
|
|
1140
|
+
tombstonedMessageId,
|
|
1141
|
+
MESSAGE_LABEL_TRASH,
|
|
1040
1142
|
inboxToken,
|
|
1041
1143
|
);
|
|
1042
|
-
continue;
|
|
1043
1144
|
}
|
|
1044
1145
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1146
|
+
// Return the tombstone
|
|
1147
|
+
return {
|
|
1148
|
+
messageId,
|
|
1149
|
+
tombstone: true,
|
|
1150
|
+
object,
|
|
1151
|
+
storageBucketKey,
|
|
1152
|
+
allowedTickets,
|
|
1153
|
+
tags: receivedTags,
|
|
1154
|
+
announcements,
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1049
1157
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1158
|
+
// Otherwise, unlabeled: try to validate the object
|
|
1159
|
+
let validationError: unknown | undefined = undefined;
|
|
1160
|
+
try {
|
|
1161
|
+
const actor = object.actor;
|
|
1162
|
+
const actorDocument = await this.dids.resolve(actor);
|
|
1163
|
+
const storageBucketService = actorDocument?.service?.find(
|
|
1164
|
+
(service) =>
|
|
1165
|
+
service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&
|
|
1166
|
+
service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,
|
|
1167
|
+
);
|
|
1168
|
+
if (!storageBucketService) {
|
|
1169
|
+
throw new GraffitiErrorNotFound(
|
|
1170
|
+
`Actor ${actor} has no storage bucket service`,
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
if (typeof storageBucketService.serviceEndpoint !== "string") {
|
|
1174
|
+
throw new GraffitiErrorNotFound(
|
|
1175
|
+
`Actor ${actor} does not have a valid storage bucket endpoint`,
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
const storageBucketEndpoint = storageBucketService.serviceEndpoint;
|
|
1179
|
+
|
|
1180
|
+
const objectBytes = await this.storageBuckets.get(
|
|
1181
|
+
storageBucketEndpoint,
|
|
1182
|
+
storageBucketKey,
|
|
1183
|
+
MAX_OBJECT_SIZE_BYTES,
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
if (MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata && !recipient) {
|
|
1187
|
+
throw new GraffitiErrorForbidden(
|
|
1188
|
+
`Recipient is required when allowed ticket is present`,
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
const privateObjectInfo = allowedTickets
|
|
1192
|
+
? { allowedTickets }
|
|
1193
|
+
: MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata
|
|
1194
|
+
? {
|
|
1195
|
+
recipient: recipient ?? "null",
|
|
1196
|
+
allowedTicket: metadata[MESSAGE_DATA_ALLOWED_TICKET_KEY],
|
|
1197
|
+
allowedIndex: metadata[MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY],
|
|
1198
|
+
}
|
|
1057
1199
|
: undefined;
|
|
1058
1200
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1201
|
+
await this.objectEncoding.validate(
|
|
1202
|
+
object,
|
|
1203
|
+
receivedTags,
|
|
1204
|
+
objectBytes,
|
|
1205
|
+
privateObjectInfo,
|
|
1206
|
+
);
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
validationError = e;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (tombstonedMessageId) {
|
|
1212
|
+
if (validationError instanceof GraffitiErrorNotFound) {
|
|
1213
|
+
// Not found == The tombstone is correct
|
|
1214
|
+
this.inboxes
|
|
1215
|
+
// Get the referenced message
|
|
1216
|
+
.get(inboxEndpoint, tombstonedMessageId, inboxToken)
|
|
1217
|
+
.then((result) => {
|
|
1218
|
+
if (
|
|
1219
|
+
// Make sure that it actually references the object being deleted
|
|
1220
|
+
result &&
|
|
1221
|
+
result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url ===
|
|
1222
|
+
object.url &&
|
|
1223
|
+
// And that the object is not already marked as trash
|
|
1224
|
+
result[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH
|
|
1225
|
+
) {
|
|
1226
|
+
// If valid but not yet trash, label the message as trash
|
|
1227
|
+
this.inboxes.label(
|
|
1228
|
+
inboxEndpoint,
|
|
1229
|
+
tombstonedMessageId,
|
|
1230
|
+
MESSAGE_LABEL_TRASH,
|
|
1231
|
+
inboxToken,
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Then, label the tombstone message as trash
|
|
1236
|
+
this.inboxes.label(
|
|
1237
|
+
inboxEndpoint,
|
|
1238
|
+
messageId,
|
|
1239
|
+
MESSAGE_LABEL_TRASH,
|
|
1240
|
+
inboxToken,
|
|
1241
|
+
);
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
return {
|
|
1061
1245
|
messageId,
|
|
1246
|
+
tombstone: true,
|
|
1062
1247
|
object,
|
|
1063
1248
|
storageBucketKey,
|
|
1064
1249
|
allowedTickets,
|
|
1065
1250
|
tags: receivedTags,
|
|
1066
1251
|
announcements,
|
|
1067
1252
|
};
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
// Make sure the tombstone points to a real message
|
|
1074
|
-
const past = await this.inboxes.get(
|
|
1253
|
+
} else {
|
|
1254
|
+
console.error("Recieved an incorrect tombstone object");
|
|
1255
|
+
console.error(validationError);
|
|
1256
|
+
this.inboxes.label(
|
|
1075
1257
|
inboxEndpoint,
|
|
1076
|
-
|
|
1258
|
+
messageId,
|
|
1259
|
+
MESSAGE_LABEL_INVALID,
|
|
1077
1260
|
inboxToken,
|
|
1078
1261
|
);
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if (past[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH) {
|
|
1090
|
-
// Label the message as trash
|
|
1091
|
-
this.inboxes.label(
|
|
1092
|
-
inboxEndpoint,
|
|
1093
|
-
tombstonedMessageId,
|
|
1094
|
-
MESSAGE_LABEL_TRASH,
|
|
1095
|
-
inboxToken,
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Return the tombstone
|
|
1100
|
-
yield {
|
|
1262
|
+
}
|
|
1263
|
+
} else {
|
|
1264
|
+
if (validationError === undefined) {
|
|
1265
|
+
this.inboxes.label(
|
|
1266
|
+
inboxEndpoint,
|
|
1267
|
+
messageId,
|
|
1268
|
+
MESSAGE_LABEL_VALID,
|
|
1269
|
+
inboxToken,
|
|
1270
|
+
);
|
|
1271
|
+
return {
|
|
1101
1272
|
messageId,
|
|
1102
|
-
tombstone: true,
|
|
1103
1273
|
object,
|
|
1104
1274
|
storageBucketKey,
|
|
1105
|
-
allowedTickets,
|
|
1106
1275
|
tags: receivedTags,
|
|
1276
|
+
allowedTickets,
|
|
1107
1277
|
announcements,
|
|
1108
1278
|
};
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const storageBucketService = actorDocument?.service?.find(
|
|
1118
|
-
(service) =>
|
|
1119
|
-
service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&
|
|
1120
|
-
service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,
|
|
1121
|
-
);
|
|
1122
|
-
if (!storageBucketService) {
|
|
1123
|
-
throw new GraffitiErrorNotFound(
|
|
1124
|
-
`Actor ${actor} has no storage bucket service`,
|
|
1125
|
-
);
|
|
1126
|
-
}
|
|
1127
|
-
if (typeof storageBucketService.serviceEndpoint !== "string") {
|
|
1128
|
-
throw new GraffitiErrorNotFound(
|
|
1129
|
-
`Actor ${actor} does not have a valid storage bucket endpoint`,
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
const storageBucketEndpoint = storageBucketService.serviceEndpoint;
|
|
1133
|
-
|
|
1134
|
-
const objectBytes = await this.storageBuckets.get(
|
|
1135
|
-
storageBucketEndpoint,
|
|
1136
|
-
storageBucketKey,
|
|
1137
|
-
MAX_OBJECT_SIZE_BYTES,
|
|
1138
|
-
);
|
|
1139
|
-
|
|
1140
|
-
if (MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata && !recipient) {
|
|
1141
|
-
throw new GraffitiErrorForbidden(
|
|
1142
|
-
`Recipient is required when allowed ticket is present`,
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
const privateObjectInfo = allowedTickets
|
|
1146
|
-
? { allowedTickets }
|
|
1147
|
-
: MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata
|
|
1148
|
-
? {
|
|
1149
|
-
recipient: recipient ?? "null",
|
|
1150
|
-
allowedTicket: metadata[MESSAGE_DATA_ALLOWED_TICKET_KEY],
|
|
1151
|
-
allowedIndex: metadata[MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY],
|
|
1152
|
-
}
|
|
1153
|
-
: undefined;
|
|
1154
|
-
|
|
1155
|
-
await this.objectEncoding.validate(
|
|
1156
|
-
object,
|
|
1157
|
-
receivedTags,
|
|
1158
|
-
objectBytes,
|
|
1159
|
-
privateObjectInfo,
|
|
1279
|
+
} else if (validationError instanceof GraffitiErrorNotFound) {
|
|
1280
|
+
// Item was deleted before we got a chance to
|
|
1281
|
+
// validate it. Just label the message as trash.
|
|
1282
|
+
this.inboxes.label(
|
|
1283
|
+
inboxEndpoint,
|
|
1284
|
+
messageId,
|
|
1285
|
+
MESSAGE_LABEL_TRASH,
|
|
1286
|
+
inboxToken,
|
|
1160
1287
|
);
|
|
1161
|
-
} catch (e) {
|
|
1162
|
-
validationError = e;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
if (tombstonedMessageId) {
|
|
1166
|
-
if (validationError instanceof GraffitiErrorNotFound) {
|
|
1167
|
-
// Not found == The tombstone is correct
|
|
1168
|
-
this.inboxes
|
|
1169
|
-
// Get the referenced message
|
|
1170
|
-
.get(inboxEndpoint, tombstonedMessageId, inboxToken)
|
|
1171
|
-
.then((result) => {
|
|
1172
|
-
if (
|
|
1173
|
-
// Make sure that it actually references the object being deleted
|
|
1174
|
-
result &&
|
|
1175
|
-
result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url ===
|
|
1176
|
-
object.url &&
|
|
1177
|
-
// And that the object is not already marked as trash
|
|
1178
|
-
result[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH
|
|
1179
|
-
) {
|
|
1180
|
-
// If valid but not yet trash, label the message as trash
|
|
1181
|
-
this.inboxes.label(
|
|
1182
|
-
inboxEndpoint,
|
|
1183
|
-
tombstonedMessageId,
|
|
1184
|
-
MESSAGE_LABEL_TRASH,
|
|
1185
|
-
inboxToken,
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
// Then, label the tombstone message as trash
|
|
1190
|
-
this.inboxes.label(
|
|
1191
|
-
inboxEndpoint,
|
|
1192
|
-
messageId,
|
|
1193
|
-
MESSAGE_LABEL_TRASH,
|
|
1194
|
-
inboxToken,
|
|
1195
|
-
);
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
|
-
yield {
|
|
1199
|
-
messageId,
|
|
1200
|
-
tombstone: true,
|
|
1201
|
-
object,
|
|
1202
|
-
storageBucketKey,
|
|
1203
|
-
allowedTickets,
|
|
1204
|
-
tags: receivedTags,
|
|
1205
|
-
announcements,
|
|
1206
|
-
};
|
|
1207
|
-
} else {
|
|
1208
|
-
console.error("Recieved an incorrect object");
|
|
1209
|
-
console.error(validationError);
|
|
1210
|
-
this.inboxes.label(
|
|
1211
|
-
inboxEndpoint,
|
|
1212
|
-
messageId,
|
|
1213
|
-
MESSAGE_LABEL_INVALID,
|
|
1214
|
-
inboxToken,
|
|
1215
|
-
);
|
|
1216
|
-
}
|
|
1217
1288
|
} else {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
messageId,
|
|
1227
|
-
object,
|
|
1228
|
-
storageBucketKey,
|
|
1229
|
-
tags: receivedTags,
|
|
1230
|
-
allowedTickets,
|
|
1231
|
-
announcements,
|
|
1232
|
-
};
|
|
1233
|
-
} else {
|
|
1234
|
-
console.error("Recieved an incorrect object");
|
|
1235
|
-
console.error(validationError);
|
|
1236
|
-
this.inboxes.label(
|
|
1237
|
-
inboxEndpoint,
|
|
1238
|
-
messageId,
|
|
1239
|
-
MESSAGE_LABEL_INVALID,
|
|
1240
|
-
inboxToken,
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1289
|
+
console.error("Recieved an incorrect object");
|
|
1290
|
+
console.error(validationError);
|
|
1291
|
+
this.inboxes.label(
|
|
1292
|
+
inboxEndpoint,
|
|
1293
|
+
messageId,
|
|
1294
|
+
MESSAGE_LABEL_INVALID,
|
|
1295
|
+
inboxToken,
|
|
1296
|
+
);
|
|
1243
1297
|
}
|
|
1244
1298
|
}
|
|
1245
1299
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../node_modules/@graffiti-garden/modal/src/style.css"],
|
|
4
|
-
"sourcesContent": [".graffiti-modal {\n --back: rgba(26, 26, 26, 0.85);\n --halfback: rgba(80, 80, 80, 0.85);\n --halfback2: rgba(26, 26, 26, 0.85);\n --hover: rgba(202, 122, 204, 0.3);\n --frontfaded: rgba(190, 190, 190);\n --front: rgba(240, 240, 240);\n --emph: rgb(202, 122, 204);\n --blurpix: 3px;\n border-color: var(--emph);\n box-sizing: border-box;\n border-width: 2px;\n padding: 0;\n margin: 0;\n border-radius: 1rem;\n box-shadow: 0 0 2rem black;\n overflow: hidden;\n opacity: 0;\n transition: opacity 0.3s;\n pointer-events: none;\n min-width: 95dvw;\n min-height: 95dvh;\n height: 95dvh;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: center;\n font-family:\n Inter,\n -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Oxygen,\n Ubuntu,\n Cantarell,\n \"Fira Sans\",\n \"Droid Sans\",\n \"Helvetica Neue\",\n sans-serif;\n color: var(--front);\n font-size: 150%;\n\n * {\n box-sizing: border-box;\n padding: 0;\n margin: 0;\n }\n\n ::selection {\n background: rgba(202, 122, 204, 0.3);\n }\n\n :focus {\n outline: 2px solid var(--front);\n }\n\n header {\n width: 100%;\n display: flex;\n justify-content: flex-end;\n }\n\n main {\n flex: 1;\n max-width: 600px;\n width: 100%;\n gap: 2em;\n padding-inline: clamp(1rem, 4dvw, 3rem);\n padding-block: clamp(1rem, 4dvh, 3rem);\n margin-inline: clamp(1rem, 4dvw, 3rem);\n margin-block: clamp(1rem, 4dvh, 3rem);\n background: var(--back);\n border-radius: 1rem;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n scrollbar-color: var(--emph) rgba(0, 0, 0, 0);\n }\n\n ul {\n list-style-type: none;\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n align-items: stretch;\n justify-content: stretch;\n }\n\n aside {\n color: var(--frontfaded);\n }\n\n .secondary,\n a:not([type=\"button\"]) {\n color: var(--emph);\n }\n\n h1 {\n font-size: 120%;\n font-family:\n Rock Salt,\n cursive,\n sans-serif;\n letter-spacing: 0.1em;\n text-align: center;\n color: var(--front);\n }\n\n h1 a:not([type=\"button\"]) {\n color: inherit;\n }\n\n h1 a:hover {\n background: none;\n color: inherit;\n }\n\n button,\n input[type=\"submit\"],\n input[type=\"text\"],\n a[type=\"button\"] {\n font-size: inherit;\n width: 100%;\n text-align: center;\n display: block;\n border-radius: 1rem;\n border: 2px solid var(--emph);\n padding: 1em;\n padding-top: 0.5em;\n padding-bottom: 0.5em;\n transition: 0.1s;\n text-overflow: ellipsis;\n background: none;\n line-height: 1.2em;\n }\n\n input[type=\"text\"] {\n font-weight: 500;\n background: var(--front);\n text-align: left;\n color: black;\n }\n\n header button {\n border-radius: 0 0 0 1rem;\n border-right: none;\n border-top: none;\n background-color: var(--halfback2);\n width: fit-content;\n position: relative;\n overflow: hidden;\n }\n\n header button::before {\n z-index: -1;\n top: 0;\n left: 0;\n height: 100%;\n position: absolute;\n width: 100%;\n content: \"\";\n background-color: var(--back);\n }\n\n @media (max-height: 600px) {\n main {\n gap: 1em;\n }\n\n h1 {\n font-size: 100%;\n }\n }\n\n @media (max-width: 600px) {\n main {\n border-radius: 0;\n margin: auto;\n overflow: auto;\n }\n\n header button {\n border-radius: 0;\n border-left: none;\n width: 100%;\n }\n\n html {\n justify-content: safe center;\n }\n }\n\n a {\n text-decoration: none;\n }\n\n :is(button, ul a, input[type=\"submit\"]):hover {\n cursor: pointer;\n background: var(--hover);\n color: var(--front);\n }\n\n a:hover {\n text-decoration: underline;\n cursor: pointer;\n }\n\n form {\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: stretch;\n gap: 0.5em;\n }\n\n iframe {\n width: 100%;\n height: 100%;\n border: none;\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]).secondary {\n color: rgb(244, 213, 244);\n background: rgba(26, 26, 26, 0.6);\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]):not(.secondary) {\n background: var(--hover);\n color: white;\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]):hover {\n background: rgba(202, 122, 204, 0.6);\n color: white;\n text-decoration: none;\n }\n}\n\n.graffiti-modal[open] {\n pointer-events: inherit;\n opacity: 1;\n}\n\n.graffiti-modal::backdrop {\n background-color: black;\n opacity: 0.5;\n}\n\n.graffiti-modal::before {\n content: \"\";\n position: fixed;\n left: 0;\n right: 0;\n z-index: -1;\n background-image: url(graffiti.jpg);\n background-size: cover;\n background-repeat: no-repeat;\n background-position: 50% 50%;\n height: calc(100% + 2 * var(--blurpix));\n width: calc(100% + 2 * var(--blurpix));\n filter: blur(var(--blurpix));\n margin: calc(-1 * var(--blurpix));\n}\n"],
|
|
5
|
-
"mappings": "4BAAA,IAAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
|
|
6
|
-
"names": ["style_default"]
|
|
7
|
-
}
|