1
+ ( ( document ) => {
2
+ 'use strict' ;
3
+ let fastNode ;
4
+ let failed ;
5
+ let isRunning ;
6
+ const DEST_LIST = [
7
+ 'cdn.jsdelivr.net' ,
8
+ 'jsd.cdn.zzko.cn' ,
9
+ 'cdn.jsdelivr.us' ,
10
+ 'jsd.onmicrosoft.cn' ,
11
+ 'fastly.jsdelivr.net' ,
12
+ 'gcore.jsdelivr.net'
13
+ ] ;
14
+ const PREFIX = '//' ;
15
+ const SOURCE = DEST_LIST [ 0 ] ;
16
+ const starTime = Date . now ( ) ;
17
+ const TIMEOUT = 2000 ;
18
+ const STORE_KEY = 'jsdelivr-auto-fallback' ;
19
+ const TEST_PATH = '/gh/PipecraftNet/jsdelivr-auto-fallback@main/empty.css?' ;
20
+ const shouldReplace = ( text ) => text && text . includes ( PREFIX + SOURCE ) ;
21
+ const replace = ( text ) => text . replace ( PREFIX + SOURCE , PREFIX + fastNode ) ;
22
+ const setTimeout = window . setTimeout ;
23
+ const $ = document . querySelectorAll . bind ( document ) ;
24
+
25
+ const replaceElementSrc = ( ) => {
26
+ let element ;
27
+ let value ;
28
+ for ( element of $ ( 'link[rel="stylesheet"]' ) ) {
29
+ value = element . href ;
30
+ if ( shouldReplace ( value ) && ! value . includes ( TEST_PATH ) ) {
31
+ element . href = replace ( value ) ;
32
+ }
33
+ }
34
+
35
+ for ( element of $ ( 'script' ) ) {
36
+ value = element . src ;
37
+ if ( shouldReplace ( value ) ) {
38
+ const newNode = document . createElement ( 'script' ) ;
39
+ newNode . src = replace ( value ) ;
40
+ element . defer = true ;
41
+ element . src = '' ;
42
+ element . before ( newNode ) ;
43
+ element . remove ( ) ;
44
+ }
45
+ }
46
+
47
+ for ( element of $ ( 'img' ) ) {
48
+ value = element . src ;
49
+ if ( shouldReplace ( value ) ) {
50
+ // Used to cancel loading. Without this line it will remain pending status.
51
+ element . src = '' ;
52
+ element . src = replace ( value ) ;
53
+ }
54
+ }
55
+
56
+ // All elements that have a style attribute
57
+ for ( element of $ ( '*[style]' ) ) {
58
+ value = element . getAttribute ( 'style' ) ;
59
+ if ( shouldReplace ( value ) ) {
60
+ element . setAttribute ( 'style' , replace ( value ) ) ;
61
+ }
62
+ }
63
+
64
+ for ( element of $ ( 'style' ) ) {
65
+ value = element . innerHTML ;
66
+ if ( shouldReplace ( value ) ) {
67
+ element . innerHTML = replace ( value ) ;
68
+ }
69
+ }
70
+ } ;
71
+
72
+ const tryReplace = ( ) => {
73
+ if ( ! isRunning && failed && fastNode ) {
74
+ console . warn ( SOURCE + ' is not available. Use ' + fastNode ) ;
75
+ isRunning = true ;
76
+ setTimeout ( replaceElementSrc , 0 ) ;
77
+ // Some need to wait for a while
78
+ setTimeout ( replaceElementSrc , 20 ) ;
79
+ // Replace dynamically added elements
80
+ setInterval ( replaceElementSrc , 500 ) ;
81
+ }
82
+ } ;
83
+
84
+ const checkAvailable = ( url , callback ) => {
85
+ let timeoutId ;
86
+ const newNode = document . createElement ( 'link' ) ;
87
+ const handleResult = ( isSuccess ) => {
88
+ if ( ! timeoutId ) {
89
+ return ;
90
+ }
91
+
92
+ clearTimeout ( timeoutId ) ;
93
+ timeoutId = 0 ;
94
+ // Used to cancel loading. Without this line it will remain pending status.
95
+ if ( ! isSuccess ) newNode . href = 'data:text/css;base64,' ;
96
+ newNode . remove ( ) ;
97
+ callback ( isSuccess ) ;
98
+ } ;
99
+
100
+ timeoutId = setTimeout ( handleResult , TIMEOUT ) ;
101
+
102
+ newNode . addEventListener ( 'error' , ( ) => handleResult ( false ) ) ;
103
+ newNode . addEventListener ( 'load' , ( ) => handleResult ( true ) ) ;
104
+ newNode . rel = 'stylesheet' ;
105
+ newNode . text = 'text/css' ;
106
+ newNode . href = url + TEST_PATH + starTime ;
107
+ document . head . insertAdjacentElement ( 'afterbegin' , newNode ) ;
108
+ } ;
109
+
110
+ const cached = ( ( ) => {
111
+ try {
112
+ return Object . assign (
113
+ { } ,
114
+ JSON . parse ( localStorage . getItem ( STORE_KEY ) || '{}' )
115
+ ) ;
116
+ } catch {
117
+ return { } ;
118
+ }
119
+ } ) ( ) ;
120
+
121
+ const main = ( ) => {
122
+ cached . time = starTime ;
123
+ cached . failed = false ;
124
+ cached . fastNode = null ;
125
+
126
+ for ( const url of DEST_LIST ) {
127
+ checkAvailable ( 'https://' + url , ( isAvailable ) => {
128
+ // console.log(url, Date.now() - starTime, Boolean(isAvailable));
129
+ if ( ! isAvailable && url === SOURCE ) {
130
+ failed = true ;
131
+ cached . failed = true ;
132
+ }
133
+
134
+ if ( isAvailable && ! fastNode ) {
135
+ fastNode = url ;
136
+ }
137
+
138
+ if ( isAvailable && ! cached . fastNode ) {
139
+ cached . fastNode = url ;
140
+ }
141
+
142
+ tryReplace ( ) ;
143
+ } ) ;
144
+ }
145
+
146
+ setTimeout ( ( ) => {
147
+ // If all domains are timeout
148
+ if ( failed && ! fastNode ) {
149
+ fastNode = DEST_LIST [ 1 ] ;
150
+ tryReplace ( ) ;
151
+ }
152
+
153
+ localStorage . setItem ( STORE_KEY , JSON . stringify ( cached ) ) ;
154
+ } , TIMEOUT + 100 ) ;
155
+ } ;
156
+
157
+ if (
158
+ cached . time &&
159
+ starTime - cached . time < 60 * 60 * 1000 &&
160
+ cached . failed &&
161
+ cached . fastNode
162
+ ) {
163
+ failed = true ;
164
+ fastNode = cached . fastNode ;
165
+ tryReplace ( ) ;
166
+ setTimeout ( main , 1000 ) ;
167
+ } else if ( document . head ) {
168
+ main ( ) ;
169
+ } else {
170
+ const observer = new MutationObserver ( ( ) => {
171
+ if ( document . head ) {
172
+ observer . disconnect ( ) ;
173
+ main ( ) ;
174
+ }
175
+ } ) ;
176
+ const observerOptions = {
177
+ childList : true ,
178
+ subtree : true
179
+ } ;
180
+ observer . observe ( document , observerOptions ) ;
181
+ }
182
+ } ) ( document ) ;
0 commit comments