11import Foundation
22import CryptoKit
3- import SwiftSoup
43
54public actor EditorAssetsLibrary {
65 enum ManifestError : Error {
76 case unavailable
87 case invalidServerResponse
8+ case invalidSiteUrl
99 }
1010
1111 let urlSession : URLSession
@@ -47,10 +47,14 @@ public actor EditorAssetsLibrary {
4747 /// - SeeAlso: `EditorAssetsLibrary.addAsset`
4848 func manifestContentForEditor( ) async throws -> Data {
4949 // 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+
5154 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) )
5458 }
5559
5660 /// Fetches all assets in the `EditorConfiguration.editorAssetsEndpoint` manifest and stores them on the device.
@@ -61,30 +65,27 @@ public actor EditorAssetsLibrary {
6165 let siteURLScheme = URL ( string: configuration. siteURL) ? . scheme
6266
6367 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)
7271
72+ for url in assetUrls {
7373 guard url. scheme == " http " || url. scheme == " https " else {
74- NSLog ( " Unexpected asset link: \( link ) " )
74+ NSLog ( " Unexpected asset link: \( url ) " )
7575 continue
7676 }
7777
78- _ = try await cacheAsset ( from: url)
78+ try await cacheAsset ( from: url)
7979 }
80- NSLog ( " \( assetLinks . count) resources processed. " )
80+ NSLog ( " \( assetUrls . count) resources processed. " )
8181 }
8282
8383 /// Fetches one asset (JavaScript or stylesheet) and caches its content on the device.
8484 ///
8585 /// - Parameters:
8686 /// - httpURL: The javascript or css URL.
8787 /// - webViewURL: The corresponding URL requested by web view, which should the "GBK cache prefix" (`gbk-cache-https://`)
88+ @discardableResult
8889 func cacheAsset( from httpURL: URL , webViewURL: URL ? = nil ) async throws -> ( URLResponse , Data ) {
8990 // The Web Inspector automatically requests ".js.map" files, we'll support it here for debugging purpose.
9091 let supportedResourceSuffixes = [ " .js " , " .css " , " .js.map " ]
@@ -191,105 +192,106 @@ private extension String {
191192 }
192193}
193194
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 ]
198216
199217 enum CodingKeys : String , CodingKey {
200218 case scripts
201219 case styles
202220 case allowedBlockTypes = " allowed_block_types "
203221 }
204222
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)
225225 }
226226
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
232231 }
233232
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+ }
256236
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 )
259239 }
260240
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)
282268 }
283269
284- let head = document. head ( ) !
285- return try head. html ( )
270+ return EditorAssetManifest (
271+ scripts: mutableScripts,
272+ styles: mutableStyles,
273+ allowedBlockTypes: self . allowedBlockTypes
274+ )
286275 }
287276
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)
291284 }
292285
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+ )
294296 }
295297}
0 commit comments