@enbox/browser 0.0.1
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/README.md +61 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web-features.js +481 -0
- package/dist/esm/web-features.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/web-features.d.ts +31 -0
- package/dist/types/web-features.d.ts.map +1 -0
- package/package.json +83 -0
- package/src/index.ts +1 -0
- package/src/web-features.ts +489 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Web5 Browser package
|
|
2
|
+
|
|
3
|
+
| Web5 tools and features to use in the browser |
|
|
4
|
+
| --------------------------------------------- |
|
|
5
|
+
|
|
6
|
+
[![NPM Package][browser-npm-badge]][browser-npm-link]
|
|
7
|
+
[![NPM Downloads][browser-downloads-badge]][browser-npm-link]
|
|
8
|
+
|
|
9
|
+
[![Build Status][browser-build-badge]][browser-build-link]
|
|
10
|
+
[![Open Issues][browser-issues-badge]][browser-issues-link]
|
|
11
|
+
[![Code Coverage][browser-coverage-badge]][browser-coverage-link]
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
- [Web5 Browser](#introduction)
|
|
16
|
+
- [Activate Polyfills](#activate-polyfills)
|
|
17
|
+
- [Project Resources](#project-resources)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<a id="introduction"></a>
|
|
22
|
+
|
|
23
|
+
This package contains browser-specific helpers for building DWAs (Decentralized Web Apps).
|
|
24
|
+
|
|
25
|
+
### Activate Polyfills
|
|
26
|
+
|
|
27
|
+
This enables a service worker that can handle Web5 features in the browser such as resolving DRLs that look like: `http://dweb/did:dht:abc123/protocols/read/aHR0cHM6Ly9hcmV3ZXdlYjV5ZXQuY29tL3NjaGVtYXMvcHJvdG9jb2xz/avatar`
|
|
28
|
+
|
|
29
|
+
To enable this functionality import and run `activatePolyfills()` at the entrypoint of your project, or within an existing service worker.
|
|
30
|
+
|
|
31
|
+
## Project Resources
|
|
32
|
+
|
|
33
|
+
| Resource | Description |
|
|
34
|
+
| --------------------------------------- | ----------------------------------------------------------------------------- |
|
|
35
|
+
| [CODEOWNERS][codeowners-link] | Outlines the project lead(s) |
|
|
36
|
+
| [CODE OF CONDUCT][code-of-conduct-link] | Expected behavior for project contributors, promoting a welcoming environment |
|
|
37
|
+
| [CONTRIBUTING][contributing-link] | Developer guide to build, test, run, access CI, chat, discuss, file issues |
|
|
38
|
+
| [GOVERNANCE][governance-link] | Project governance |
|
|
39
|
+
| [LICENSE][license-link] | Apache License, Version 2.0 |
|
|
40
|
+
|
|
41
|
+
[browser-npm-badge]: https://img.shields.io/npm/v/@enbox/browser.svg?style=flat&color=blue&santize=true
|
|
42
|
+
[browser-npm-link]: https://www.npmjs.com/package/@enbox/browser
|
|
43
|
+
[browser-downloads-badge]: https://img.shields.io/npm/dt/@enbox/browser?&color=blue
|
|
44
|
+
[browser-build-badge]: https://img.shields.io/github/actions/workflow/status/enboxorg/enbox/enbox/tests-ci.yml?branch=main&label=build
|
|
45
|
+
[browser-build-link]: https://github.com/enboxorg/enbox/enbox/actions/workflows/tests-ci.yml
|
|
46
|
+
[browser-coverage-badge]: https://img.shields.io/codecov/c/gh/enboxorg/enbox/enbox/main?style=flat&token=YI87CKF1LI
|
|
47
|
+
[browser-coverage-link]: https://app.codecov.io/github/enboxorg/enbox/enbox/tree/main/packages%2Fbrowser
|
|
48
|
+
[browser-issues-badge]: https://img.shields.io/github/issues/enboxorg/enbox/enbox/package:%20browser?label=issues
|
|
49
|
+
[browser-issues-link]: https://github.com/enboxorg/enbox/enbox/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+browser"
|
|
50
|
+
[browser-repo-link]: https://github.com/enboxorg/enbox/enbox/tree/main/packages/browser
|
|
51
|
+
[browser-jsdelivr-link]: https://www.jsdelivr.com/package/npm/@enbox/browser
|
|
52
|
+
[browser-jsdelivr-browser]: https://cdn.jsdelivr.net/npm/@enbox/browser/dist/browser.mjs
|
|
53
|
+
[browser-unpkg-link]: https://unpkg.com/@enbox/browser
|
|
54
|
+
[browser-unpkg-browser]: https://unpkg.com/@enbox/browser/dist/browser.mjs
|
|
55
|
+
[codeowners-link]: https://github.com/enboxorg/enbox/enbox/blob/main/CODEOWNERS
|
|
56
|
+
[code-of-conduct-link]: https://github.com/enboxorg/enbox/enbox/blob/main/CODE_OF_CONDUCT.md
|
|
57
|
+
[contributing-link]: https://github.com/enboxorg/enbox/enbox/blob/main/CONTRIBUTING.md
|
|
58
|
+
[governance-link]: https://github.com/enboxorg/enbox/enbox/blob/main/GOVERNANCE.md
|
|
59
|
+
[license-link]: https://github.com/enboxorg/enbox/enbox/blob/main/LICENSE
|
|
60
|
+
[discord-badge]: https://img.shields.io/discord/937858703112155166?color=5865F2&logo=discord&logoColor=white
|
|
61
|
+
[discord-link]: https://discord.com/channels/937858703112155166/969272658501976117
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
/*
|
|
12
|
+
This file is run in dual environments to make installation of the Service Worker code easier.
|
|
13
|
+
Be mindful that code placed in any open excution space may be evaluated multiple times in different contexts,
|
|
14
|
+
so take care to gate additions to only activate code in the right env, such as a Service Worker scope or page window.
|
|
15
|
+
*/
|
|
16
|
+
import { UniversalResolver, DidDht, DidWeb } from '@enbox/dids';
|
|
17
|
+
// This is in place to prevent our `bundler-bonanza` repo from failing for Node CJS builds
|
|
18
|
+
// Not sure if this is working as expected in all environments, crated an issue
|
|
19
|
+
// TODO: https://github.com/TBD54566975/web5-js/issues/767
|
|
20
|
+
function importMetaIfSupported() {
|
|
21
|
+
try {
|
|
22
|
+
return new Function('return import.meta')();
|
|
23
|
+
}
|
|
24
|
+
catch (_error) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const DidResolver = new UniversalResolver({ didResolvers: [DidDht, DidWeb] });
|
|
29
|
+
const didUrlRegex = /^https?:\/\/dweb\/([^/]+)\/?(.*)?$/;
|
|
30
|
+
const httpToHttpsRegex = /^http:/;
|
|
31
|
+
const trailingSlashRegex = /\/$/;
|
|
32
|
+
function getDwnEndpoints(did) {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
const { didDocument } = yield DidResolver.resolve(did);
|
|
36
|
+
const endpoints = (_b = (_a = didDocument === null || didDocument === void 0 ? void 0 : didDocument.service) === null || _a === void 0 ? void 0 : _a.find((service) => service.type === 'DecentralizedWebNode')) === null || _b === void 0 ? void 0 : _b.serviceEndpoint;
|
|
37
|
+
return (Array.isArray(endpoints) ? endpoints : [endpoints]).filter((url) => url.startsWith('http'));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function handleEvent(event, did, path, options) {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
const drl = event.request.url
|
|
43
|
+
.replace(httpToHttpsRegex, 'https:')
|
|
44
|
+
.replace(trailingSlashRegex, '');
|
|
45
|
+
const responseCache = yield caches.open('drl');
|
|
46
|
+
const cachedResponse = yield responseCache.match(drl);
|
|
47
|
+
if (cachedResponse) {
|
|
48
|
+
if (!navigator.onLine)
|
|
49
|
+
return cachedResponse;
|
|
50
|
+
const match = yield (options === null || options === void 0 ? void 0 : options.onCacheCheck(event, drl));
|
|
51
|
+
if (match) {
|
|
52
|
+
const cacheTime = cachedResponse.headers.get('dwn-cache-time');
|
|
53
|
+
if (cacheTime &&
|
|
54
|
+
Date.now() < Number(cacheTime) + (Number(match.ttl) || 0)) {
|
|
55
|
+
return cachedResponse;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (!path) {
|
|
61
|
+
const response = yield DidResolver.resolve(did);
|
|
62
|
+
return new Response(JSON.stringify(response), {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
return yield fetchResource(event, did, drl, path, responseCache, options);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof Response) {
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
console.log(`Error in DID URL fetch: ${error}`);
|
|
77
|
+
return new Response('DID URL fetch error', { status: 500 });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function fetchResource(event, did, drl, path, responseCache, options) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
const endpoints = yield getDwnEndpoints(did);
|
|
84
|
+
if (!(endpoints === null || endpoints === void 0 ? void 0 : endpoints.length)) {
|
|
85
|
+
throw new Response('DWeb Node resolution failed: no valid endpoints found.', { status: 530 });
|
|
86
|
+
}
|
|
87
|
+
for (const endpoint of endpoints) {
|
|
88
|
+
try {
|
|
89
|
+
const url = `${endpoint.replace(trailingSlashRegex, '')}/${did}/${path}`;
|
|
90
|
+
const response = yield fetch(url, { headers: event.request.headers });
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
const match = yield (options === null || options === void 0 ? void 0 : options.onCacheCheck(event, drl));
|
|
93
|
+
if (match) {
|
|
94
|
+
cacheResponse(drl, url, response, responseCache);
|
|
95
|
+
}
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
console.log(`DWN endpoint error: ${response.status}`);
|
|
99
|
+
return new Response('DWeb Node request failed', {
|
|
100
|
+
status: response.status,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.log(`DWN endpoint error: ${error}`);
|
|
105
|
+
return new Response('DWeb Node request failed: ' + error, {
|
|
106
|
+
status: 500,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function cacheResponse(drl, url, response, cache) {
|
|
113
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
const clonedResponse = response.clone();
|
|
115
|
+
const headers = new Headers(clonedResponse.headers);
|
|
116
|
+
headers.append('dwn-cache-time', Date.now().toString());
|
|
117
|
+
headers.append('dwn-composed-url', url);
|
|
118
|
+
const modifiedResponse = new Response(clonedResponse.body, { headers });
|
|
119
|
+
cache.put(drl, modifiedResponse);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/* Service Worker-based features */
|
|
123
|
+
function installWorker(options = {}) {
|
|
124
|
+
var _a, _b, _c;
|
|
125
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
const workerSelf = self;
|
|
127
|
+
try {
|
|
128
|
+
// Check to see if we are in a Service Worker already, if so, proceed
|
|
129
|
+
// You can call the activatePolyfills() function in your own worker, or standalone as a root worker
|
|
130
|
+
if (typeof ServiceWorkerGlobalScope !== 'undefined' &&
|
|
131
|
+
workerSelf instanceof ServiceWorkerGlobalScope) {
|
|
132
|
+
workerSelf.skipWaiting();
|
|
133
|
+
workerSelf.addEventListener('activate', (event) => {
|
|
134
|
+
// Claim clients to make the service worker take control immediately
|
|
135
|
+
event.waitUntil(workerSelf.clients.claim());
|
|
136
|
+
});
|
|
137
|
+
workerSelf.addEventListener('fetch', (event) => {
|
|
138
|
+
const match = event.request.url.match(didUrlRegex);
|
|
139
|
+
if (match) {
|
|
140
|
+
event.respondWith(handleEvent(event, match[1], match[2], options));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// If the code gets here, it is not a SW env, it is likely DOM, but check to be sure
|
|
145
|
+
else if ((_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _a === void 0 ? void 0 : _a.serviceWorker) {
|
|
146
|
+
const registration = yield navigator.serviceWorker.getRegistration('/');
|
|
147
|
+
// You can only have one worker per path, so check to see if one is already registered
|
|
148
|
+
if (!registration) {
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
const installUrl = options.path ||
|
|
151
|
+
(globalThis.document
|
|
152
|
+
? (_b = document === null || document === void 0 ? void 0 : document.currentScript) === null || _b === void 0 ? void 0 : _b.src
|
|
153
|
+
: (_c = importMetaIfSupported()) === null || _c === void 0 ? void 0 : _c.url);
|
|
154
|
+
if (installUrl)
|
|
155
|
+
navigator.serviceWorker
|
|
156
|
+
.register(installUrl, { type: 'module' })
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
console.error('DWeb networking feature installation failed: ', error);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
throw new Error('DWeb networking features are not available for install in this environment');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error('Error in installing networking features:', error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/* DOM Environment Features */
|
|
172
|
+
const loaderStyles = `
|
|
173
|
+
.drl-loading-overlay {
|
|
174
|
+
position: fixed;
|
|
175
|
+
inset: 0;
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-wrap: wrap;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
font-size: 22px;
|
|
182
|
+
color: #fff;
|
|
183
|
+
background: rgba(0, 0, 0, 0.75);
|
|
184
|
+
backdrop-filter: blur(15px);
|
|
185
|
+
-webkit-backdrop-filter: blur(15px);
|
|
186
|
+
z-index: 1000000;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.drl-loading-overlay > div {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.drl-loading-spinner {
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
justify-content: center;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.drl-loading-spinner div {
|
|
202
|
+
position: relative;
|
|
203
|
+
width: 2em;
|
|
204
|
+
height: 2em;
|
|
205
|
+
margin: 0.1em 0.25em 0 0;
|
|
206
|
+
}
|
|
207
|
+
.drl-loading-spinner div::after,
|
|
208
|
+
.drl-loading-spinner div::before {
|
|
209
|
+
content: '';
|
|
210
|
+
box-sizing: border-box;
|
|
211
|
+
width: 100%;
|
|
212
|
+
height: 100%;
|
|
213
|
+
border-radius: 50%;
|
|
214
|
+
border: 0.1em solid #FFF;
|
|
215
|
+
position: absolute;
|
|
216
|
+
left: 0;
|
|
217
|
+
top: 0;
|
|
218
|
+
opacity: 0;
|
|
219
|
+
animation: drl-loading-spinner 2s linear infinite;
|
|
220
|
+
}
|
|
221
|
+
.drl-loading-spinner div::after {
|
|
222
|
+
animation-delay: 1s;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.drl-loading-overlay span {
|
|
226
|
+
--text-opacity: 2;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
margin: 2em auto 0;
|
|
230
|
+
padding: 0.2em 0.75em 0.25em;
|
|
231
|
+
text-align: center;
|
|
232
|
+
border-radius: 5em;
|
|
233
|
+
background: rgba(255 255 255 / 8%);
|
|
234
|
+
opacity: 0.8;
|
|
235
|
+
transition: opacity 0.3s ease;
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.drl-loading-overlay span:focus {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.drl-loading-overlay span:hover {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.drl-loading-overlay span::before {
|
|
248
|
+
content: '✕ ';
|
|
249
|
+
margin: 0 0.4em 0 0;
|
|
250
|
+
color: red;
|
|
251
|
+
font-size: 65%;
|
|
252
|
+
font-weight: bold;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.drl-loading-overlay span::after {
|
|
256
|
+
content: 'stop';
|
|
257
|
+
display: block;
|
|
258
|
+
font-size: 60%;
|
|
259
|
+
line-height: 0;
|
|
260
|
+
color: rgba(255 255 255 / 60%);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.drl-loading-overlay.new-tab-overlay span::after {
|
|
264
|
+
content: 'close';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@keyframes drl-loading-spinner {
|
|
268
|
+
0% {
|
|
269
|
+
transform: scale(0);
|
|
270
|
+
opacity: 1;
|
|
271
|
+
}
|
|
272
|
+
100% {
|
|
273
|
+
transform: scale(1);
|
|
274
|
+
opacity: 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
const tabContent = `
|
|
279
|
+
<!DOCTYPE html>
|
|
280
|
+
<html lang='en'>
|
|
281
|
+
<head>
|
|
282
|
+
<meta charset='UTF-8'>
|
|
283
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
284
|
+
<title>Loading DRL...</title>
|
|
285
|
+
<style>
|
|
286
|
+
html, body {
|
|
287
|
+
background-color: #151518;
|
|
288
|
+
height: 100%;
|
|
289
|
+
margin: 0;
|
|
290
|
+
padding: 0;
|
|
291
|
+
font-family: Arial, sans-serif;
|
|
292
|
+
text-align: center;
|
|
293
|
+
}
|
|
294
|
+
${loaderStyles}
|
|
295
|
+
</style>
|
|
296
|
+
</head>
|
|
297
|
+
<body>
|
|
298
|
+
<div class='drl-loading-overlay new-tab-overlay'>
|
|
299
|
+
<div class='drl-loading-spinner'>
|
|
300
|
+
<div></div>
|
|
301
|
+
Loading DRL
|
|
302
|
+
</div>
|
|
303
|
+
<span onclick='window.close()' tabindex='0'></span>
|
|
304
|
+
</div>
|
|
305
|
+
</body>
|
|
306
|
+
</html>
|
|
307
|
+
`;
|
|
308
|
+
let elementsInjected = false;
|
|
309
|
+
function injectElements() {
|
|
310
|
+
if (elementsInjected)
|
|
311
|
+
return;
|
|
312
|
+
const style = document.createElement('style');
|
|
313
|
+
style.innerHTML = `
|
|
314
|
+
${loaderStyles}
|
|
315
|
+
|
|
316
|
+
.drl-loading-overlay {
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transition: opacity 0.3s ease;
|
|
319
|
+
pointer-events: none;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
:root[drl-link-loading] .drl-loading-overlay {
|
|
323
|
+
opacity: 1;
|
|
324
|
+
pointer-events: all;
|
|
325
|
+
}
|
|
326
|
+
`;
|
|
327
|
+
document.head.append(style);
|
|
328
|
+
const overlay = document.createElement('div');
|
|
329
|
+
overlay.classList.add('drl-loading-overlay');
|
|
330
|
+
overlay.innerHTML = `
|
|
331
|
+
<div class='drl-loading-spinner'>
|
|
332
|
+
<div></div>
|
|
333
|
+
Loading DRL
|
|
334
|
+
</div>
|
|
335
|
+
<span tabindex='0'></span>
|
|
336
|
+
`;
|
|
337
|
+
overlay.lastElementChild.addEventListener('click', cancelNavigation);
|
|
338
|
+
document.body.prepend(overlay);
|
|
339
|
+
elementsInjected = true;
|
|
340
|
+
}
|
|
341
|
+
function cancelNavigation() {
|
|
342
|
+
document.documentElement.removeAttribute('drl-link-loading');
|
|
343
|
+
activeNavigation = null;
|
|
344
|
+
}
|
|
345
|
+
let activeNavigation;
|
|
346
|
+
let linkFeaturesActive = false;
|
|
347
|
+
function addLinkFeatures() {
|
|
348
|
+
if (!linkFeaturesActive) {
|
|
349
|
+
document.addEventListener('click', (event) => __awaiter(this, void 0, void 0, function* () {
|
|
350
|
+
const anchor = event.target.closest('a');
|
|
351
|
+
if (anchor) {
|
|
352
|
+
const href = anchor.href;
|
|
353
|
+
const match = href.match(didUrlRegex);
|
|
354
|
+
if (match) {
|
|
355
|
+
const did = match[1];
|
|
356
|
+
const path = match[2];
|
|
357
|
+
const openAsTab = anchor.target === '_blank';
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
try {
|
|
360
|
+
let tab;
|
|
361
|
+
if (openAsTab) {
|
|
362
|
+
tab = window.open('', '_blank');
|
|
363
|
+
tab.document.write(tabContent);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
activeNavigation = path;
|
|
367
|
+
// this is to allow for cached DIDs to instantly load without any flash of loading UI
|
|
368
|
+
setTimeout(() => document.documentElement.setAttribute('drl-link-loading', ''), 50);
|
|
369
|
+
}
|
|
370
|
+
const endpoints = yield getDwnEndpoints(did);
|
|
371
|
+
if (!endpoints.length)
|
|
372
|
+
throw null;
|
|
373
|
+
const url = `${endpoints[0].replace(trailingSlashRegex, '')}/${did}/${path}`;
|
|
374
|
+
if (openAsTab) {
|
|
375
|
+
if (!tab.closed)
|
|
376
|
+
tab.location.href = url;
|
|
377
|
+
}
|
|
378
|
+
else if (activeNavigation === path) {
|
|
379
|
+
window.location.href = url;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
if (activeNavigation === path) {
|
|
384
|
+
cancelNavigation();
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`DID endpoint resolution failed for the DRL: ${href}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}));
|
|
391
|
+
document.addEventListener('pointercancel', resetContextMenuTarget);
|
|
392
|
+
document.addEventListener('pointerdown', (event) => __awaiter(this, void 0, void 0, function* () {
|
|
393
|
+
var _a;
|
|
394
|
+
const target = event.composedPath()[0];
|
|
395
|
+
if ((event.pointerType === 'mouse' && event.button === 2) ||
|
|
396
|
+
(event.pointerType === 'touch' && event.isPrimary)) {
|
|
397
|
+
resetContextMenuTarget();
|
|
398
|
+
if (target && ((_a = target === null || target === void 0 ? void 0 : target.src) === null || _a === void 0 ? void 0 : _a.match(didUrlRegex))) {
|
|
399
|
+
contextMenuTarget = target;
|
|
400
|
+
target.__src__ = target.src;
|
|
401
|
+
const drl = target.src
|
|
402
|
+
.replace(httpToHttpsRegex, 'https:')
|
|
403
|
+
.replace(trailingSlashRegex, '');
|
|
404
|
+
const responseCache = yield caches.open('drl');
|
|
405
|
+
const response = yield responseCache.match(drl);
|
|
406
|
+
const url = response.headers.get('dwn-composed-url');
|
|
407
|
+
if (url)
|
|
408
|
+
target.src = url;
|
|
409
|
+
target.addEventListener('pointerup', resetContextMenuTarget, {
|
|
410
|
+
once: true,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else if (target === contextMenuTarget) {
|
|
415
|
+
resetContextMenuTarget();
|
|
416
|
+
}
|
|
417
|
+
}));
|
|
418
|
+
linkFeaturesActive = true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let contextMenuTarget;
|
|
422
|
+
function resetContextMenuTarget(e) {
|
|
423
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
424
|
+
if ((e === null || e === void 0 ? void 0 : e.type) === 'pointerup') {
|
|
425
|
+
yield new Promise((r) => requestAnimationFrame(r));
|
|
426
|
+
}
|
|
427
|
+
if (contextMenuTarget) {
|
|
428
|
+
contextMenuTarget.src = contextMenuTarget.__src__;
|
|
429
|
+
delete contextMenuTarget.__src__;
|
|
430
|
+
contextMenuTarget = null;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Activates various polyfills to enable Web5 features in Web environments.
|
|
436
|
+
*
|
|
437
|
+
* @param {object} [options={}] - Configuration options to control the activation of polyfills.
|
|
438
|
+
* @param {boolean} [options.serviceWorker=true] - Option to avoid installation of the Service Worker. Defaults to true, installing the Service Worker.
|
|
439
|
+
* @param {boolean} [options.injectStyles=true] - Option to skip injection of styles for UI related UX polyfills. Defaults to true, injecting styles.
|
|
440
|
+
* @param {boolean} [options.links=true] - Option to skip activation of DRL link features. Defaults to true, activating link features.
|
|
441
|
+
* @param {function} [options.onCacheCheck] - Callback function to handle cache check events, allowing fine-grained control over what DRL request to cache, and for how long.
|
|
442
|
+
* @param {object} [options.onCacheCheck.event] - The event object passed to the callback.
|
|
443
|
+
* @param {object} [options.onCacheCheck.route] - The route object passed to the callback.
|
|
444
|
+
* @returns {object} [options.onCacheCheck.return] - The return object from the callback.
|
|
445
|
+
* @returns {number} [options.onCacheCheck.return.ttl] - Time-to-live for the cached DRL response, in milliseconds.
|
|
446
|
+
*
|
|
447
|
+
* @returns {void}
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* // Activate all polyfills with default options, and cache every DRL for 1 minute
|
|
451
|
+
* activatePolyfills({
|
|
452
|
+
* onCacheCheck(event, route){
|
|
453
|
+
* return {
|
|
454
|
+
* ttl: 60_000
|
|
455
|
+
* }
|
|
456
|
+
* }
|
|
457
|
+
* });
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* // Activate polyfills, but without Service Worker activation
|
|
461
|
+
* activatePolyfills({ serviceWorker: false });
|
|
462
|
+
*/
|
|
463
|
+
export function activatePolyfills(options = {}) {
|
|
464
|
+
if (options.serviceWorker !== false) {
|
|
465
|
+
installWorker(options);
|
|
466
|
+
}
|
|
467
|
+
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
|
|
468
|
+
if (options.injectStyles !== false) {
|
|
469
|
+
if (document.readyState !== 'loading')
|
|
470
|
+
injectElements();
|
|
471
|
+
else {
|
|
472
|
+
document.addEventListener('DOMContentLoaded', injectElements, {
|
|
473
|
+
once: true,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (options.links !== false)
|
|
478
|
+
addLinkFeatures();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=web-features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-features.js","sourceRoot":"","sources":["../../src/web-features.ts"],"names":[],"mappings":"AAAA,aAAa;;;;;;;;;;AAEb;;;;EAIE;AAEF,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEhE,0FAA0F;AAC1F,+EAA+E;AAC/E,0DAA0D;AAC1D,SAAS,qBAAqB;IAC5B,IAAI;QACF,OAAO,IAAI,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;KAC7C;IAAC,OAAO,MAAM,EAAE;QACf,OAAO,SAAS,CAAC;KAClB;AACH,CAAC;AAID,MAAM,WAAW,GAAG,IAAI,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;AAC9E,MAAM,WAAW,GAAG,oCAAoC,CAAC;AACzD,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAClC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAEjC,SAAe,eAAe,CAAC,GAAG;;;QAChC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,MAAA,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,0CAAE,IAAI,CAC1C,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,sBAAsB,CACrD,0CAAE,eAAe,CAAC;QACnB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CACzE,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CACvB,CAAC;;CACH;AAED,SAAe,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO;;QAClD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG;aAC1B,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC;aACnC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,cAAc,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtD,IAAI,cAAc,EAAE;YAClB,IAAI,CAAC,SAAS,CAAC,MAAM;gBAAE,OAAO,cAAc,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA,CAAC;YACtD,IAAI,KAAK,EAAE;gBACT,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAC/D,IACE,SAAS;oBACT,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EACzD;oBACA,OAAO,cAAc,CAAC;iBACvB;aACF;SACF;QACD,IAAI;YACF,IAAI,CAAC,IAAI,EAAE;gBACT,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE;oBAC5C,MAAM,EAAI,GAAG;oBACb,OAAO,EAAG;wBACR,cAAc,EAAE,kBAAkB;qBACnC;iBACF,CAAC,CAAC;aACJ;;gBACC,OAAO,MAAM,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;SAC7E;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,QAAQ,EAAE;gBAC7B,OAAO,KAAK,CAAC;aACd;YACD,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,EAAE,CAAC,CAAC;YAChD,OAAO,IAAI,QAAQ,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;SAC7D;IACH,CAAC;CAAA;AAED,SAAe,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO;;QACxE,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAA,EAAE;YACtB,MAAM,IAAI,QAAQ,CAChB,wDAAwD,EACxD,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;SACH;QACD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE;YAChC,IAAI;gBACF,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;gBACzE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBACtE,IAAI,QAAQ,CAAC,EAAE,EAAE;oBACf,MAAM,KAAK,GAAG,MAAM,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA,CAAC;oBACtD,IAAI,KAAK,EAAE;wBACT,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;qBAClD;oBACD,OAAO,QAAQ,CAAC;iBACjB;gBACD,OAAO,CAAC,GAAG,CAAC,uBAAuB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBACtD,OAAO,IAAI,QAAQ,CAAC,0BAA0B,EAAE;oBAC9C,MAAM,EAAE,QAAQ,CAAC,MAAM;iBACxB,CAAC,CAAC;aACJ;YAAC,OAAO,KAAK,EAAE;gBACd,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;gBAC5C,OAAO,IAAI,QAAQ,CAAC,4BAA4B,GAAG,KAAK,EAAE;oBACxD,MAAM,EAAE,GAAG;iBACZ,CAAC,CAAC;aACJ;SACF;IACH,CAAC;CAAA;AAED,SAAe,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK;;QACpD,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,gBAAgB,GAAG,IAAI,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IACnC,CAAC;CAAA;AAED,mCAAmC;AAEnC,SAAe,aAAa,CAAC,UAAe,EAAE;;;QAC5C,MAAM,UAAU,GAAG,IAAW,CAAC;QAC/B,IAAI;YACF,qEAAqE;YACrE,mGAAmG;YACnG,IACE,OAAO,wBAAwB,KAAK,WAAW;gBAC/C,UAAU,YAAY,wBAAwB,EAC9C;gBACA,UAAU,CAAC,WAAW,EAAE,CAAC;gBACzB,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;oBAChD,oEAAoE;oBACpE,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC9C,CAAC,CAAC,CAAC;gBACH,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;oBACnD,IAAI,KAAK,EAAE;wBACT,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;qBACpE;gBACH,CAAC,CAAC,CAAC;aACJ;YACD,oFAAoF;iBAC/E,IAAI,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,0CAAE,aAAa,EAAE;gBAC7C,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;gBACxE,sFAAsF;gBACtF,IAAI,CAAC,YAAY,EAAE;oBACjB,aAAa;oBACb,MAAM,UAAU,GAChB,OAAO,CAAC,IAAI;wBACZ,CAAC,UAAU,CAAC,QAAQ;4BAClB,CAAC,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,aAAa,0CAAE,GAAG;4BAC9B,CAAC,CAAC,MAAA,qBAAqB,EAAE,0CAAE,GAAG,CAAC,CAAC;oBAClC,IAAI,UAAU;wBACZ,SAAS,CAAC,aAAa;6BACpB,QAAQ,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;6BACxC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;4BACf,OAAO,CAAC,KAAK,CACX,+CAA+C,EAC/C,KAAK,CACN,CAAC;wBACJ,CAAC,CAAC,CAAC;iBACR;aACF;iBAAM;gBACL,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;aACH;SACF;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;SAClE;;CACF;AAED,8BAA8B;AAE9B,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyGpB,CAAC;AACF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;MAgBb,YAAY;;;;;;;;;;;;;CAajB,CAAC;AAEF,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAC7B,SAAS,cAAc;IACrB,IAAI,gBAAgB;QAAE,OAAO;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,SAAS,GAAG;MACd,YAAY;;;;;;;;;;;;GAYf,CAAC;IACF,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE5B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC7C,OAAO,CAAC,SAAS,GAAG;;;;;;GAMnB,CAAC;IACF,OAAO,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACrE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB;IACvB,QAAQ,CAAC,eAAe,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC;IAC7D,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,IAAI,gBAAgB,CAAC;AACrB,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAC/B,SAAS,eAAe;IACtB,IAAI,CAAC,kBAAkB,EAAE;QACvB,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAO,KAAU,EAAE,EAAE;YACtD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACzC,IAAI,MAAM,EAAE;gBACV,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;gBACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACtC,IAAI,KAAK,EAAE;oBACT,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACrB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC;oBAC7C,KAAK,CAAC,cAAc,EAAE,CAAC;oBACvB,IAAI;wBACF,IAAI,GAAG,CAAC;wBACR,IAAI,SAAS,EAAE;4BACb,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;4BAChC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;yBAChC;6BAAM;4BACL,gBAAgB,GAAG,IAAI,CAAC;4BACxB,qFAAqF;4BACrF,UAAU,CACR,GAAG,EAAE,CACH,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,kBAAkB,EAAE,EAAE,CAAC,EAC/D,EAAE,CACH,CAAC;yBACH;wBACD,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;wBAC7C,IAAI,CAAC,SAAS,CAAC,MAAM;4BAAE,MAAM,IAAI,CAAC;wBAClC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CACjC,kBAAkB,EAClB,EAAE,CACH,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;wBACnB,IAAI,SAAS,EAAE;4BACb,IAAI,CAAC,GAAG,CAAC,MAAM;gCAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC;yBAC1C;6BAAM,IAAI,gBAAgB,KAAK,IAAI,EAAE;4BACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC;yBAC5B;qBACF;oBAAC,OAAO,CAAC,EAAE;wBACV,IAAI,gBAAgB,KAAK,IAAI,EAAE;4BAC7B,gBAAgB,EAAE,CAAC;yBACpB;wBACD,MAAM,IAAI,KAAK,CACb,+CAA+C,IAAI,EAAE,CACtD,CAAC;qBACH;iBACF;aACF;QACH,CAAC,CAAA,CAAC,CAAC;QAEH,QAAQ,CAAC,gBAAgB,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;QACnE,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAO,KAAU,EAAE,EAAE;;YAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;YACvC,IACE,CAAC,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;gBACrD,CAAC,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,EAClD;gBACA,sBAAsB,EAAE,CAAC;gBACzB,IAAI,MAAM,KAAI,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,GAAG,0CAAE,KAAK,CAAC,WAAW,CAAC,CAAA,EAAE;oBAC7C,iBAAiB,GAAG,MAAM,CAAC;oBAC3B,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC;oBAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG;yBACnB,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC;yBACnC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;oBACnC,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC/C,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;oBACrD,IAAI,GAAG;wBAAE,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;oBAC1B,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,sBAAsB,EAAE;wBAC3D,IAAI,EAAE,IAAI;qBACX,CAAC,CAAC;iBACJ;aACF;iBAAM,IAAI,MAAM,KAAK,iBAAiB,EAAE;gBACvC,sBAAsB,EAAE,CAAC;aAC1B;QACH,CAAC,CAAA,CAAC,CAAC;QAEH,kBAAkB,GAAG,IAAI,CAAC;KAC3B;AACH,CAAC;AAED,IAAI,iBAAiB,CAAC;AACtB,SAAe,sBAAsB,CAAC,CAAO;;QAC3C,IAAI,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,IAAI,MAAK,WAAW,EAAE;YAC3B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD;QACD,IAAI,iBAAiB,EAAE;YACrB,iBAAiB,CAAC,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC;YAClD,OAAO,iBAAiB,CAAC,OAAO,CAAC;YACjC,iBAAiB,GAAG,IAAI,CAAC;SAC1B;IACH,CAAC;CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAe,EAAE;IACjD,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,EAAE;QACnC,aAAa,CAAC,OAAO,CAAC,CAAC;KACxB;IACD,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,WAAW,EAAE;QAC3E,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK,EAAE;YAClC,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS;gBAAE,cAAc,EAAE,CAAC;iBACnD;gBACH,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,cAAc,EAAE;oBAC5D,IAAI,EAAE,IAAI;iBACX,CAAC,CAAC;aACJ;SACF;QACD,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;YAAE,eAAe,EAAE,CAAC;KAChD;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activates various polyfills to enable Web5 features in Web environments.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} [options={}] - Configuration options to control the activation of polyfills.
|
|
5
|
+
* @param {boolean} [options.serviceWorker=true] - Option to avoid installation of the Service Worker. Defaults to true, installing the Service Worker.
|
|
6
|
+
* @param {boolean} [options.injectStyles=true] - Option to skip injection of styles for UI related UX polyfills. Defaults to true, injecting styles.
|
|
7
|
+
* @param {boolean} [options.links=true] - Option to skip activation of DRL link features. Defaults to true, activating link features.
|
|
8
|
+
* @param {function} [options.onCacheCheck] - Callback function to handle cache check events, allowing fine-grained control over what DRL request to cache, and for how long.
|
|
9
|
+
* @param {object} [options.onCacheCheck.event] - The event object passed to the callback.
|
|
10
|
+
* @param {object} [options.onCacheCheck.route] - The route object passed to the callback.
|
|
11
|
+
* @returns {object} [options.onCacheCheck.return] - The return object from the callback.
|
|
12
|
+
* @returns {number} [options.onCacheCheck.return.ttl] - Time-to-live for the cached DRL response, in milliseconds.
|
|
13
|
+
*
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Activate all polyfills with default options, and cache every DRL for 1 minute
|
|
18
|
+
* activatePolyfills({
|
|
19
|
+
* onCacheCheck(event, route){
|
|
20
|
+
* return {
|
|
21
|
+
* ttl: 60_000
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Activate polyfills, but without Service Worker activation
|
|
28
|
+
* activatePolyfills({ serviceWorker: false });
|
|
29
|
+
*/
|
|
30
|
+
export declare function activatePolyfills(options?: any): void;
|
|
31
|
+
//# sourceMappingURL=web-features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-features.d.ts","sourceRoot":"","sources":["../../src/web-features.ts"],"names":[],"mappings":"AA4bA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,GAAQ,QAelD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@enbox/browser",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Web5 tools and features to use in the browser",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/esm/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"clean": "rimraf dist coverage tests/compiled",
|
|
11
|
+
"build:tests": "rimraf tests/compiled && node build/esbuild-tests.cjs",
|
|
12
|
+
"build:esm": "rimraf dist/esm dist/types && pnpm tsc -p tsconfig.json",
|
|
13
|
+
"build:browser": "pnpm build:esm",
|
|
14
|
+
"build": "pnpm clean && pnpm build:esm",
|
|
15
|
+
"lint": "eslint . --max-warnings 0",
|
|
16
|
+
"lint:fix": "eslint . --fix",
|
|
17
|
+
"test:browser": "pnpm build:tests && web-test-runner"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/enboxorg/enbox/tree/main/packages/browser#readme",
|
|
20
|
+
"bugs": "https://github.com/enboxorg/enbox/issues",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/enboxorg/enbox.git",
|
|
24
|
+
"directory": "packages/browser"
|
|
25
|
+
},
|
|
26
|
+
"license": "Apache-2.0",
|
|
27
|
+
"contributors": [
|
|
28
|
+
{
|
|
29
|
+
"name": "Daniel Buchner",
|
|
30
|
+
"url": "https://github.com/csuwildcat"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "Liran Cohen",
|
|
34
|
+
"url": "https://github.com/lirancohen"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src"
|
|
40
|
+
],
|
|
41
|
+
"keywords": [
|
|
42
|
+
"decentralized",
|
|
43
|
+
"decentralized-applications",
|
|
44
|
+
"decentralized-identity",
|
|
45
|
+
"decentralized-web",
|
|
46
|
+
"DID",
|
|
47
|
+
"sdk",
|
|
48
|
+
"verifiable-credentials",
|
|
49
|
+
"web5",
|
|
50
|
+
"web5-sdk",
|
|
51
|
+
"browser",
|
|
52
|
+
"tools"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@enbox/dids": "workspace:*"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@playwright/test": "1.45.3",
|
|
62
|
+
"@types/chai": "4.3.6",
|
|
63
|
+
"@types/eslint": "8.56.10",
|
|
64
|
+
"@types/mocha": "10.0.1",
|
|
65
|
+
"@types/sinon": "17.0.3",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "7.9.0",
|
|
67
|
+
"@typescript-eslint/parser": "7.14.1",
|
|
68
|
+
"@web/test-runner": "0.18.2",
|
|
69
|
+
"@web/test-runner-playwright": "0.11.0",
|
|
70
|
+
"c8": "9.1.0",
|
|
71
|
+
"chai": "4.3.10",
|
|
72
|
+
"esbuild": "0.19.8",
|
|
73
|
+
"eslint": "9.3.0",
|
|
74
|
+
"eslint-plugin-mocha": "10.4.3",
|
|
75
|
+
"mocha": "10.2.0",
|
|
76
|
+
"mocha-junit-reporter": "2.2.1",
|
|
77
|
+
"playwright": "1.45.3",
|
|
78
|
+
"rimraf": "4.4.0",
|
|
79
|
+
"sinon": "18.0.0",
|
|
80
|
+
"source-map-loader": "4.0.2",
|
|
81
|
+
"typescript": "5.1.6"
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './web-features.js';
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This file is run in dual environments to make installation of the Service Worker code easier.
|
|
5
|
+
Be mindful that code placed in any open excution space may be evaluated multiple times in different contexts,
|
|
6
|
+
so take care to gate additions to only activate code in the right env, such as a Service Worker scope or page window.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { UniversalResolver, DidDht, DidWeb } from '@enbox/dids';
|
|
10
|
+
|
|
11
|
+
// This is in place to prevent our `bundler-bonanza` repo from failing for Node CJS builds
|
|
12
|
+
// Not sure if this is working as expected in all environments, crated an issue
|
|
13
|
+
// TODO: https://github.com/TBD54566975/web5-js/issues/767
|
|
14
|
+
function importMetaIfSupported() {
|
|
15
|
+
try {
|
|
16
|
+
return new Function('return import.meta')();
|
|
17
|
+
} catch (_error) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare const ServiceWorkerGlobalScope: any;
|
|
23
|
+
|
|
24
|
+
const DidResolver = new UniversalResolver({ didResolvers: [DidDht, DidWeb] });
|
|
25
|
+
const didUrlRegex = /^https?:\/\/dweb\/([^/]+)\/?(.*)?$/;
|
|
26
|
+
const httpToHttpsRegex = /^http:/;
|
|
27
|
+
const trailingSlashRegex = /\/$/;
|
|
28
|
+
|
|
29
|
+
async function getDwnEndpoints(did) {
|
|
30
|
+
const { didDocument } = await DidResolver.resolve(did);
|
|
31
|
+
const endpoints = didDocument?.service?.find(
|
|
32
|
+
(service) => service.type === 'DecentralizedWebNode'
|
|
33
|
+
)?.serviceEndpoint;
|
|
34
|
+
return (Array.isArray(endpoints) ? endpoints : [endpoints]).filter((url) =>
|
|
35
|
+
url.startsWith('http')
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function handleEvent(event, did, path, options) {
|
|
40
|
+
const drl = event.request.url
|
|
41
|
+
.replace(httpToHttpsRegex, 'https:')
|
|
42
|
+
.replace(trailingSlashRegex, '');
|
|
43
|
+
const responseCache = await caches.open('drl');
|
|
44
|
+
const cachedResponse = await responseCache.match(drl);
|
|
45
|
+
if (cachedResponse) {
|
|
46
|
+
if (!navigator.onLine) return cachedResponse;
|
|
47
|
+
const match = await options?.onCacheCheck(event, drl);
|
|
48
|
+
if (match) {
|
|
49
|
+
const cacheTime = cachedResponse.headers.get('dwn-cache-time');
|
|
50
|
+
if (
|
|
51
|
+
cacheTime &&
|
|
52
|
+
Date.now() < Number(cacheTime) + (Number(match.ttl) || 0)
|
|
53
|
+
) {
|
|
54
|
+
return cachedResponse;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (!path) {
|
|
60
|
+
const response = await DidResolver.resolve(did);
|
|
61
|
+
return new Response(JSON.stringify(response), {
|
|
62
|
+
status : 200,
|
|
63
|
+
headers : {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
} else
|
|
68
|
+
return await fetchResource(event, did, drl, path, responseCache, options);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof Response) {
|
|
71
|
+
return error;
|
|
72
|
+
}
|
|
73
|
+
console.log(`Error in DID URL fetch: ${error}`);
|
|
74
|
+
return new Response('DID URL fetch error', { status: 500 });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function fetchResource(event, did, drl, path, responseCache, options) {
|
|
79
|
+
const endpoints = await getDwnEndpoints(did);
|
|
80
|
+
if (!endpoints?.length) {
|
|
81
|
+
throw new Response(
|
|
82
|
+
'DWeb Node resolution failed: no valid endpoints found.',
|
|
83
|
+
{ status: 530 }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
for (const endpoint of endpoints) {
|
|
87
|
+
try {
|
|
88
|
+
const url = `${endpoint.replace(trailingSlashRegex, '')}/${did}/${path}`;
|
|
89
|
+
const response = await fetch(url, { headers: event.request.headers });
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
const match = await options?.onCacheCheck(event, drl);
|
|
92
|
+
if (match) {
|
|
93
|
+
cacheResponse(drl, url, response, responseCache);
|
|
94
|
+
}
|
|
95
|
+
return response;
|
|
96
|
+
}
|
|
97
|
+
console.log(`DWN endpoint error: ${response.status}`);
|
|
98
|
+
return new Response('DWeb Node request failed', {
|
|
99
|
+
status: response.status,
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(`DWN endpoint error: ${error}`);
|
|
103
|
+
return new Response('DWeb Node request failed: ' + error, {
|
|
104
|
+
status: 500,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function cacheResponse(drl, url, response, cache) {
|
|
111
|
+
const clonedResponse = response.clone();
|
|
112
|
+
const headers = new Headers(clonedResponse.headers);
|
|
113
|
+
headers.append('dwn-cache-time', Date.now().toString());
|
|
114
|
+
headers.append('dwn-composed-url', url);
|
|
115
|
+
const modifiedResponse = new Response(clonedResponse.body, { headers });
|
|
116
|
+
cache.put(drl, modifiedResponse);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Service Worker-based features */
|
|
120
|
+
|
|
121
|
+
async function installWorker(options: any = {}): Promise<void> {
|
|
122
|
+
const workerSelf = self as any;
|
|
123
|
+
try {
|
|
124
|
+
// Check to see if we are in a Service Worker already, if so, proceed
|
|
125
|
+
// You can call the activatePolyfills() function in your own worker, or standalone as a root worker
|
|
126
|
+
if (
|
|
127
|
+
typeof ServiceWorkerGlobalScope !== 'undefined' &&
|
|
128
|
+
workerSelf instanceof ServiceWorkerGlobalScope
|
|
129
|
+
) {
|
|
130
|
+
workerSelf.skipWaiting();
|
|
131
|
+
workerSelf.addEventListener('activate', (event) => {
|
|
132
|
+
// Claim clients to make the service worker take control immediately
|
|
133
|
+
event.waitUntil(workerSelf.clients.claim());
|
|
134
|
+
});
|
|
135
|
+
workerSelf.addEventListener('fetch', (event) => {
|
|
136
|
+
const match = event.request.url.match(didUrlRegex);
|
|
137
|
+
if (match) {
|
|
138
|
+
event.respondWith(handleEvent(event, match[1], match[2], options));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// If the code gets here, it is not a SW env, it is likely DOM, but check to be sure
|
|
143
|
+
else if (globalThis?.navigator?.serviceWorker) {
|
|
144
|
+
const registration = await navigator.serviceWorker.getRegistration('/');
|
|
145
|
+
// You can only have one worker per path, so check to see if one is already registered
|
|
146
|
+
if (!registration) {
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
const installUrl =
|
|
149
|
+
options.path ||
|
|
150
|
+
(globalThis.document
|
|
151
|
+
? document?.currentScript?.src
|
|
152
|
+
: importMetaIfSupported()?.url);
|
|
153
|
+
if (installUrl)
|
|
154
|
+
navigator.serviceWorker
|
|
155
|
+
.register(installUrl, { type: 'module' })
|
|
156
|
+
.catch((error) => {
|
|
157
|
+
console.error(
|
|
158
|
+
'DWeb networking feature installation failed: ',
|
|
159
|
+
error
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
throw new Error(
|
|
165
|
+
'DWeb networking features are not available for install in this environment'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Error in installing networking features:', error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* DOM Environment Features */
|
|
174
|
+
|
|
175
|
+
const loaderStyles = `
|
|
176
|
+
.drl-loading-overlay {
|
|
177
|
+
position: fixed;
|
|
178
|
+
inset: 0;
|
|
179
|
+
display: flex;
|
|
180
|
+
flex-wrap: wrap;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
font-size: 22px;
|
|
185
|
+
color: #fff;
|
|
186
|
+
background: rgba(0, 0, 0, 0.75);
|
|
187
|
+
backdrop-filter: blur(15px);
|
|
188
|
+
-webkit-backdrop-filter: blur(15px);
|
|
189
|
+
z-index: 1000000;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.drl-loading-overlay > div {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.drl-loading-spinner {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.drl-loading-spinner div {
|
|
205
|
+
position: relative;
|
|
206
|
+
width: 2em;
|
|
207
|
+
height: 2em;
|
|
208
|
+
margin: 0.1em 0.25em 0 0;
|
|
209
|
+
}
|
|
210
|
+
.drl-loading-spinner div::after,
|
|
211
|
+
.drl-loading-spinner div::before {
|
|
212
|
+
content: '';
|
|
213
|
+
box-sizing: border-box;
|
|
214
|
+
width: 100%;
|
|
215
|
+
height: 100%;
|
|
216
|
+
border-radius: 50%;
|
|
217
|
+
border: 0.1em solid #FFF;
|
|
218
|
+
position: absolute;
|
|
219
|
+
left: 0;
|
|
220
|
+
top: 0;
|
|
221
|
+
opacity: 0;
|
|
222
|
+
animation: drl-loading-spinner 2s linear infinite;
|
|
223
|
+
}
|
|
224
|
+
.drl-loading-spinner div::after {
|
|
225
|
+
animation-delay: 1s;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.drl-loading-overlay span {
|
|
229
|
+
--text-opacity: 2;
|
|
230
|
+
display: flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
margin: 2em auto 0;
|
|
233
|
+
padding: 0.2em 0.75em 0.25em;
|
|
234
|
+
text-align: center;
|
|
235
|
+
border-radius: 5em;
|
|
236
|
+
background: rgba(255 255 255 / 8%);
|
|
237
|
+
opacity: 0.8;
|
|
238
|
+
transition: opacity 0.3s ease;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.drl-loading-overlay span:focus {
|
|
243
|
+
opacity: 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.drl-loading-overlay span:hover {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.drl-loading-overlay span::before {
|
|
251
|
+
content: '✕ ';
|
|
252
|
+
margin: 0 0.4em 0 0;
|
|
253
|
+
color: red;
|
|
254
|
+
font-size: 65%;
|
|
255
|
+
font-weight: bold;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.drl-loading-overlay span::after {
|
|
259
|
+
content: 'stop';
|
|
260
|
+
display: block;
|
|
261
|
+
font-size: 60%;
|
|
262
|
+
line-height: 0;
|
|
263
|
+
color: rgba(255 255 255 / 60%);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.drl-loading-overlay.new-tab-overlay span::after {
|
|
267
|
+
content: 'close';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@keyframes drl-loading-spinner {
|
|
271
|
+
0% {
|
|
272
|
+
transform: scale(0);
|
|
273
|
+
opacity: 1;
|
|
274
|
+
}
|
|
275
|
+
100% {
|
|
276
|
+
transform: scale(1);
|
|
277
|
+
opacity: 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
const tabContent = `
|
|
282
|
+
<!DOCTYPE html>
|
|
283
|
+
<html lang='en'>
|
|
284
|
+
<head>
|
|
285
|
+
<meta charset='UTF-8'>
|
|
286
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
287
|
+
<title>Loading DRL...</title>
|
|
288
|
+
<style>
|
|
289
|
+
html, body {
|
|
290
|
+
background-color: #151518;
|
|
291
|
+
height: 100%;
|
|
292
|
+
margin: 0;
|
|
293
|
+
padding: 0;
|
|
294
|
+
font-family: Arial, sans-serif;
|
|
295
|
+
text-align: center;
|
|
296
|
+
}
|
|
297
|
+
${loaderStyles}
|
|
298
|
+
</style>
|
|
299
|
+
</head>
|
|
300
|
+
<body>
|
|
301
|
+
<div class='drl-loading-overlay new-tab-overlay'>
|
|
302
|
+
<div class='drl-loading-spinner'>
|
|
303
|
+
<div></div>
|
|
304
|
+
Loading DRL
|
|
305
|
+
</div>
|
|
306
|
+
<span onclick='window.close()' tabindex='0'></span>
|
|
307
|
+
</div>
|
|
308
|
+
</body>
|
|
309
|
+
</html>
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
let elementsInjected = false;
|
|
313
|
+
function injectElements() {
|
|
314
|
+
if (elementsInjected) return;
|
|
315
|
+
const style = document.createElement('style');
|
|
316
|
+
style.innerHTML = `
|
|
317
|
+
${loaderStyles}
|
|
318
|
+
|
|
319
|
+
.drl-loading-overlay {
|
|
320
|
+
opacity: 0;
|
|
321
|
+
transition: opacity 0.3s ease;
|
|
322
|
+
pointer-events: none;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
:root[drl-link-loading] .drl-loading-overlay {
|
|
326
|
+
opacity: 1;
|
|
327
|
+
pointer-events: all;
|
|
328
|
+
}
|
|
329
|
+
`;
|
|
330
|
+
document.head.append(style);
|
|
331
|
+
|
|
332
|
+
const overlay = document.createElement('div');
|
|
333
|
+
overlay.classList.add('drl-loading-overlay');
|
|
334
|
+
overlay.innerHTML = `
|
|
335
|
+
<div class='drl-loading-spinner'>
|
|
336
|
+
<div></div>
|
|
337
|
+
Loading DRL
|
|
338
|
+
</div>
|
|
339
|
+
<span tabindex='0'></span>
|
|
340
|
+
`;
|
|
341
|
+
overlay.lastElementChild.addEventListener('click', cancelNavigation);
|
|
342
|
+
document.body.prepend(overlay);
|
|
343
|
+
elementsInjected = true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function cancelNavigation() {
|
|
347
|
+
document.documentElement.removeAttribute('drl-link-loading');
|
|
348
|
+
activeNavigation = null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let activeNavigation;
|
|
352
|
+
let linkFeaturesActive = false;
|
|
353
|
+
function addLinkFeatures() {
|
|
354
|
+
if (!linkFeaturesActive) {
|
|
355
|
+
document.addEventListener('click', async (event: any) => {
|
|
356
|
+
const anchor = event.target.closest('a');
|
|
357
|
+
if (anchor) {
|
|
358
|
+
const href = anchor.href;
|
|
359
|
+
const match = href.match(didUrlRegex);
|
|
360
|
+
if (match) {
|
|
361
|
+
const did = match[1];
|
|
362
|
+
const path = match[2];
|
|
363
|
+
const openAsTab = anchor.target === '_blank';
|
|
364
|
+
event.preventDefault();
|
|
365
|
+
try {
|
|
366
|
+
let tab;
|
|
367
|
+
if (openAsTab) {
|
|
368
|
+
tab = window.open('', '_blank');
|
|
369
|
+
tab.document.write(tabContent);
|
|
370
|
+
} else {
|
|
371
|
+
activeNavigation = path;
|
|
372
|
+
// this is to allow for cached DIDs to instantly load without any flash of loading UI
|
|
373
|
+
setTimeout(
|
|
374
|
+
() =>
|
|
375
|
+
document.documentElement.setAttribute('drl-link-loading', ''),
|
|
376
|
+
50
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const endpoints = await getDwnEndpoints(did);
|
|
380
|
+
if (!endpoints.length) throw null;
|
|
381
|
+
const url = `${endpoints[0].replace(
|
|
382
|
+
trailingSlashRegex,
|
|
383
|
+
''
|
|
384
|
+
)}/${did}/${path}`;
|
|
385
|
+
if (openAsTab) {
|
|
386
|
+
if (!tab.closed) tab.location.href = url;
|
|
387
|
+
} else if (activeNavigation === path) {
|
|
388
|
+
window.location.href = url;
|
|
389
|
+
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (activeNavigation === path) {
|
|
392
|
+
cancelNavigation();
|
|
393
|
+
}
|
|
394
|
+
throw new Error(
|
|
395
|
+
`DID endpoint resolution failed for the DRL: ${href}`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
document.addEventListener('pointercancel', resetContextMenuTarget);
|
|
403
|
+
document.addEventListener('pointerdown', async (event: any) => {
|
|
404
|
+
const target = event.composedPath()[0];
|
|
405
|
+
if (
|
|
406
|
+
(event.pointerType === 'mouse' && event.button === 2) ||
|
|
407
|
+
(event.pointerType === 'touch' && event.isPrimary)
|
|
408
|
+
) {
|
|
409
|
+
resetContextMenuTarget();
|
|
410
|
+
if (target && target?.src?.match(didUrlRegex)) {
|
|
411
|
+
contextMenuTarget = target;
|
|
412
|
+
target.__src__ = target.src;
|
|
413
|
+
const drl = target.src
|
|
414
|
+
.replace(httpToHttpsRegex, 'https:')
|
|
415
|
+
.replace(trailingSlashRegex, '');
|
|
416
|
+
const responseCache = await caches.open('drl');
|
|
417
|
+
const response = await responseCache.match(drl);
|
|
418
|
+
const url = response.headers.get('dwn-composed-url');
|
|
419
|
+
if (url) target.src = url;
|
|
420
|
+
target.addEventListener('pointerup', resetContextMenuTarget, {
|
|
421
|
+
once: true,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
} else if (target === contextMenuTarget) {
|
|
425
|
+
resetContextMenuTarget();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
linkFeaturesActive = true;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let contextMenuTarget;
|
|
434
|
+
async function resetContextMenuTarget(e?: any) {
|
|
435
|
+
if (e?.type === 'pointerup') {
|
|
436
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
437
|
+
}
|
|
438
|
+
if (contextMenuTarget) {
|
|
439
|
+
contextMenuTarget.src = contextMenuTarget.__src__;
|
|
440
|
+
delete contextMenuTarget.__src__;
|
|
441
|
+
contextMenuTarget = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Activates various polyfills to enable Web5 features in Web environments.
|
|
447
|
+
*
|
|
448
|
+
* @param {object} [options={}] - Configuration options to control the activation of polyfills.
|
|
449
|
+
* @param {boolean} [options.serviceWorker=true] - Option to avoid installation of the Service Worker. Defaults to true, installing the Service Worker.
|
|
450
|
+
* @param {boolean} [options.injectStyles=true] - Option to skip injection of styles for UI related UX polyfills. Defaults to true, injecting styles.
|
|
451
|
+
* @param {boolean} [options.links=true] - Option to skip activation of DRL link features. Defaults to true, activating link features.
|
|
452
|
+
* @param {function} [options.onCacheCheck] - Callback function to handle cache check events, allowing fine-grained control over what DRL request to cache, and for how long.
|
|
453
|
+
* @param {object} [options.onCacheCheck.event] - The event object passed to the callback.
|
|
454
|
+
* @param {object} [options.onCacheCheck.route] - The route object passed to the callback.
|
|
455
|
+
* @returns {object} [options.onCacheCheck.return] - The return object from the callback.
|
|
456
|
+
* @returns {number} [options.onCacheCheck.return.ttl] - Time-to-live for the cached DRL response, in milliseconds.
|
|
457
|
+
*
|
|
458
|
+
* @returns {void}
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* // Activate all polyfills with default options, and cache every DRL for 1 minute
|
|
462
|
+
* activatePolyfills({
|
|
463
|
+
* onCacheCheck(event, route){
|
|
464
|
+
* return {
|
|
465
|
+
* ttl: 60_000
|
|
466
|
+
* }
|
|
467
|
+
* }
|
|
468
|
+
* });
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* // Activate polyfills, but without Service Worker activation
|
|
472
|
+
* activatePolyfills({ serviceWorker: false });
|
|
473
|
+
*/
|
|
474
|
+
export function activatePolyfills(options: any = {}) {
|
|
475
|
+
if (options.serviceWorker !== false) {
|
|
476
|
+
installWorker(options);
|
|
477
|
+
}
|
|
478
|
+
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
|
|
479
|
+
if (options.injectStyles !== false) {
|
|
480
|
+
if (document.readyState !== 'loading') injectElements();
|
|
481
|
+
else {
|
|
482
|
+
document.addEventListener('DOMContentLoaded', injectElements, {
|
|
483
|
+
once: true,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (options.links !== false) addLinkFeatures();
|
|
488
|
+
}
|
|
489
|
+
}
|