generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 56
/
Copy pathutils.ts
173 lines (144 loc) · 5.94 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import type { DidUrlDereferencer } from '@web5/dids';
import { PaginationCursor, RecordsDeleteMessage, RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js';
import { Readable } from '@web5/common';
import { utils as didUtils } from '@web5/dids';
import { ReadableWebToNodeStream } from 'readable-web-to-node-stream';
import { DateSort, DwnInterfaceName, DwnMethodName, Message, Records, RecordsWrite } from '@tbd54566975/dwn-sdk-js';
export function blobToIsomorphicNodeReadable(blob: Blob): Readable {
return webReadableToIsomorphicNodeReadable(blob.stream() as ReadableStream<any>);
}
export async function getDwnServiceEndpointUrls(didUri: string, dereferencer: DidUrlDereferencer): Promise<string[]> {
// Attempt to dereference the DID service with ID fragment #dwn.
const dereferencingResult = await dereferencer.dereference(`${didUri}#dwn`);
if (dereferencingResult.dereferencingMetadata.error) {
throw new Error(`Failed to dereference '${didUri}#dwn': ${dereferencingResult.dereferencingMetadata.error}`);
}
if (didUtils.isDwnDidService(dereferencingResult.contentStream)) {
const { serviceEndpoint } = dereferencingResult.contentStream;
const serviceEndpointUrls = typeof serviceEndpoint === 'string'
// If the service endpoint is a string, format it as a single-element array.
? [serviceEndpoint]
: Array.isArray(serviceEndpoint) && serviceEndpoint.every(endpoint => typeof endpoint === 'string')
// If the service endpoint is an array of strings, use it as is.
? serviceEndpoint as string[]
// If the service endpoint is neither a string nor an array of strings, return an empty array.
: [];
if (serviceEndpointUrls.length > 0) {
return serviceEndpointUrls;
}
}
// If the DID service with ID fragment #dwn was not found or is not valid, return an empty array.
return [];
}
export function getRecordAuthor(record: RecordsWriteMessage | RecordsDeleteMessage): string | undefined {
return Message.getAuthor(record);
}
export function isRecordsWrite(obj: unknown): obj is RecordsWrite {
// Validate that the given value is an object.
if (!obj || typeof obj !== 'object' || obj === null) return false;
// Validate that the object has the necessary properties of RecordsWrite.
return (
'message' in obj && typeof obj.message === 'object' && obj.message !== null &&
'descriptor' in obj.message && typeof obj.message.descriptor === 'object' && obj.message.descriptor !== null &&
'interface' in obj.message.descriptor && obj.message.descriptor.interface === DwnInterfaceName.Records &&
'method' in obj.message.descriptor && obj.message.descriptor.method === DwnMethodName.Write
);
}
/**
* Get the CID of the given RecordsWriteMessage.
*/
export function getRecordMessageCid(message: RecordsWriteMessage): Promise<string> {
return Message.getCid(message);
}
/**
* Get the pagination cursor for the given RecordsWriteMessage and DateSort.
*
* @param message The RecordsWriteMessage for which to get the pagination cursor.
* @param dateSort The date sort that will be used in the query or subscription to which the cursor will be applied.
*/
export async function getPaginationCursor(message: RecordsWriteMessage, dateSort: DateSort): Promise<PaginationCursor> {
const value = dateSort === DateSort.CreatedAscending || dateSort === DateSort.CreatedDescending ?
message.descriptor.dateCreated : message.descriptor.datePublished;
if (value === undefined) {
throw new Error('The dateCreated or datePublished property is missing from the record descriptor.');
}
return {
messageCid: await getRecordMessageCid(message),
value
};
}
export function webReadableToIsomorphicNodeReadable(webReadable: ReadableStream<any>) {
return new ReadableWebToNodeStream(webReadable);
}
/**
* Polling function with interval, TTL accepting a custom fetch function
* @template T - the return you expect from the fetcher
* @param fetchFunction an http fetch function
* @param [interval=3000] how frequently to poll
* @param [ttl=300_000] how long until polling stops
* @returns T - the result of fetch
*/
export function pollWithTtl(
fetchFunction: () => Promise<Response>,
interval = 3000,
ttl = 300_000,
abortSignal?: AbortSignal
): Promise<Response | null> {
const endTime = Date.now() + ttl;
let timeoutId: NodeJS.Timeout | null = null;
let isPolling = true;
return new Promise((resolve, reject) => {
if (abortSignal) {
abortSignal.addEventListener('abort', () => {
isPolling = false;
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
console.log('Polling aborted by user');
resolve(null);
});
}
async function poll() {
if (!isPolling) return;
const remainingTime = endTime - Date.now();
if (remainingTime <= 0) {
isPolling = false;
console.log('Polling stopped: TTL reached');
resolve(null);
return;
}
console.log(`Polling... (Remaining time: ${Math.ceil(remainingTime / 1000)}s)`);
try {
const response = await fetchFunction();
if (response.ok) {
isPolling = false;
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
console.log('Polling stopped: Success condition met');
resolve(response);
return;
}
} catch (error) {
console.error('Error fetching data:', error);
reject(error);
}
if (isPolling) {
timeoutId = setTimeout(poll, interval);
}
}
poll();
});
}
/** Concatenates a base URL and a path ensuring that there is exactly one slash between them */
export function concatenateUrl(baseUrl: string, path: string): string {
// Remove trailing slash from baseUrl if it exists
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, -1);
}
// Remove leading slash from path if it exists
if (path.startsWith('/')) {
path = path.slice(1);
}
return `${baseUrl}/${path}`;
}