1
1
import Foundation
2
2
import CryptoKit
3
- import SwiftSoup
4
3
5
4
public actor EditorAssetsLibrary {
6
5
enum ManifestError : Error {
7
6
case unavailable
8
7
case invalidServerResponse
8
+ case invalidSiteUrl
9
9
}
10
10
11
11
let urlSession : URLSession
@@ -47,10 +47,14 @@ public actor EditorAssetsLibrary {
47
47
/// - SeeAlso: `EditorAssetsLibrary.addAsset`
48
48
func manifestContentForEditor( ) async throws -> Data {
49
49
// For scheme-less links (i.e. '//stats.wp.com/w.js'), use the scheme in `siteURL`.
50
- let siteURLScheme = URL ( string: configuration. siteURL) ? . scheme
50
+ guard let siteURLScheme = URL ( string: configuration. siteURL) ? . scheme else {
51
+ throw ManifestError . invalidSiteUrl
52
+ }
53
+
51
54
let data = try await loadManifestContent ( )
52
- let manifest = try JSONDecoder ( ) . decode ( EditorAssetsMainifest . self, from: data)
53
- return try manifest. renderForEditor ( defaultScheme: siteURLScheme)
55
+ let manifest = try EditorAssetManifest ( data: data)
56
+
57
+ return try JSONEncoder ( ) . encode ( manifest. applyingUrlScheme ( siteURLScheme, using: configuration. assetManifestParser) )
54
58
}
55
59
56
60
/// Fetches all assets in the `EditorConfiguration.editorAssetsEndpoint` manifest and stores them on the device.
@@ -61,30 +65,27 @@ public actor EditorAssetsLibrary {
61
65
let siteURLScheme = URL ( string: configuration. siteURL) ? . scheme
62
66
63
67
let data = try await loadManifestContent ( )
64
- let manifest = try JSONDecoder ( ) . decode ( EditorAssetsMainifest . self, from: data)
65
- let assetLinks = try manifest. parseAssetLinks ( defaultScheme: siteURLScheme)
66
-
67
- for link in assetLinks {
68
- guard let url = URL ( string: link) else {
69
- NSLog ( " Malformed asset link: \( link) " )
70
- continue
71
- }
68
+ let manifest = try EditorAssetManifest ( data: data)
69
+ . applyingUrlScheme ( siteURLScheme, using: configuration. assetManifestParser)
70
+ let assetUrls = try manifest. getAllAssetUrls ( using: configuration. assetManifestParser)
72
71
72
+ for url in assetUrls {
73
73
guard url. scheme == " http " || url. scheme == " https " else {
74
- NSLog ( " Unexpected asset link: \( link ) " )
74
+ NSLog ( " Unexpected asset link: \( url ) " )
75
75
continue
76
76
}
77
77
78
- _ = try await cacheAsset ( from: url)
78
+ try await cacheAsset ( from: url)
79
79
}
80
- NSLog ( " \( assetLinks . count) resources processed. " )
80
+ NSLog ( " \( assetUrls . count) resources processed. " )
81
81
}
82
82
83
83
/// Fetches one asset (JavaScript or stylesheet) and caches its content on the device.
84
84
///
85
85
/// - Parameters:
86
86
/// - httpURL: The javascript or css URL.
87
87
/// - webViewURL: The corresponding URL requested by web view, which should the "GBK cache prefix" (`gbk-cache-https://`)
88
+ @discardableResult
88
89
func cacheAsset( from httpURL: URL , webViewURL: URL ? = nil ) async throws -> ( URLResponse , Data ) {
89
90
// The Web Inspector automatically requests ".js.map" files, we'll support it here for debugging purpose.
90
91
let supportedResourceSuffixes = [ " .js " , " .css " , " .js.map " ]
@@ -191,105 +192,106 @@ private extension String {
191
192
}
192
193
}
193
194
194
- struct EditorAssetsMainifest : Codable {
195
- var scripts : String
196
- var styles : String
197
- var allowedBlockTypes : [ String ]
195
+ public struct EditorAssetSchemeResolver {
196
+ // Takes a URL string and applies the given scheme to it.
197
+ //
198
+ // If there is no scheme present, the `defaultScheme` will be applied to it. If no `defaultScheme` is
199
+ // provided, `https` will be used.
200
+ public static func resolveSchemeFor( _ link: String , defaultScheme: String ? ) -> String {
201
+ if link. starts ( with: " // " ) {
202
+ return " \( defaultScheme ?? " https " ) : \( link) "
203
+ }
204
+
205
+ return link
206
+ }
207
+ }
208
+
209
+
210
+ // An object representing the JSON response we receive from the server
211
+ //
212
+ public struct EditorAssetManifest : Codable {
213
+ public let scripts : String
214
+ public let styles : String
215
+ public let allowedBlockTypes : [ String ]
198
216
199
217
enum CodingKeys : String , CodingKey {
200
218
case scripts
201
219
case styles
202
220
case allowedBlockTypes = " allowed_block_types "
203
221
}
204
222
205
- func parseAssetLinks( defaultScheme: String ? ) throws -> [ String ] {
206
- let html = """
207
- <html>
208
- <head>
209
- \( scripts)
210
- \( styles)
211
- </head>
212
- <body></body>
213
- </html>
214
- """
215
- let document = try SwiftSoup . parse ( html)
216
-
217
- var assetLinks : [ String ] = [ ]
218
- assetLinks += try document. select ( " script[src] " ) . map {
219
- Self . resolveAssetLink ( try $0. attr ( " src " ) , defaultScheme: defaultScheme)
220
- }
221
- assetLinks += try document. select ( #"link[rel="stylesheet"][href]"# ) . map {
222
- Self . resolveAssetLink ( try $0. attr ( " href " ) , defaultScheme: defaultScheme)
223
- }
224
- return assetLinks
223
+ init ( data: Data ) throws {
224
+ self = try JSONDecoder ( ) . decode ( EditorAssetManifest . self, from: data)
225
225
}
226
226
227
- func renderForEditor( defaultScheme: String ? ) throws -> Data {
228
- var rendered = self
229
- rendered. scripts = try Self . renderForEditor ( scripts: self . scripts, defaultScheme: defaultScheme)
230
- rendered. styles = try Self . renderForEditor ( styles: self . styles, defaultScheme: defaultScheme)
231
- return try JSONEncoder ( ) . encode ( rendered)
227
+ init ( scripts: String , styles: String , allowedBlockTypes: [ String ] ) {
228
+ self . scripts = scripts
229
+ self . styles = styles
230
+ self . allowedBlockTypes = allowedBlockTypes
232
231
}
233
232
234
- private static func renderForEditor( scripts: String , defaultScheme: String ? ) throws -> String {
235
- let html = """
236
- <html>
237
- <head>
238
- \( scripts)
239
- </head>
240
- <body></body>
241
- </html>
242
- """
243
- let document = try SwiftSoup . parse ( html)
244
-
245
- for script in try document. select ( " script[src] " ) {
246
- if let src = try ? script. attr ( " src " ) {
247
- let link = Self . resolveAssetLink ( src, defaultScheme: defaultScheme)
248
- #if canImport(UIKit)
249
- let newLink = CachedAssetSchemeHandler . cachedURL ( forWebLink: link) ?? link
250
- #else
251
- let newLink = link
252
- #endif
253
- try script. attr ( " src " , newLink)
254
- }
255
- }
233
+ func getScriptUrlStrings( using parser: EditorAssetManifestParser ) throws -> [ String ] {
234
+ try parser. extractScriptURLs ( from: self . scripts)
235
+ }
256
236
257
- let head = document . head ( ) !
258
- return try head . html ( )
237
+ func getScriptUrls ( using parser : EditorAssetManifestParser ) throws -> [ URL ] {
238
+ try getScriptUrlStrings ( using : parser ) . compactMap ( URL . init )
259
239
}
260
240
261
- private static func renderForEditor( styles: String , defaultScheme: String ? ) throws -> String {
262
- let html = """
263
- <html>
264
- <head>
265
- \( styles)
266
- </head>
267
- <body></body>
268
- </html>
269
- """
270
- let document = try SwiftSoup . parse ( html)
271
-
272
- for stylesheet in try document. select ( #"link[rel="stylesheet"][href]"# ) {
273
- if let href = try ? stylesheet. attr ( " href " ) {
274
- let link = Self . resolveAssetLink ( href, defaultScheme: defaultScheme)
275
- #if canImport(UIKit)
276
- let newLink = CachedAssetSchemeHandler . cachedURL ( forWebLink: link) ?? link
277
- #else
278
- let newLink = link
279
- #endif
280
- try stylesheet. attr ( " href " , newLink)
281
- }
241
+ func getStyleUrlStrings( using parser: EditorAssetManifestParser ) throws -> [ String ] {
242
+ try parser. extractStyleURLs ( from: self . styles)
243
+ }
244
+
245
+ func getStyleUrls( using parser: EditorAssetManifestParser ) throws -> [ URL ] {
246
+ try getStyleUrlStrings ( using: parser) . compactMap ( URL . init)
247
+ }
248
+
249
+ func getAllAssetUrls( applyingDefaultScheme scheme: String ? = nil , using parser: EditorAssetManifestParser ) throws -> [ URL ] {
250
+ let scriptUrls = try self . getScriptUrls ( using: parser)
251
+ let styleUrls = try self . getStyleUrls ( using: parser)
252
+
253
+ return scriptUrls + styleUrls
254
+ }
255
+
256
+ func applyingUrlScheme( _ newScheme: String ? , using manifestParser: EditorAssetManifestParser ) throws -> Self {
257
+ var mutableStyles = self . styles
258
+ var mutableScripts = self . scripts
259
+
260
+ for rawLink in try getStyleUrlStrings ( using: manifestParser) {
261
+ let resolvedLink = EditorAssetSchemeResolver . resolveSchemeFor ( rawLink, defaultScheme: newScheme)
262
+ mutableStyles = mutableStyles. replacingOccurrences ( of: rawLink, with: resolvedLink)
263
+ }
264
+
265
+ for rawLink in try getScriptUrlStrings ( using: manifestParser) {
266
+ let resolvedLink = EditorAssetSchemeResolver . resolveSchemeFor ( rawLink, defaultScheme: newScheme)
267
+ mutableScripts = mutableScripts. replacingOccurrences ( of: rawLink, with: resolvedLink)
282
268
}
283
269
284
- let head = document. head ( ) !
285
- return try head. html ( )
270
+ return EditorAssetManifest (
271
+ scripts: mutableScripts,
272
+ styles: mutableStyles,
273
+ allowedBlockTypes: self . allowedBlockTypes
274
+ )
286
275
}
287
276
288
- private static func resolveAssetLink( _ link: String , defaultScheme: String ? ) -> String {
289
- if link. starts ( with: " // " ) {
290
- return " \( defaultScheme ?? " https " ) : \( link) "
277
+ func resolvingCachedUrls( using manifestParser: EditorAssetManifestParser ) throws -> Self {
278
+ var mutableStyles = self . styles
279
+ var mutableScripts = self . scripts
280
+
281
+ for url in try getStyleUrls ( using: manifestParser) {
282
+ let cachedLink = CachedAssetSchemeHandler . cachedURL ( for: url)
283
+ mutableStyles = mutableStyles. replacingOccurrences ( of: url. absoluteString, with: cachedLink. absoluteString)
291
284
}
292
285
293
- return link
286
+ for url in try getScriptUrls ( using: manifestParser) {
287
+ let cachedLink = CachedAssetSchemeHandler . cachedURL ( for: url)
288
+ mutableScripts = mutableScripts. replacingOccurrences ( of: url. absoluteString, with: cachedLink. absoluteString)
289
+ }
290
+
291
+ return EditorAssetManifest (
292
+ scripts: mutableScripts,
293
+ styles: mutableStyles,
294
+ allowedBlockTypes: self . allowedBlockTypes
295
+ )
294
296
}
295
297
}
0 commit comments