about summary refs log tree commit diff
path: root/modules/BlueskyClip/ViewController.swift
blob: e8adb532595e941020f54860c6873f64e680d803 (plain) (blame)
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
import UIKit
import WebKit
import StoreKit

class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate {
  let defaults = UserDefaults(suiteName: "group.app.bsky")

  var window: UIWindow
  var webView: WKWebView?

  var prevUrl: URL?
  var starterPackUrl: URL?

  init(window: UIWindow) {
    self.window = window
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let contentController = WKUserContentController()
    contentController.add(self, name: "onMessage")
    let configuration = WKWebViewConfiguration()
    configuration.userContentController = contentController

    let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
    webView.translatesAutoresizingMaskIntoConstraints = false
    webView.contentMode = .scaleToFill
    webView.navigationDelegate = self
    self.view.addSubview(webView)
    self.webView = webView
    self.webView?.load(URLRequest(url: URL(string: "https://bsky.app/?splash=true&clip=true")!))
  }

  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard let response = message.body as? String,
          let data = response.data(using: .utf8),
          let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else {
      return
    }
    
    switch payload.action {
    case .present:
      self.presentAppStoreOverlay()

      if let url = self.starterPackUrl {
        defaults?.setValue(url.absoluteString, forKey: "starterPackUri")
      }
    case .store:
      guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else {
        return
      }

      self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs)
    }
  }

  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
    // Detect when we land on the right URL. This is incase of a short link opening the app clip
    guard let url = navigationAction.request.url else {
      return .allow
    }
    
    // Store the previous one to compare later, but only set starterPackUrl when we find the right one
    prevUrl = url
    // pathComponents starts with "/" as the first component, then each path name. so...
    // ["/", "start", "name", "rkey"]
    if isStarterPackUrl(url){
      self.starterPackUrl = url
    }
    
    return .allow
  }
  
  func isStarterPackUrl(_ url: URL) -> Bool {
    var host: String?
    if #available(iOS 16.0, *) {
      host = url.host()
    } else {
      host = url.host
    }
    
    switch host {
    case "bsky.app":
      if url.pathComponents.count == 4,
         (url.pathComponents[1] == "start" || url.pathComponents[1] == "starter-pack") {
        return true
      }
      return false
    case "go.bsky.app":
      if url.pathComponents.count == 2 {
        return true
      }
      return false
    default:
      return false
    }
  }

  func handleURL(url: URL) {
    if isStarterPackUrl(url) {
      let urlString = "\(url.absoluteString)?clip=true"
      if let url = URL(string: urlString) {
        self.webView?.load(URLRequest(url: url))
      }
    } else {
      self.webView?.load(URLRequest(url: URL(string: "https://bsky.app/?splash=true&clip=true")!))
    }
  }

  func presentAppStoreOverlay() {
    guard let windowScene = self.window.windowScene else {
      return
    }

    let configuration = SKOverlay.AppClipConfiguration(position: .bottomRaised)
    let overlay = SKOverlay(configuration: configuration)

    overlay.present(in: windowScene)
  }

  func getHost(_ url: URL?) -> String? {
    if #available(iOS 16.0, *) {
      return url?.host()
    } else {
      return url?.host
    }
  }

  func getQuery(_ url: URL?) -> String? {
    if #available(iOS 16.0, *) {
      return url?.query()
    } else {
      return url?.query
    }
  }

  func urlMatchesPrevious(_ url: URL?) -> Bool {
    if #available(iOS 16.0, *) {
      return url?.query() == prevUrl?.query() && url?.host() == prevUrl?.host() && url?.query() == prevUrl?.query()
    } else {
      return url?.query == prevUrl?.query && url?.host == prevUrl?.host && url?.query == prevUrl?.query
    }
  }
}

struct WebViewActionPayload: Decodable {
  enum Action: String, Decodable {
    case present, store
  }

  let action: Action
  let keyToStoreAs: String?
  let jsonToStore: String?
}