func find(_ predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.Iterator.Element? {

return try index(where: predicate).map({self[$0]})

replace replace replace replace replace replace replace replace keep keep keep keep keep

import WebKit

let oneYearInSeconds = 60 * 60 * 24 * 365

let GCDWebServerRequestAttribute_Asset = "GCDWebServerRequestAttribute_Asset"
let GCDWebServerRequestAttribute_FilePath = "GCDWebServerRequestAttribute_FilePath"

let localFileSystemPath = "/local-filesystem"

@objc(METWebAppLocalServer)
open class WebAppLocalServer: METPlugin, AssetBundleManagerDelegate {
  /// The local web server responsible for serving assets to the web app
  private(set) var localServer: GCDWebServer!

  /// The listening port of the local web server
  private var localServerPort: UInt = 0

  let authTokenKeyValuePair: String = {
    let authToken = ProcessInfo.processInfo.globallyUniqueString
    return "cdvToken=\(authToken)"
  }()

  /// The www directory in the app bundle
  private(set) var wwwDirectoryURL: URL!

  /// Persistent configuration settings for the webapp
  private(set) var configuration: WebAppConfiguration!

  /// The asset bundle manager is responsible for managing asset bundles
  /// and checking for updates
  private(set) var assetBundleManager: AssetBundleManager!

  /// The asset bundle currently used to serve assets from
  private var currentAssetBundle: AssetBundle! {
    didSet {
      if currentAssetBundle != nil {
        configuration.appId = currentAssetBundle.appId
        configuration.rootURL = currentAssetBundle.rootURL
        configuration.cordovaCompatibilityVersion = currentAssetBundle.cordovaCompatibilityVersion

        NSLog("Serving asset bundle version: \(currentAssetBundle.version)")
      }
    }
  }

  /// Downloaded asset bundles are considered pending until the next page reload
  /// because we don't want the app to end up in an inconsistent state by
  /// loading assets from different bundles.
  private var pendingAssetBundle: AssetBundle?

  /// Callback ID used to send a newVersionReady notification to JavaScript
  var newVersionReadyCallbackId: String?

  /// Callback ID used to send an error notification to JavaScript
  var errorCallbackId: String?

  /// Timer used to wait for startup to complete after a reload
  private var startupTimer: METTimer?

  /// The number of seconds to wait for startup to complete, after which
  /// we revert to the last known good version
  private var startupTimeoutInterval: TimeInterval = 20.0

  private var isTesting: Bool = false

  // MARK: - Lifecycle

  /// Called by Cordova on plugin initialization
  override open func pluginInitialize() {
    super.pluginInitialize()

    // Detect whether we are testing the app using
    // cordova-plugin-test-framework
    if let viewController = self.viewController as? CDVViewController,
      viewController.startPage == "cdvtests/index.html" {
        isTesting = true
    }

    configuration = WebAppConfiguration()

    wwwDirectoryURL = Bundle.main.resourceURL!.appendingPathComponent("www")

    initializeAssetBundles()

    // The WebAppLocalServerPort setting is currently only used for testing
    if let portString = (commandDelegate?.settings["WebAppLocalServerPort".lowercased()] as? String),
       let localServerPort = UInt(portString) {
      self.localServerPort = localServerPort
    // In all other cases, we use a listening port that has been set during build
    // and that is determined based on the appId. Hopefully this will avoid
    // collisions between Meteor apps installed on the same device
    } else if let viewController = self.viewController as? CDVViewController,
        let port = URLComponents(string: viewController.startPage)?.port {
      localServerPort = UInt(port)
    }

    do {
      try startLocalServer()
    } catch {
      NSLog("Could not start local server: \(error)")
      return
    }

    if let startupTimeoutString = (commandDelegate?.settings["WebAppStartupTimeout".lowercased()] as? String),
       let startupTimeoutMilliseconds = UInt(startupTimeoutString) {
      startupTimeoutInterval =  TimeInterval(startupTimeoutMilliseconds / 1000)
    }

    if !isTesting {
      startupTimer = METTimer(queue: DispatchQueue.global(qos: .utility)) { [weak self] in
        NSLog("App startup timed out, reverting to last known good version")
        self?.revertToLastKnownGoodVersion()
      }
    }

    NotificationCenter.default.addObserver(self, selector: #selector(WebAppLocalServer.applicationDidEnterBackground), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(WebAppLocalServer.pageDidLoad), name: NSNotification.Name.CDVPageDidLoad, object: webView)
  }

  func initializeAssetBundles() {
    assetBundleManager = nil;

    // The initial asset bundle consists of the assets bundled with the app
    let initialAssetBundle: AssetBundle
    do {
      let directoryURL = wwwDirectoryURL.appendingPathComponent("application")
      initialAssetBundle = try AssetBundle(directoryURL: directoryURL)
    } catch {
      NSLog("Could not load initial asset bundle: \(error)")
      return
    }

    let fileManager = FileManager.default

    // Downloaded versions are stored in Library/NoCloud/meteor
    let libraryDirectoryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
    let versionsDirectoryURL = libraryDirectoryURL.appendingPathComponent("NoCloud/meteor")

    // If the last seen initial version is different from the currently bundled
    // version, we delete the versions directory and unset lastDownloadedVersion
    // and blacklistedVersions
    if configuration.lastSeenInitialVersion != initialAssetBundle.version {
      do {
        if fileManager.fileExists(atPath: versionsDirectoryURL.path) {
          try fileManager.removeItem(at: versionsDirectoryURL)
        }
      } catch {
        NSLog("Could not remove versions directory: \(error)")
      }

      configuration.reset()
    }

    // We keep track of the last seen initial version (see above)
    configuration.lastSeenInitialVersion = initialAssetBundle.version

    // If the versions directory does not exist, we create it
    do {
      if !fileManager.fileExists(atPath: versionsDirectoryURL.path) {
        try fileManager.createDirectory(at: versionsDirectoryURL, withIntermediateDirectories: true, attributes: nil)
      }
    } catch {
      NSLog("Could not create versions directory: \(error)")
      return
    }

    assetBundleManager = AssetBundleManager(configuration: configuration, versionsDirectoryURL: versionsDirectoryURL, initialAssetBundle: initialAssetBundle)
    assetBundleManager.delegate = self

    // If a last downloaded version has been set and the asset bundle exists,
    // we set it as the current asset bundle
    if let lastDownloadedVersion = configuration.lastDownloadedVersion,
      let downloadedAssetBundle = assetBundleManager.downloadedAssetBundleWithVersion(lastDownloadedVersion) {
        currentAssetBundle = downloadedAssetBundle
    } else {
      currentAssetBundle = initialAssetBundle
    }

    pendingAssetBundle = nil
  }

  /// Called by Cordova before page reload
  override open func onReset() {
    super.onReset()

    // Clear existing callbacks
    newVersionReadyCallbackId = nil
    errorCallbackId = nil

    // If there is a pending asset bundle, we make it the current
    if let pendingAssetBundle = pendingAssetBundle {
      currentAssetBundle = pendingAssetBundle
      self.pendingAssetBundle = nil
    }

    startupTimer?.start(withTimeInterval: startupTimeoutInterval)
  }

  // MARK: - Notifications

  func pageDidLoad() {
  }

  func applicationDidEnterBackground() {
    // Stop startup timer when going into the background, to avoid
    // blacklisting a version just because the web view has been suspended
    startupTimer?.stop()
  }

  // MARK: - Public plugin commands

  open func startupDidComplete(_ command: CDVInvokedUrlCommand) {
    startupTimer?.stop()

    // If startup completed successfully, we consider a version good
    configuration.lastKnownGoodVersion = currentAssetBundle.version

    commandDelegate?.run() {
      do {
        try self.assetBundleManager.removeAllDownloadedAssetBundlesExceptFor(self.currentAssetBundle)
      } catch {
        NSLog("Could not remove unused asset bundles: \(error)")
      }
    }

    let result = CDVPluginResult(status: CDVCommandStatus_OK)
    self.commandDelegate?.send(result, callbackId: command.callbackId)
  }

  open func checkForUpdates(_ command: CDVInvokedUrlCommand) {
    guard let rootURL = configuration.rootURL else {
      let errorMessage = "checkForUpdates requires a rootURL to be configured"
      let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: errorMessage)
      commandDelegate?.send(result, callbackId: command.callbackId)
      return
    }

    let baseURL = rootURL.appendingPathComponent("__cordova/")
    assetBundleManager.checkForUpdatesWithBaseURL(baseURL)

    let result = CDVPluginResult(status: CDVCommandStatus_OK)
    commandDelegate?.send(result, callbackId: command.callbackId)
  }

  open func onNewVersionReady(_ command: CDVInvokedUrlCommand) {
    newVersionReadyCallbackId = command.callbackId

    let result = CDVPluginResult(status: CDVCommandStatus_NO_RESULT)
    // This allows us to invoke the callback later
    result?.setKeepCallbackAs(true)
    commandDelegate?.send(result, callbackId: newVersionReadyCallbackId)
  }

  private func notifyNewVersionReady(_ version: String?) {
    guard let newVersionReadyCallbackId = newVersionReadyCallbackId else { return }

    let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: version)
    // This allows us to invoke the callback later
    result?.setKeepCallbackAs(true)
    commandDelegate?.send(result, callbackId: newVersionReadyCallbackId)
  }

  open func onError(_ command: CDVInvokedUrlCommand) {
    errorCallbackId = command.callbackId

    let result = CDVPluginResult(status: CDVCommandStatus_NO_RESULT)
    // This allows us to invoke the callback later
    result?.setKeepCallbackAs(true)
    commandDelegate?.send(result, callbackId: errorCallbackId)
  }

  private func notifyError(_ error: Error) {
    NSLog("Download failure: \(error)")

    guard let errorCallbackId = errorCallbackId else { return }

    let errorMessage = String(describing: error)
    let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: errorMessage)
    // This allows us to invoke the callback later
    result?.setKeepCallbackAs(true)
    commandDelegate?.send(result, callbackId: errorCallbackId)
  }

  // MARK: - Managing Versions

  func revertToLastKnownGoodVersion() {
    // Blacklist the current version, so we don't update to it again right away
    configuration.addBlacklistedVersion(currentAssetBundle.version)

    // If there is a last known good version and we can load the bundle, revert to it
    if let lastKnownGoodVersion = configuration.lastKnownGoodVersion,
        let lastKnownGoodAssetBundle = assetBundleManager.downloadedAssetBundleWithVersion(lastKnownGoodVersion) {
      pendingAssetBundle = lastKnownGoodAssetBundle
    // Else, revert to the initial asset bundle, unless that is what we are
    // currently serving
  } else if currentAssetBundle.version != assetBundleManager.initialAssetBundle.version {
      pendingAssetBundle = assetBundleManager.initialAssetBundle
    }

    // Only reload if we have a pending asset bundle to reload
    if pendingAssetBundle != nil {
      forceReload()
    }
  }

  func forceReload() {
    if let webView = self.webView as? WKWebView {
      webView.reloadFromOrigin()
    }
  }

  // MARK: AssetBundleManagerDelegate

  func assetBundleManager(_ assetBundleManager: AssetBundleManager, shouldDownloadBundleForManifest manifest: AssetManifest) -> Bool {
    // No need to redownload the current or the pending version
    if currentAssetBundle.version == manifest.version || pendingAssetBundle?.version == manifest.version {
      return false
    }

    // Don't download blacklisted versions
    if configuration.blacklistedVersions.contains(manifest.version) {
      notifyError(WebAppError.unsuitableAssetBundle(reason: "Skipping downloading blacklisted version", underlyingError: nil))
      return false
    }

    // Don't download versions potentially incompatible with the bundled native code
    if manifest.cordovaCompatibilityVersion != configuration.cordovaCompatibilityVersion {
      notifyError(WebAppError.unsuitableAssetBundle(reason: "Skipping downloading new version because the Cordova platform version or plugin versions have changed and are potentially incompatible", underlyingError: nil))
      return false
    }

    return true
  }

  func assetBundleManager(_ assetBundleManager: AssetBundleManager, didFinishDownloadingBundle assetBundle: AssetBundle) {
    NSLog("Finished downloading new asset bundle version: \(assetBundle.version)")

    configuration.lastDownloadedVersion = assetBundle.version
    pendingAssetBundle = assetBundle
    notifyNewVersionReady(assetBundle.version)
  }

  func assetBundleManager(_ assetBundleManager: AssetBundleManager, didFailDownloadingBundleWithError error: Error) {
    notifyError(error)
  }

  // MARK: - Local server

  func startLocalServer() throws {
    localServer = GCDWebServer()
    // setLogLevel for some reason expects an int instead of an enum
    GCDWebServer.setLogLevel(GCDWebServerLoggingLevel.info.rawValue)

    // Handlers are added last to first
    addNotFoundHandler()
    addIndexFileHandler()
    addHandlerForLocalFileSystem()
    addHandlerForWwwDirectory()
    addHandlerForAssetBundle()

    let options = [
      GCDWebServerOption_Port: NSNumber(value: localServerPort as UInt),
      GCDWebServerOption_BindToLocalhost: true]
    try localServer.start(options: options)

    // Set localServerPort to the assigned port, in case it is different
    localServerPort = localServer.port

    if !isTesting, let viewController = self.viewController as? CDVViewController {
      // Do not modify startPage if we are testing the app using
      // cordova-plugin-test-framework
      viewController.startPage = "http://localhost:\(localServerPort)?\(authTokenKeyValuePair)"
    }
  }

  // MARK: Request Handlers

  private func addHandlerForAssetBundle() {
    localServer.addHandler(match: { [weak self] (requestMethod, requestURL, requestHeaders, urlPath, urlQuery) -> GCDWebServerRequest! in
      if requestMethod != "GET" { return nil }
      guard let urlPath = urlPath else { return nil }
      guard let asset = self?.currentAssetBundle?.assetForURLPath(urlPath) else { return nil }

      let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)!
      request.setAttribute(Box(asset), forKey: GCDWebServerRequestAttribute_Asset)
      return request
    }) { (request) -> GCDWebServerResponse! in
        let asset = (request?.attribute(forKey: GCDWebServerRequestAttribute_Asset) as! Box<Asset>).value
        return self.responseForAsset(request!, asset: asset)
    }
  }

  private func addHandlerForWwwDirectory() {
    localServer.addHandler(match: { [weak self] (requestMethod, requestURL, requestHeaders, urlPath, urlQuery) -> GCDWebServerRequest! in
      if requestMethod != "GET" { return nil }
      guard let urlPath = urlPath else { return nil }

      // Do not serve files from /application, because these should only be served through the initial asset bundle
      if (urlPath.hasPrefix("/application")) { return nil }

      guard let fileURL = self?.wwwDirectoryURL?.appendingPathComponent(urlPath) else { return nil }
      if fileURL.isRegularFile != true { return nil }

      let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)
      request?.setAttribute(fileURL.path, forKey: GCDWebServerRequestAttribute_FilePath)
      return request
    }) { (request) -> GCDWebServerResponse! in
      let filePath = request?.attribute(forKey: GCDWebServerRequestAttribute_FilePath) as! String
      return self.responseForFile(request!, filePath: filePath, cacheable: false)
    }
  }

  private func addHandlerForLocalFileSystem() {
    localServer.addHandler(match: { (requestMethod, requestURL, requestHeaders, urlPath, urlQuery) -> GCDWebServerRequest! in
      if requestMethod != "GET" { return nil }
      guard let urlPath = urlPath else { return nil }

      if !(urlPath.hasPrefix(localFileSystemPath)) { return nil }

      let filePath = urlPath.substring(from: localFileSystemPath.endIndex)
      let fileURL = URL(fileURLWithPath: filePath)
      if fileURL.isRegularFile != true { return nil }

      let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)
      request?.setAttribute(filePath, forKey: GCDWebServerRequestAttribute_FilePath)
      return request
      }) { (request) -> GCDWebServerResponse! in
        let filePath = request?.attribute(forKey: GCDWebServerRequestAttribute_FilePath) as! String
        return self.responseForFile(request!, filePath: filePath, cacheable: false)
    }
  }

  private func addIndexFileHandler() {
    localServer.addHandler(match: { [weak self] (requestMethod, requestURL, requestHeaders, urlPath, urlQuery) -> GCDWebServerRequest! in
      if requestMethod != "GET" { return nil }
      guard let urlPath = urlPath else { return nil }

      // Don't serve index.html for local file system paths
      if (urlPath.hasPrefix(localFileSystemPath)) { return nil }

      if urlPath == "/favicon.ico" { return nil }

      guard let indexFile = self?.currentAssetBundle?.indexFile else { return nil }

      let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)
      request?.setAttribute(Box(indexFile), forKey: GCDWebServerRequestAttribute_Asset)
      return request
      }) { (request) -> GCDWebServerResponse! in
        let asset = (request?.attribute(forKey: GCDWebServerRequestAttribute_Asset) as! Box<Asset>).value
        return self.responseForAsset(request!, asset: asset)
    }
  }

  private func addNotFoundHandler() {
    localServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self) { (request) -> GCDWebServerResponse! in
      return GCDWebServerResponse(statusCode: GCDWebServerClientErrorHTTPStatusCode.httpStatusCode_NotFound.rawValue)
    }
  }

  private func responseForAsset(_ request: GCDWebServerRequest, asset: Asset) -> GCDWebServerResponse {
    let filePath = asset.fileURL.path
    return responseForFile(request, filePath: filePath, cacheable: asset.cacheable, hash: asset.hash, sourceMapURLPath: asset.sourceMapURLPath)
  }

  private func responseForFile(_ request: GCDWebServerRequest, filePath: String, cacheable: Bool, hash: String? = nil, sourceMapURLPath: String? = nil) -> GCDWebServerResponse {
    // To protect our server from access by other apps running on the same device,
    // we check whether the rponsequest contains an auth token.
    // The auth token can be passed either as a query item or as a cookie.
    // If the auth token was passed as a query item, we set the cookie.
    var shouldSetCookie = false
    if let query = request.url.query, query.contains(authTokenKeyValuePair) {
      shouldSetCookie = true
    } else if let cookie = request.headers["Cookie"], (cookie as AnyObject).contains(authTokenKeyValuePair) {
    } else {
      return GCDWebServerResponse(statusCode: GCDWebServerClientErrorHTTPStatusCode.httpStatusCode_Forbidden.rawValue)
    }

    if !FileManager.default.fileExists(atPath: filePath) {
      NSLog("File not found: \(filePath)")
      return GCDWebServerResponse(statusCode: GCDWebServerClientErrorHTTPStatusCode.httpStatusCode_NotFound.rawValue)
    }

    // Support partial requests using byte ranges
    guard let response = GCDWebServerFileResponse(file: filePath, byteRange: request.byteRange) else {
      return GCDWebServerResponse(statusCode: GCDWebServerClientErrorHTTPStatusCode.httpStatusCode_NotFound.rawValue)
    }

    response.setValue("bytes", forAdditionalHeader: "Accept-Ranges")

    if shouldSetCookie {
      response.setValue(authTokenKeyValuePair, forAdditionalHeader: "Set-Cookie")
    }

    // Only cache files when the file is cacheable and the request URL includes a cache buster
    let shouldCache = cacheable &&
      (!(request.url.query?.isEmpty ?? true)
        || sha1HashRegEx.matches(request.url.path))
    response.cacheControlMaxAge = UInt(shouldCache ? oneYearInSeconds : 0)

    // If we don't set an ETag ourselves, GCDWebServerFileResponse will generate
    // one based on the inode of the file
    if let hash = hash {
      response.eTag = hash
    }

    // GCDWebServerFileResponse sets this to the file modification date, which
    // isn't very useful for our purposes and would hamper
    // the ability to serve conditional requests
    response.lastModifiedDate = nil

    // If the asset has a source map, set the X-SourceMap header
    if let sourceMapURLPath = sourceMapURLPath,
        let sourceMapURL = URL(string: sourceMapURLPath, relativeTo: localServer.serverURL) {
      response.setValue(sourceMapURL.absoluteString, forAdditionalHeader: "X-SourceMap")
    }

    return response
  }
}


HTTP/1.1 404 Not Found
Content-Length: 201
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 05:11:00 GMT

<!DOCTYPE html><meta charset="utf-8"><title>HTTP Error 404</title>
HTTP Error 404: "/images/._hero_mba_11.jpg" does not exist


PROPFIND /images/._Green%20iPad.png HTTP/1.1
Host: localhost:8080
Content-Type: text/xml
Depth: 0
Accept: */*
User-Agent: WebDAVFS/3.0.1 (03018000) Darwin/13.1.0 (x86_64)
Content-Length: 179
Connection: keep-alive

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:getlastmodified/>
<D:getcontentlength/>
<D:creationdate/>
<D:resourcetype/>
</D:prop>
</D:propfind>


guard let asset = self?.currentAssetBundle?.assetForURLPath(urlPath) else { return nil }

let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)!

}) { (request) -> GCDWebServerResponse! in


HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 1037
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 05:11:27 GMT

<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/PDF%20Reports/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/PDF%20Reports/Apple%20Economic%20Impact%20on%20Cupertino.pdf</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2013-05-01T12:01:13+00:00</D:creationdate><D:getlastmodified>Wed, 01 May 2013 12:01:13 GMT</D:getlastmodified><D:getcontentlength>181952</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/PDF%20Reports/Test%20File.txt</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2014-04-12T05:11:26+00:00</D:creationdate><D:getlastmodified>Sat, 12 Apr 2014 05:11:26 GMT</D:getlastmodified><D:getcontentlength>21</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>


HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 328
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 05:11:26 GMT

<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/PDF%20Reports/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>


let asset = (request?.attribute(forKey: GCDWebServerRequestAttribute_Asset) as! Box<Asset>).value

return self.responseForAsset(request!, asset: asset)

private func addHandlerForWwwDirectory() {

keep keep replace keep keep keep keep keep

let asset = request?.attribute(forKey: GCDWebServerRequestAttribute_Asset) as! Asset


PUT /.DS_Store HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 0
Connection: keep-alive
User-Agent: WebDAVFS/3.0.1 (03018000) Darwin/13.1.0 (x86_64)


PROPFIND /PDF%20Reports/ HTTP/1.1
Host: localhost:8080
Content-Type: text/xml
Depth: 1
Accept: */*
User-Agent: WebDAVFS/3.0.1 (03018000) Darwin/13.1.0 (x86_64)
Content-Length: 179
Connection: keep-alive

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:getlastmodified/>
<D:getcontentlength/>
<D:creationdate/>
<D:resourcetype/>
</D:prop>
</D:propfind>


guard let indexFile = self?.currentAssetBundle?.indexFile else { return nil }

let request = GCDWebServerRequest(method: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, query: urlQuery)


PROPFIND /PDF%20Reports/ HTTP/1.1
Host: localhost:8080
Content-Type: text/xml
Depth: 0
Accept: */*
User-Agent: WebDAVFS/3.0.1 (03018000) Darwin/13.1.0 (x86_64)
Content-Length: 179
Connection: keep-alive

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:getlastmodified/>
<D:getcontentlength/>
<D:creationdate/>
<D:resourcetype/>
</D:prop>
</D:propfind>


PROPFIND / HTTP/1.1
Host: localhost:8080
Content-Type: text/xml
Depth: 0
Accept: */*
User-Agent: WebDAVFS/3.0.1 (03018000) Darwin/13.1.0 (x86_64)
Content-Length: 175
Connection: keep-alive

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:quota-available-bytes/>
<D:quota-used-bytes/>
<D:quota/>
<D:quotaused/>
</D:prop>
</D:propfind>


HTTP/1.1 207 Multi-Status
Cache-Control: no-cache
Content-Length: 1108
Content-Type: application/xml; charset="utf-8"
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 05:11:31 GMT

<?xml version="1.0" encoding="utf-8" ?><D:multistatus xmlns:D="DAV:">
<D:response><D:href>/</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/Apple%20Reports</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/Copy.txt</D:href><D:propstat><D:prop><D:resourcetype/><D:creationdate>2014-04-10T11:10:14+00:00</D:creationdate><D:getlastmodified>Thu, 10 Apr 2014 11:10:14 GMT</D:getlastmodified><D:getcontentlength>271</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
<D:response><D:href>/images</D:href><D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>2014-01-01T00:00:00+00:00</D:creationdate></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>
</D:multistatus>


HTTP/1.1 403 Forbidden
Content-Length: 221
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebDAVServer
Date: Sat, 12 Apr 2014 05:11:44 GMT

<!DOCTYPE html><meta charset="utf-8"><title>HTTP Error 403</title>
HTTP Error 403: Retrieving properties for item name ".DS_Store" is not allowed


## Changelog
## 1.7.3 - 2020-10-05
### Breaking changes

- You can now override the default API version by setting `Meteor.settings.public.packages.facebook-oauth.apiVersion` to for example `8.0`

Facebook = {};
import crypto from 'crypto';

Facebook.handleAuthFromAccessToken = (accessToken, expiresAt) => {
  // include basic fields from facebook
  // https://developers.facebook.com/docs/facebook-login/permissions/
  const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name',
    'middle_name', 'name_format', 'picture', 'short_name'];

  const identity = getIdentity(accessToken, whitelisted);

  const fields = {};
  whitelisted.forEach(field => fields[field] = identity[field]);
  const serviceData = {
    accessToken,
    expiresAt,
    ...fields,
  };

  return {
    serviceData,
    options: {profile: {name: identity.name}}
  };
};

OAuth.registerService('facebook', 2, null, query => {
  const response = getTokenResponse(query);
  const { accessToken } = response;
  const { expiresIn } = response;

  return Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn));
});

function getAbsoluteUrlOptions(query) {
  const overrideRootUrlFromStateRedirectUrl = Meteor.settings?.packages?.['facebook-oauth']?.overrideRootUrlFromStateRedirectUrl;
  if (!overrideRootUrlFromStateRedirectUrl) {
    return undefined;
  }
  try {
    const state = OAuth._stateFromQuery(query) || {};
    const redirectUrl = new URL(state.redirectUrl);
    return {
      rootUrl: redirectUrl.origin,
    }
  } catch (e) {
    console.error(
      `Failed to complete OAuth handshake with Facebook because it was not able to obtain the redirect url from the state and you are using overrideRootUrlFromStateRedirectUrl.`, e
    );
    return undefined;
  }
}

// returns an object containing:
// - accessToken
// - expiresIn: lifetime of token in seconds
const getTokenResponse = query => {
  const config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
  if (!config)
    throw new ServiceConfiguration.ConfigError();

  let responseContent;
  try {

    const absoluteUrlOptions = getAbsoluteUrlOptions(query);
    const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions);
    // Request an access token
    responseContent = HTTP.get(
      "https://graph.facebook.com/v8.0/oauth/access_token", {
        params: {
          client_id: config.appId,
          redirect_uri: redirectUri,
          client_secret: OAuth.openSecret(config.secret),
          code: query.code
        }
      }).data;
  } catch (err) {
    throw Object.assign(
      new Error(`Failed to complete OAuth handshake with Facebook. ${err.message}`),
      { response: err.response },
    );
  }

  const fbAccessToken = responseContent.access_token;
  const fbExpires = responseContent.expires_in;

  if (!fbAccessToken) {
    throw new Error("Failed to complete OAuth handshake with facebook " +
                    `-- can't find access token in HTTP response. ${responseContent}`);
  }
  return {
    accessToken: fbAccessToken,
    expiresIn: fbExpires
  };
};

const getIdentity = (accessToken, fields) => {
  const config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
  if (!config)
    throw new ServiceConfiguration.ConfigError();

  // Generate app secret proof that is a sha256 hash of the app access token, with the app secret as the key
  // https://developers.facebook.com/docs/graph-api/securing-requests#appsecret_proof
  const hmac = crypto.createHmac('sha256', OAuth.openSecret(config.secret));
  hmac.update(accessToken);

  try {
    return HTTP.get("https://graph.facebook.com/v8.0/me", {
      params: {
        access_token: accessToken,
        appsecret_proof: hmac.digest('hex'),
        fields: fields.join(",")
      }
    }).data;
  } catch (err) {
    throw Object.assign(
      new Error(`Failed to fetch identity from Facebook. ${err.message}`),
      { response: err.response },
    );
  }
};

Facebook.retrieveCredential = (credentialToken, credentialSecret) =>
  OAuth.retrieveCredential(credentialToken, credentialSecret);


Plugin.registerCompiler({
  extensions: ['printme'],
  archMatching: 'os'
}, function () {
  throw new Error("Error in my registerCompiler callback!");
});


import AleaRandomGenerator from './AleaRandomGenerator';

// instantiate RNG.  Heuristically collect entropy from various sources when a
// cryptographic PRNG isn't available.

// client sources
const height = (typeof window !== 'undefined' && window.innerHeight) ||
      (typeof document !== 'undefined'
       && document.documentElement
       && document.documentElement.clientHeight) ||
      (typeof document !== 'undefined'
       && document.body
       && document.body.clientHeight) ||
      1;

const width = (typeof window !== 'undefined' && window.innerWidth) ||
      (typeof document !== 'undefined'
       && document.documentElement
       && document.documentElement.clientWidth) ||
      (typeof document !== 'undefined'
       && document.body
       && document.body.clientWidth) ||
      1;

const agent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';

export default function createAleaGenerator() {
  return new AleaRandomGenerator({
    seeds: [new Date, height, width, agent, Math.random()],
  });
}


if (options && options.requestPermissions)

const loginStyle = OAuth._loginStyle('facebook', config, options);

const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '10.0';

## Changelog

## 1.7.3 - 2020-10-05
### Breaking changes
- N/A

### Changes
- Updated to `1.8` now using Facebook GraphAPI v8.

## 1.7.2 - 2020-09-30
### Breaking changes
- N/A

### Changes
- Supports a new way to call the token from Facebook using a URL that is different from the ROOT_URL. With `overrideRootUrlFromStateRedirectUrl` as `true` in the settings (`packages.facebook-oauth.overrideRootUrlFromStateRedirectUrl`) we are going to use the redirect URL provided in the initial call. So the redirect URL will be used again in the server to get the token, this information will come from the state. This is important to authenticate using Facebook using different domains in the same server.

## 1.7.1 - 2020-09-29
### Breaking changes
- N/A

### Changes
- Supports `params` and `absoluteUrlOptions` as options and pass along to `OAuth._redirectUri`, this is important to authenticate using Facebook using different domains in the same server.


.pkg-lazy-css.imported {
  padding: 20px;
}

.not-imported {
  padding: 0px;
}


try {
  Symbol = exports.Symbol = require("core-js/es/symbol");
  Map = exports.Map = require("core-js/es/map");
  Set = exports.Set = require("core-js/es/set");

} catch (e) {
  throw new Error([
    "The core-js npm package could not be found in your node_modules ",
    "directory. Please run the following command to install it:",
    "",
    "  meteor npm install --save core-js",
    ""
  ].join("\n"));
}

// ECMAScript 2015 polyfills.
require("core-js/es/array");
require("core-js/es/function");
require("core-js/es/math");
require("core-js/es/object");
require("core-js/es/regexp");
require("core-js/es/string");
require("core-js/es/weak-map");
require("core-js/es/weak-set");

// If the Reflect global namespace is missing or undefined, explicitly
// initialize it as undefined, so that expressions like _typeof(Reflect)
// won't throw in older browsers. Fixes #9598.
if (typeof Reflect === "undefined") {
  global.Reflect = void 0;
}

// We want everything from the core-js/es/number module except
// es.number.constructor.
require('core-js/modules/es.number.epsilon');
require('core-js/modules/es.number.is-finite');
require('core-js/modules/es.number.is-integer');
require('core-js/modules/es.number.is-nan');
require('core-js/modules/es.number.is-safe-integer');
require('core-js/modules/es.number.max-safe-integer');
require('core-js/modules/es.number.min-safe-integer');
require('core-js/modules/es.number.parse-float');
require('core-js/modules/es.number.parse-int');

// Typed Arrays
require('core-js/modules/es.typed-array.uint8-array');
require('core-js/modules/es.typed-array.uint32-array');

require("./modern.js");


`&redirect_uri=${OAuth._redirectUri('facebook', config, options.params, options.absoluteUrlOptions)}` +

`&state=${OAuth._stateParam(loginStyle, credentialToken, options && options.redirectUrl)}`;


Package.describe({
  summary: "Moved to the 'ddp' package",
  version: '1.0.18'
});

Package.onUse(function (api) {
  api.use("ddp");
  api.imply("ddp");

  // XXX COMPAT WITH PACKAGES BUILT FOR 0.9.0.
  //
  // (in particular, packages that have a weak dependency on this
  // package, since then exported symbols live on the
  // `Package.livedata` object)
  api.export('DDP');
  api.export('DDPServer', 'server');
  api.export('LivedataTest', {testOnly: true});
});


Package.describe({
  name: "jquery-history",
  summary: "Deprecated package for HTML5 pushState",
  version: "1.0.2"
});

Package.onUse(function (api) {
  api.versionsFrom('1.0');
  api.use('json', 'client');
  api.use('jquery', 'client');
  api.addFiles(['history.adapter.jquery.js',
                 'history.html4.js',
                 'history.js'],
                'client');
});


Facebook.handleAuthFromAccessToken = (accessToken, expiresAt) => {

const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name',

Facebook = {};

// Request Facebook credentials for the user
//
// @param options {optional}
// @param credentialRequestCompleteCallback {Function} Callback function to call on
//   completion. Takes one argument, credentialToken on success, or Error on
//   error.
Facebook.requestCredential = (options, credentialRequestCompleteCallback) => {
  // support both (options, callback) and (callback).
  if (!credentialRequestCompleteCallback && typeof options === 'function') {
    credentialRequestCompleteCallback = options;
    options = {};
  }

  const config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
  if (!config) {
    credentialRequestCompleteCallback && credentialRequestCompleteCallback(
      new ServiceConfiguration.ConfigError());
    return;
  }

  const credentialToken = Random.secret();
  const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent);
  const display = mobile ? 'touch' : 'popup';

  let scope = "email";
  if (options && options.requestPermissions)
    scope = options.requestPermissions.join(',');

  const loginStyle = OAuth._loginStyle('facebook', config, options);

  let loginUrl =
      `https://www.facebook.com/v8.0/dialog/oauth?client_id=${config.appId}` +
      `&redirect_uri=${OAuth._redirectUri('facebook', config, options.params, options.absoluteUrlOptions)}` +
      `&display=${display}&scope=${scope}` +
      `&state=${OAuth._stateParam(loginStyle, credentialToken, options && options.redirectUrl)}`;

  // Handle authentication type (e.g. for force login you need auth_type: "reauthenticate")
  if (options && options.auth_type) {
    loginUrl += `&auth_type=${encodeURIComponent(options.auth_type)}`;
  }

  OAuth.launchLogin({
    loginService: "facebook",
    loginStyle,
    loginUrl,
    credentialRequestCompleteCallback,
    credentialToken,
  });
};


Package.describe({
  summary: "Register callbacks on a hook",
  version: '1.3.0'
});

Package.onUse(function (api) {
  api.use('ecmascript');
  api.mainModule('hook.js');
  api.export('Hook');
});

Package.onTest(function (api) {
  api.use('callback-hook');
  api.use('tinytest');
  api.addFiles('hook_tests.js', 'server');
});


const fs = require("fs");
const { convertToOSPath } = require("./convert-to-os-path.js");

exports.makeLink = function (target, linkPath) {
  target = convertToOSPath(target);
  linkPath = convertToOSPath(linkPath);

  var tempPath = linkPath + "-" + Math.random().toString(36).slice(2);

  try {
    fs.symlinkSync(target, tempPath, "junction");
  } catch (e) {
    fs.writeFileSync(tempPath, target, "utf8");
  }

  try {
    fs.renameSync(tempPath, linkPath);
  } catch (e) {
    // If renaming fails, try unlinking first.
    require("rimraf").sync(linkPath);
    fs.renameSync(tempPath, linkPath);
  }
};

// Note: this function returns an OS-specific path!
exports.readLink = function (linkPath) {
  linkPath = convertToOSPath(linkPath);

  var stat = fs.lstatSync(linkPath);
  if (stat.isSymbolicLink()) {
    linkPath = fs.realpathSync(linkPath);
  } else if (stat.isFile()) {
    linkPath = fs.readFileSync(linkPath, "utf8");
  }

  return linkPath;
};


try {
  var jQuery = require("jquery");
} catch (e) {
  console.warn([
    "The jquery npm package could not be found in your node_modules directory.",
    "Please run the following command to install it:",
    "",
    "  meteor npm install jquery",
    "",
    "If you previously relied on a specific version of jquery, it may be important",
    "to install that version; for example:",
    "",
    "  meteor npm install jquery@1.12.1",
    "",
  ].join("\n"));
}

if (jQuery) {
  // Provide values for the exported variables of the jquery package.
  exports.$ = exports.jQuery = jQuery;

  // There's no stopping legacy code from referring to window.$ or
  // window.jQuery, so we have to keep defining those properties globally,
  // but at least the exports of this package will be reliable.
  global.$ = global.$ || jQuery;
  global.jQuery = global.jQuery || jQuery;
}


const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions);

keep keep keep replace keep keep keep keep keep


import {AccountsCommon} from "./accounts_common.js";

/**
 * @summary Constructor for the `Accounts` object on the client.
 * @locus Client
 * @class AccountsClient
 * @extends AccountsCommon
 * @instancename accountsClient
 * @param {Object} options an object with fields:
 * @param {Object} options.connection Optional DDP connection to reuse.
 * @param {String} options.ddpUrl Optional URL for creating a new DDP connection.
 */
export class AccountsClient extends AccountsCommon {
  constructor(options) {
    super(options);

    this._loggingIn = new ReactiveVar(false);
    this._loggingOut = new ReactiveVar(false);

    this._loginServicesHandle =
      this.connection.subscribe("meteor.loginServiceConfiguration");

    this._pageLoadLoginCallbacks = [];
    this._pageLoadLoginAttemptInfo = null;

    this.savedHash = window.location.hash;
    this._initUrlMatching();

    // Defined in localstorage_token.js.
    this._initLocalStorage();

    // This is for .registerClientLoginFunction & .callLoginFunction.
    this._loginFuncs = {};
  }

  ///
  /// CURRENT USER
  ///

  // @override
  userId() {
    return this.connection.userId();
  }

  // This is mostly just called within this file, but Meteor.loginWithPassword
  // also uses it to make loggingIn() be true during the beginPasswordExchange
  // method call too.
  _setLoggingIn(x) {
    this._loggingIn.set(x);
  }

  /**
   * @summary True if a login method (such as `Meteor.loginWithPassword`, `Meteor.loginWithFacebook`, or `Accounts.createUser`) is currently in progress. A reactive data source.
   * @locus Client
   */
  loggingIn() {
    return this._loggingIn.get();
  }

  /**
   * @summary True if a logout method (such as `Meteor.logout`) is currently in progress. A reactive data source.
   * @locus Client
   */
  loggingOut() {
    return this._loggingOut.get();
  }

  /**
   * @summary Register a new login function on the client. Intended for OAuth package authors. You can call the login function by using
   `Accounts.callLoginFunction` or `Accounts.callLoginFunction`.
   * @locus Client
   * @param {String} funcName The name of your login function. Used by `Accounts.callLoginFunction` and `Accounts.applyLoginFunction`.
   Should be the OAuth provider name accordingly.
   * @param {Function} func The actual function you want to call. Just write it in the manner of `loginWithFoo`.
   */
  registerClientLoginFunction(funcName, func) {
    if (this._loginFuncs[funcName]) {
      throw new Error(`${funcName} has been defined already`);
    }
    this._loginFuncs[funcName] = func;
  }

  /**
   * @summary Call a login function defined using `Accounts.registerClientLoginFunction`. Excluding the first argument, all remaining
   arguments are passed to the login function accordingly. Use `applyLoginFunction` if you want to pass in an arguments array that contains
   all arguments for the login function.
   * @locus Client
   * @param {String} funcName The name of the login function you wanted to call.
   */
  callLoginFunction(funcName, ...funcArgs) {
    if (!this._loginFuncs[funcName]) {
      throw new Error(`${funcName} was not defined`);
    }
    return this._loginFuncs[funcName].apply(this, funcArgs);
  }

  /**
   * @summary Same as ``callLoginFunction` but accept an `arguments` which contains all arguments for the login
   function.
   * @locus Client
   * @param {String} funcName The name of the login function you wanted to call.
   * @param {Array} funcArgs The `arguments` for the login function.
   */
  applyLoginFunction(funcName, funcArgs) {
    if (!this._loginFuncs[funcName]) {
      throw new Error(`${funcName} was not defined`);
    }
    return this._loginFuncs[funcName].apply(this, funcArgs);
  }

  /**
   * @summary Log the user out.
   * @locus Client
   * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
   */
  logout(callback) {
    this._loggingOut.set(true);
    this.connection.apply('logout', [], {
      wait: true
    }, (error, result) => {
      this._loggingOut.set(false);
      if (error) {
        callback && callback(error);
      } else {
        this.makeClientLoggedOut();
        callback && callback();
      }
    });
  }

  /**
   * @summary Log out other clients logged in as the current user, but does not log out the client that calls this function.
   * @locus Client
   * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
   */
  logoutOtherClients(callback) {
    // We need to make two method calls: one to replace our current token,
    // and another to remove all tokens except the current one. We want to
    // call these two methods one after the other, without any other
    // methods running between them. For example, we don't want `logout`
    // to be called in between our two method calls (otherwise the second
    // method call would return an error). Another example: we don't want
    // logout to be called before the callback for `getNewToken`;
    // otherwise we would momentarily log the user out and then write a
    // new token to localStorage.
    //
    // To accomplish this, we make both calls as wait methods, and queue
    // them one after the other, without spinning off the event loop in
    // between. Even though we queue `removeOtherTokens` before
    // `getNewToken`, we won't actually send the `removeOtherTokens` call
    // until the `getNewToken` callback has finished running, because they
    // are both wait methods.
    this.connection.apply(
      'getNewToken',
      [],
      { wait: true },
      (err, result) => {
        if (! err) {
          this._storeLoginToken(
            this.userId(),
            result.token,
            result.tokenExpires
          );
        }
      }
    );

    this.connection.apply(
      'removeOtherTokens',
      [],
      { wait: true },
      err => callback && callback(err)
    );
  }

  ///
  /// LOGIN METHODS
  ///

  // Call a login method on the server.
  //
  // A login method is a method which on success calls `this.setUserId(id)` and
  // `Accounts._setLoginToken` on the server and returns an object with fields
  // 'id' (containing the user id), 'token' (containing a resume token), and
  // optionally `tokenExpires`.
  //
  // This function takes care of:
  //   - Updating the Meteor.loggingIn() reactive data source
  //   - Calling the method in 'wait' mode
  //   - On success, saving the resume token to localStorage
  //   - On success, calling Accounts.connection.setUserId()
  //   - Setting up an onReconnect handler which logs in with
  //     the resume token
  //
  // Options:
  // - methodName: The method to call (default 'login')
  // - methodArguments: The arguments for the method
  // - validateResult: If provided, will be called with the result of the
  //                 method. If it throws, the client will not be logged in (and
  //                 its error will be passed to the callback).
  // - userCallback: Will be called with no arguments once the user is fully
  //                 logged in, or with the error on error.
  //
  callLoginMethod(options) {
    options = {
      methodName: 'login',
      methodArguments: [{}],
      _suppressLoggingIn: false,
      ...options,
    };

    // Set defaults for callback arguments to no-op functions; make sure we
    // override falsey values too.
    ['validateResult', 'userCallback'].forEach(f => {
      if (!options[f])
        options[f] = () => null;
    })

    // Prepare callbacks: user provided and onLogin/onLoginFailure hooks.
    let called;
    const loginCallbacks = ({ error, loginDetails }) => {
      if (!called) {
        called = true;
        if (!error) {
          this._onLoginHook.each(callback => {
            callback(loginDetails);
            return true;
          });
        } else {
          this._onLoginFailureHook.each(callback => {
            callback({ error });
            return true;
          });
        }
        options.userCallback(error, loginDetails);
      }
    }

    let reconnected = false;

    // We want to set up onReconnect as soon as we get a result token back from
    // the server, without having to wait for subscriptions to rerun. This is
    // because if we disconnect and reconnect between getting the result and
    // getting the results of subscription rerun, we WILL NOT re-send this
    // method (because we never re-send methods whose results we've received)
    // but we WILL call loggedInAndDataReadyCallback at "reconnect quiesce"
    // time. This will lead to makeClientLoggedIn(result.id) even though we
    // haven't actually sent a login method!
    //
    // But by making sure that we send this "resume" login in that case (and
    // calling makeClientLoggedOut if it fails), we'll end up with an accurate
    // client-side userId. (It's important that livedata_connection guarantees
    // that the "reconnect quiesce"-time call to loggedInAndDataReadyCallback
    // will occur before the callback from the resume login call.)
    const onResultReceived = (err, result) => {
      if (err || !result || !result.token) {
        // Leave onReconnect alone if there was an error, so that if the user was
        // already logged in they will still get logged in on reconnect.
        // See issue #4970.
      } else {
        // First clear out any previously set Acccounts login onReconnect
        // callback (to make sure we don't keep piling up duplicate callbacks,
        // which would then all be triggered when reconnecting).
        if (this._reconnectStopper) {
          this._reconnectStopper.stop();
        }

        this._reconnectStopper = DDP.onReconnect(conn => {
          if (conn != this.connection) {
            return;
          }
          reconnected = true;
          // If our token was updated in storage, use the latest one.
          const storedToken = this._storedLoginToken();
          if (storedToken) {
            result = {
              token: storedToken,
              tokenExpires: this._storedLoginTokenExpires()
            };
          }
          if (!result.tokenExpires)
            result.tokenExpires = this._tokenExpiration(new Date());
          if (this._tokenExpiresSoon(result.tokenExpires)) {
            this.makeClientLoggedOut();
          } else {
            this.callLoginMethod({
              methodArguments: [{resume: result.token}],
              // Reconnect quiescence ensures that the user doesn't see an
              // intermediate state before the login method finishes. So we don't
              // need to show a logging-in animation.
              _suppressLoggingIn: true,
              userCallback: (error, loginDetails) => {
                const storedTokenNow = this._storedLoginToken();
                if (error) {
                  // If we had a login error AND the current stored token is the
                  // one that we tried to log in with, then declare ourselves
                  // logged out. If there's a token in storage but it's not the
                  // token that we tried to log in with, we don't know anything
                  // about whether that token is valid or not, so do nothing. The
                  // periodic localStorage poll will decide if we are logged in or
                  // out with this token, if it hasn't already. Of course, even
                  // with this check, another tab could insert a new valid token
                  // immediately before we clear localStorage here, which would
                  // lead to both tabs being logged out, but by checking the token
                  // in storage right now we hope to make that unlikely to happen.
                  //
                  // If there is no token in storage right now, we don't have to
                  // do anything; whatever code removed the token from storage was
                  // responsible for calling `makeClientLoggedOut()`, or the
                  // periodic localStorage poll will call `makeClientLoggedOut`
                  // eventually if another tab wiped the token from storage.
                  if (storedTokenNow && storedTokenNow === result.token) {
                    this.makeClientLoggedOut();
                  }
                }
                // Possibly a weird callback to call, but better than nothing if
                // there is a reconnect between "login result received" and "data
                // ready".
                loginCallbacks({ error, loginDetails });
              }});
          }
        });
      }
    };

    // This callback is called once the local cache of the current-user
    // subscription (and all subscriptions, in fact) are guaranteed to be up to
    // date.
    const loggedInAndDataReadyCallback = (error, result) => {
      // If the login method returns its result but the connection is lost
      // before the data is in the local cache, it'll set an onReconnect (see
      // above). The onReconnect will try to log in using the token, and *it*
      // will call userCallback via its own version of this
      // loggedInAndDataReadyCallback. So we don't have to do anything here.
      if (reconnected)
        return;

      // Note that we need to call this even if _suppressLoggingIn is true,
      // because it could be matching a _setLoggingIn(true) from a
      // half-completed pre-reconnect login method.
      this._setLoggingIn(false);
      if (error || !result) {
        error = error || new Error(
          `No result from call to ${options.methodName}`
        );
        loginCallbacks({ error });
        return;
      }
      try {
        options.validateResult(result);
      } catch (e) {
        loginCallbacks({ error: e });
        return;
      }

      // Make the client logged in. (The user data should already be loaded!)
      this.makeClientLoggedIn(result.id, result.token, result.tokenExpires);
      loginCallbacks({ loginDetails: { type: result.type } });
    };

    if (!options._suppressLoggingIn) {
      this._setLoggingIn(true);
    }
    this.connection.apply(
      options.methodName,
      options.methodArguments,
      { wait: true, onResultReceived: onResultReceived },
      loggedInAndDataReadyCallback);
  }

  makeClientLoggedOut() {
    // Ensure client was successfully logged in before running logout hooks.
    if (this.connection._userId) {
      this._onLogoutHook.each(callback => {
        callback();
        return true;
      });
    }
    this._unstoreLoginToken();
    this.connection.setUserId(null);
    this._reconnectStopper && this._reconnectStopper.stop();
  }

  makeClientLoggedIn(userId, token, tokenExpires) {
    this._storeLoginToken(userId, token, tokenExpires);
    this.connection.setUserId(userId);
  }

  ///
  /// LOGIN SERVICES
  ///

  // A reactive function returning whether the loginServiceConfiguration
  // subscription is ready. Used by accounts-ui to hide the login button
  // until we have all the configuration loaded
  //
  loginServicesConfigured() {
    return this._loginServicesHandle.ready();
  };

  // Some login services such as the redirect login flow or the resume
  // login handler can log the user in at page load time.  The
  // Meteor.loginWithX functions have a callback argument, but the
  // callback function instance won't be in memory any longer if the
  // page was reloaded.  The `onPageLoadLogin` function allows a
  // callback to be registered for the case where the login was
  // initiated in a previous VM, and we now have the result of the login
  // attempt in a new VM.

  // Register a callback to be called if we have information about a
  // login attempt at page load time.  Call the callback immediately if
  // we already have the page load login attempt info, otherwise stash
  // the callback to be called if and when we do get the attempt info.
  //
  onPageLoadLogin(f) {
    if (this._pageLoadLoginAttemptInfo) {
      f(this._pageLoadLoginAttemptInfo);
    } else {
      this._pageLoadLoginCallbacks.push(f);
    }
  };

  // Receive the information about the login attempt at page load time.
  // Call registered callbacks, and also record the info in case
  // someone's callback hasn't been registered yet.
  //
  _pageLoadLogin(attemptInfo) {
    if (this._pageLoadLoginAttemptInfo) {
      Meteor._debug(
        'Ignoring unexpected duplicate page load login attempt info'
      );
      return;
    }

    this._pageLoadLoginCallbacks.forEach(callback => callback(attemptInfo));
    this._pageLoadLoginCallbacks = [];
    this._pageLoadLoginAttemptInfo = attemptInfo;
  }

  // _startupCallback executes on onLogin callbacks
  // at registration time if already logged in
  // this can happen when new AccountsClient is created
  // before callbacks are registered see #10157
  _startupCallback(callback) {
    // Are we already logged in?
    if (this.connection._userId) {
      // If already logged in before handler is registered, it's safe to
      // assume type is a 'resume', so we execute the callback at the end
      // of the queue so that Meteor.startup can complete before any
      // embedded onLogin callbacks would execute.
      Meteor.setTimeout(() => callback({ type: 'resume' }), 0);
    }
  }

  ///
  /// LOGIN TOKENS
  ///

  // These methods deal with storing a login token and user id in the
  // browser's localStorage facility. It polls local storage every few
  // seconds to synchronize login state between multiple tabs in the same
  // browser.

  loginWithToken(token, callback) {
    this.callLoginMethod({
      methodArguments: [{
        resume: token
      }],
      userCallback: callback
    });
  };

  // Semi-internal API. Call this function to re-enable auto login after
  // if it was disabled at startup.
  _enableAutoLogin() {
    this._autoLoginEnabled = true;
    this._pollStoredLoginToken();
  };

  ///
  /// STORING
  ///

  // Call this from the top level of the test file for any test that does
  // logging in and out, to protect multiple tabs running the same tests
  // simultaneously from interfering with each others' localStorage.
  _isolateLoginTokenForTest() {
    this.LOGIN_TOKEN_KEY = this.LOGIN_TOKEN_KEY + Random.id();
    this.USER_ID_KEY = this.USER_ID_KEY + Random.id();
  };

  _storeLoginToken(userId, token, tokenExpires) {
    Meteor._localStorage.setItem(this.USER_ID_KEY, userId);
    Meteor._localStorage.setItem(this.LOGIN_TOKEN_KEY, token);
    if (! tokenExpires)
      tokenExpires = this._tokenExpiration(new Date());
    Meteor._localStorage.setItem(this.LOGIN_TOKEN_EXPIRES_KEY, tokenExpires);

    // to ensure that the localstorage poller doesn't end up trying to
    // connect a second time
    this._lastLoginTokenWhenPolled = token;
  };

  _unstoreLoginToken() {
    Meteor._localStorage.removeItem(this.USER_ID_KEY);
    Meteor._localStorage.removeItem(this.LOGIN_TOKEN_KEY);
    Meteor._localStorage.removeItem(this.LOGIN_TOKEN_EXPIRES_KEY);

    // to ensure that the localstorage poller doesn't end up trying to
    // connect a second time
    this._lastLoginTokenWhenPolled = null;
  };

  // This is private, but it is exported for now because it is used by a
  // test in accounts-password.
  _storedLoginToken() {
    return Meteor._localStorage.getItem(this.LOGIN_TOKEN_KEY);
  };

  _storedLoginTokenExpires() {
    return Meteor._localStorage.getItem(this.LOGIN_TOKEN_EXPIRES_KEY);
  };

  _storedUserId() {
    return Meteor._localStorage.getItem(this.USER_ID_KEY);
  };

  _unstoreLoginTokenIfExpiresSoon() {
    const tokenExpires = this._storedLoginTokenExpires();
    if (tokenExpires && this._tokenExpiresSoon(new Date(tokenExpires))) {
      this._unstoreLoginToken();
    }
  };

  ///
  /// AUTO-LOGIN
  ///

  _initLocalStorage() {
    // Key names to use in localStorage
    this.LOGIN_TOKEN_KEY = "Meteor.loginToken";
    this.LOGIN_TOKEN_EXPIRES_KEY = "Meteor.loginTokenExpires";
    this.USER_ID_KEY = "Meteor.userId";

    const rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
    if (rootUrlPathPrefix || this.connection !== Meteor.connection) {
      // We want to keep using the same keys for existing apps that do not
      // set a custom ROOT_URL_PATH_PREFIX, so that most users will not have
      // to log in again after an app updates to a version of Meteor that
      // contains this code, but it's generally preferable to namespace the
      // keys so that connections from distinct apps to distinct DDP URLs
      // will be distinct in Meteor._localStorage.
      let namespace = `:${this.connection._stream.rawUrl}`;
      if (rootUrlPathPrefix) {
        namespace += `:${rootUrlPathPrefix}`;
      }
      this.LOGIN_TOKEN_KEY += namespace;
      this.LOGIN_TOKEN_EXPIRES_KEY += namespace;
      this.USER_ID_KEY += namespace;
    }

    let token;
    if (this._autoLoginEnabled) {
      // Immediately try to log in via local storage, so that any DDP
      // messages are sent after we have established our user account
      this._unstoreLoginTokenIfExpiresSoon();
      token = this._storedLoginToken();
      if (token) {
        // On startup, optimistically present us as logged in while the
        // request is in flight. This reduces page flicker on startup.
        const userId = this._storedUserId();
        userId && this.connection.setUserId(userId);
        this.loginWithToken(token, err => {
          if (err) {
            Meteor._debug(`Error logging in with token: ${err}`);
            this.makeClientLoggedOut();
          }

          this._pageLoadLogin({
            type: "resume",
            allowed: !err,
            error: err,
            methodName: "login",
            // XXX This is duplicate code with loginWithToken, but
            // loginWithToken can also be called at other times besides
            // page load.
            methodArguments: [{resume: token}]
          });
        });
      }
    }

    // Poll local storage every 3 seconds to login if someone logged in in
    // another tab
    this._lastLoginTokenWhenPolled = token;

    if (this._pollIntervalTimer) {
      // Unlikely that _initLocalStorage will be called more than once for
      // the same AccountsClient instance, but just in case...
      clearInterval(this._pollIntervalTimer);
    }

    this._pollIntervalTimer = setInterval(() => {
      this._pollStoredLoginToken();
    }, 3000);
  };

  _pollStoredLoginToken() {
    if (! this._autoLoginEnabled) {
      return;
    }

    const currentLoginToken = this._storedLoginToken();

    // != instead of !== just to make sure undefined and null are treated the same
    if (this._lastLoginTokenWhenPolled != currentLoginToken) {
      if (currentLoginToken) {
        this.loginWithToken(currentLoginToken, (err) => {
          if (err) {
            this.makeClientLoggedOut();
          }
        });
      } else {
        this.logout();
      }
    }

    this._lastLoginTokenWhenPolled = currentLoginToken;
  };

  ///
  /// URLS
  ///

  _initUrlMatching() {
    // By default, allow the autologin process to happen.
    this._autoLoginEnabled = true;

    // We only support one callback per URL.
    this._accountsCallbacks = {};

    // Try to match the saved value of window.location.hash.
    this._attemptToMatchHash();
  };

  // Separate out this functionality for testing
  _attemptToMatchHash() {
    attemptToMatchHash(this, this.savedHash, defaultSuccessHandler);
  };

  /**
   * @summary Register a function to call when a reset password link is clicked
   * in an email sent by
   * [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail).
   * This function should be called in top-level code, not inside
   * `Meteor.startup()`.
   * @memberof! Accounts
   * @name onResetPasswordLink
   * @param  {Function} callback The function to call. It is given two arguments:
   *
   * 1. `token`: A password reset token that can be passed to
   * [`Accounts.resetPassword`](#accounts_resetpassword).
   * 2. `done`: A function to call when the password reset UI flow is complete. The normal
   * login process is suspended until this function is called, so that the
   * password for user A can be reset even if user B was logged in.
   * @locus Client
   */
  onResetPasswordLink(callback) {
    if (this._accountsCallbacks["reset-password"]) {
      Meteor._debug("Accounts.onResetPasswordLink was called more than once. " +
        "Only one callback added will be executed.");
    }

    this._accountsCallbacks["reset-password"] = callback;
  };

  /**
   * @summary Register a function to call when an email verification link is
   * clicked in an email sent by
   * [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail).
   * This function should be called in top-level code, not inside
   * `Meteor.startup()`.
   * @memberof! Accounts
   * @name onEmailVerificationLink
   * @param  {Function} callback The function to call. It is given two arguments:
   *
   * 1. `token`: An email verification token that can be passed to
   * [`Accounts.verifyEmail`](#accounts_verifyemail).
   * 2. `done`: A function to call when the email verification UI flow is complete.
   * The normal login process is suspended until this function is called, so
   * that the user can be notified that they are verifying their email before
   * being logged in.
   * @locus Client
   */
  onEmailVerificationLink(callback) {
    if (this._accountsCallbacks["verify-email"]) {
      Meteor._debug("Accounts.onEmailVerificationLink was called more than once. " +
        "Only one callback added will be executed.");
    }

    this._accountsCallbacks["verify-email"] = callback;
  };

  /**
   * @summary Register a function to call when an account enrollment link is
   * clicked in an email sent by
   * [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail).
   * This function should be called in top-level code, not inside
   * `Meteor.startup()`.
   * @memberof! Accounts
   * @name onEnrollmentLink
   * @param  {Function} callback The function to call. It is given two arguments:
   *
   * 1. `token`: A password reset token that can be passed to
   * [`Accounts.resetPassword`](#accounts_resetpassword) to give the newly
   * enrolled account a password.
   * 2. `done`: A function to call when the enrollment UI flow is complete.
   * The normal login process is suspended until this function is called, so that
   * user A can be enrolled even if user B was logged in.
   * @locus Client
   */
  onEnrollmentLink(callback) {
    if (this._accountsCallbacks["enroll-account"]) {
      Meteor._debug("Accounts.onEnrollmentLink was called more than once. " +
        "Only one callback added will be executed.");
    }

    this._accountsCallbacks["enroll-account"] = callback;
  };

};

/**
 * @summary True if a login method (such as `Meteor.loginWithPassword`,
 * `Meteor.loginWithFacebook`, or `Accounts.createUser`) is currently in
 * progress. A reactive data source.
 * @locus Client
 * @importFromPackage meteor
 */
Meteor.loggingIn = () => Accounts.loggingIn();

/**
 * @summary True if a logout method (such as `Meteor.logout`) is currently in
 * progress. A reactive data source.
 * @locus Client
 * @importFromPackage meteor
 */
Meteor.loggingOut = () => Accounts.loggingOut();

/**
 * @summary Log the user out.
 * @locus Client
 * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 * @importFromPackage meteor
 */
Meteor.logout = callback => Accounts.logout(callback);

/**
 * @summary Log out other clients logged in as the current user, but does not log out the client that calls this function.
 * @locus Client
 * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 * @importFromPackage meteor
 */
Meteor.logoutOtherClients = callback => Accounts.logoutOtherClients(callback);

/**
 * @summary Login with a Meteor access token.
 * @locus Client
 * @param {Object} [token] Local storage token for use with login across
 * multiple tabs in the same browser.
 * @param {Function} [callback] Optional callback. Called with no arguments on
 * success.
 * @importFromPackage meteor
 */
Meteor.loginWithToken = (token, callback) =>
  Accounts.loginWithToken(token, callback);

///
/// HANDLEBARS HELPERS
///

// If our app has a Blaze, register the {{currentUser}} and {{loggingIn}}
// global helpers.
if (Package.blaze) {
  const { Template } = Package.blaze.Blaze;

  /**
   * @global
   * @name  currentUser
   * @isHelper true
   * @summary Calls [Meteor.user()](#meteor_user). Use `{{#if currentUser}}` to check whether the user is logged in.
   */
  Template.registerHelper('currentUser', () => Meteor.user());

  /**
   * @global
   * @name  loggingIn
   * @isHelper true
   * @summary Calls [Meteor.loggingIn()](#meteor_loggingin).
   */
  Template.registerHelper('loggingIn', () => Meteor.loggingIn());

  /**
   * @global
   * @name  loggingOut
   * @isHelper true
   * @summary Calls [Meteor.loggingOut()](#meteor_loggingout).
   */
  Template.registerHelper('loggingOut', () => Meteor.loggingOut());

  /**
   * @global
   * @name  loggingInOrOut
   * @isHelper true
   * @summary Calls [Meteor.loggingIn()](#meteor_loggingin) or [Meteor.loggingOut()](#meteor_loggingout).
   */
  Template.registerHelper(
    'loggingInOrOut',
    () => Meteor.loggingIn() || Meteor.loggingOut()
  );
}

const defaultSuccessHandler = function(token, urlPart) {
  // put login in a suspended state to wait for the interaction to finish
  this._autoLoginEnabled = false;

  // wait for other packages to register callbacks
  Meteor.startup(() => {
    // if a callback has been registered for this kind of token, call it
    if (this._accountsCallbacks[urlPart]) {
      this._accountsCallbacks[urlPart](token, () => this._enableAutoLogin());
    }
  });
}

// Note that both arguments are optional and are currently only passed by
// accounts_url_tests.js.
const attemptToMatchHash = (accounts, hash, success) => {
  // All of the special hash URLs we support for accounts interactions
  ["reset-password", "verify-email", "enroll-account"].forEach(urlPart => {
    let token;

    const tokenRegex = new RegExp(`^\\#\\/${urlPart}\\/(.*)$`);
    const match = hash.match(tokenRegex);

    if (match) {
      token = match[1];

      // XXX COMPAT WITH 0.9.3
      if (urlPart === "reset-password") {
        accounts._resetPasswordToken = token;
      } else if (urlPart === "verify-email") {
        accounts._verifyEmailToken = token;
      } else if (urlPart === "enroll-account") {
        accounts._enrollAccountToken = token;
      }
    } else {
      return;
    }

    // If no handlers match the hash, then maybe it's meant to be consumed
    // by some entirely different code, so we only clear it the first time
    // a handler successfully matches. Note that later handlers reuse the
    // savedHash, so clearing window.location.hash here will not interfere
    // with their needs.
    window.location.hash = "";

    // Do some stuff with the token we matched
    success.call(accounts, token, urlPart);
  });
}

// Export for testing
export const AccountsTest = {
  attemptToMatchHash: (hash, success) =>
    attemptToMatchHash(Accounts, hash, success),
};


var _ = require('underscore');
var semver = require('semver');
var os = require('os');
var url = require('url');

var archinfo = require('./archinfo');
var buildmessage = require('./buildmessage.js');
var files = require('../fs/files');
var packageVersionParser = require('../packaging/package-version-parser.js');

var utils = exports;

// Parses <protocol>://<host>:<port> into an object { protocol: *, host:
// *, port: * }. The input can also be of the form <host>:<port> or just
// <port>. We're not simply using 'url.parse' because we want '3000' to
// parse as {host: undefined, protocol: undefined, port: '3000'}, whereas
// 'url.parse' would give us {protocol:' 3000', host: undefined, port:
// undefined} or something like that.
//
// 'defaults' is an optional object with 'hostname', 'port', and 'protocol' keys.
exports.parseUrl = function (str, defaults) {
  // XXX factor this out into a {type: host/port}?

  defaults = defaults || {};
  var defaultHostname = defaults.hostname || undefined;
  var defaultPort = defaults.port || undefined;
  var defaultProtocol = defaults.protocol || undefined;

  if (str.match(/^[0-9]+$/)) { // just a port
    return {
      port: str,
      hostname: defaultHostname,
      protocol: defaultProtocol };
  }

  var hasScheme = exports.hasScheme(str);
  if (! hasScheme) {
    str = "http://" + str;
  }

  var parsed = url.parse(str);

  // for consistency remove colon at the end of protocol
  parsed.protocol = parsed.protocol.replace(/\:$/, '');

  var ret = {
    protocol: hasScheme ? parsed.protocol : defaultProtocol,
    hostname: parsed.hostname || defaultHostname,
    port: parsed.port || defaultPort
  };
  if (parsed.pathname !== '/' && parsed.pathname) {
    ret.pathname = parsed.pathname;
  }
  return ret;
};

// 'options' is an object with 'hostname', 'port', and 'protocol' keys, such as
// the return value of parseUrl.
exports.formatUrl = function (options) {
  // For consistency with `Meteor.absoluteUrl`, add a trailing slash to make
  // this a valid URL
  if (!options.pathname)
    options.pathname = "/";

  return url.format(options);
};

exports.ipAddress = function () {
  const interfaces = os.networkInterfaces();

  // If we don't know the default route, we'll lookup all non-internal
  // IPv4 addresses and hope to find only one
  let addressEntries = _.chain(interfaces)
    .values()
    .flatten()
    .where({ family: "IPv4", internal: false })
    .value();

  if (! addressEntries.length) {
    throw new Error(`Could not find a network interface with a non-internal IPv4 address.`);
  }

  if (addressEntries.length > 1) {
    throw new Error(`Found multiple network interfaces with non-internal IPv4 addresses:
${addressEntries.map(entry => entry.address).join(', ')}`);
  }

  return addressEntries[0].address;
};

exports.hasScheme = function (str) {
  return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
};

exports.hasScheme = function (str) {
  return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
};

exports.isIPv4Address = function (str) {
  return str.match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/);
}

// XXX: Move to e.g. formatters.js?
// Prints a package list in a nice format.
// Input is an array of objects with keys 'name' and 'description'.
exports.printPackageList = function (items, options) {
  options = options || {};

  var rows = _.map(items, function (item) {
    var name = item.name;
    var description = item.description || 'No description';
    return [name, description];
  });

  var alphaSort = function (row) {
    return row[0];
  };
  rows = _.sortBy(rows, alphaSort);

  var Console = require('../console/console.js').Console;
  return Console.printTwoColumns(rows, options);
};

// Determine a human-readable hostname for this computer. Prefer names
// that make sense to users (eg, the name they manually gave their
// computer on OS X, which might contain spaces) over names that have
// any particular technical significance (eg, might resolve in DNS).
exports.getHost = function (...args) {
  var ret;
  var attempt = function (...args) {
    var output = exports.execFileSync(args[0], args.slice(1)).stdout;
    if (output) {
      ret = output.trim();
    }
  };

  if (archinfo.matches(archinfo.host(), 'os.osx')) {
    // On OSX, to get the human-readable hostname that the user chose,
    // we call:
    //   scutil --get ComputerName
    // This can contain spaces. See
    // http://osxdaily.com/2012/10/24/set-the-hostname-computer-name-and-bonjour-name-separately-in-os-x/
    if (! ret) {
      attempt("scutil", "--get", "ComputerName");
    }
  }

  if (archinfo.matches(archinfo.host(), 'os.osx') ||
      archinfo.matches(archinfo.host(), 'os.linux')) {
    // On Unix-like platforms, try passing -s to hostname to strip off
    // the domain name, to reduce the extent to which the output
    // varies with DNS.
    if (! ret) {
      attempt("hostname", "-s");
    }
  }

  // Try "hostname" on any platform. It should work on
  // Windows. Unknown platforms that have a command called "hostname"
  // that deletes all of your files deserve what the get.
  if (! ret) {
    attempt("hostname");
  }

  // Otherwise, see what Node can come up with.
  return ret || os.hostname();
};

// Return standard info about this user-agent. Used when logging in to
// Meteor Accounts, mostly so that when the user is seeing a list of
// their open sessions in their profile on the web, they have a way to
// decide which ones they want to revoke.
exports.getAgentInfo = function () {
  var ret = {};

  var host = utils.getHost();
  if (host) {
    ret.host = host;
  }
  ret.agent = "Meteor";
  ret.agentVersion =
    files.inCheckout() ? "checkout" : files.getToolsVersion();
  ret.arch = archinfo.host();

  return ret;
};

// Wait for 'ms' milliseconds, and then return. Yields. (Must be
// called within a fiber, and blocks only the calling fiber, not the
// whole program.)
exports.sleepMs = function (ms) {
  if (ms <= 0) {
    return;
  }

  new Promise(function (resolve) {
    setTimeout(resolve, ms);
  }).await();
};

// Return a short, high entropy string without too many funny
// characters in it.
exports.randomToken = function () {
  return (Math.random() * 0x100000000 + 1).toString(36);
};

// Like utils.randomToken, except a legal variable name, i.e. the first
// character is guaranteed to be [a-z] and the rest [a-z0-9].
exports.randomIdentifier = function () {
  const firstLetter = String.fromCharCode(
    "a".charCodeAt(0) + Math.floor(Math.random() * 26));
  return firstLetter + Math.random().toString(36).slice(2);
};

// Returns a random non-privileged port number.
exports.randomPort = function () {
  return 20000 + Math.floor(Math.random() * 10000);
};

// Like packageVersionParser.parsePackageConstraint, but if called in a
// buildmessage context uses buildmessage to raise errors.
exports.parsePackageConstraint = function (constraintString, options) {
  try {
    return packageVersionParser.parsePackageConstraint(constraintString);
  } catch (e) {
    if (! (e.versionParserError && options && options.useBuildmessage)) {
      throw e;
    }
    buildmessage.error(e.message, { file: options.buildmessageFile });
    return null;
  }
};

exports.validatePackageName = function (name, options) {
  try {
    return packageVersionParser.validatePackageName(name, options);
  } catch (e) {
    if (! (e.versionParserError && options && options.useBuildmessage)) {
      throw e;
    }
    buildmessage.error(e.message, { file: options.buildmessageFile });
    return null;
  }
};

// Parse a string of the form `package + " " + version` into an object
// of the form {package, version}.  For backwards compatibility,
// an "@" separator instead of a space is also accepted.
//
// Lines of `.meteor/versions` are parsed using this function, among
// other uses.
exports.parsePackageAndVersion = function (packageAtVersionString, options) {
  var error = null;
  var separatorPos = Math.max(packageAtVersionString.lastIndexOf(' '),
                              packageAtVersionString.lastIndexOf('@'));
  if (separatorPos < 0) {
    error = new Error("Malformed package version: " +
                      JSON.stringify(packageAtVersionString));
  } else {
    var packageName = packageAtVersionString.slice(0, separatorPos);
    var version = packageAtVersionString.slice(separatorPos+1);
    try {
      packageVersionParser.validatePackageName(packageName);
      // validate the version, ignoring the parsed result:
      packageVersionParser.parse(version);
    } catch (e) {
      if (! e.versionParserError) {
        throw e;
      }
      error = e;
    }
    if (! error) {
      return { package: packageName, version: version };
    }
  }
  // `error` holds an Error
  if (! (options && options.useBuildmessage)) {
    throw error;
  }
  buildmessage.error(error.message, { file: options.buildmessageFile });
  return null;
};

// Check for invalid package names. Currently package names can only contain
// ASCII alphanumerics, dash, and dot, and must contain at least one letter. For
// safety reasons, package names may not start with a dot. Package names must be
// lowercase.
//
// These do not check that the package name is valid in terms of our naming
// scheme: ie, that it is prepended by a user's username. That check should
// happen at publication time.
//
// 3 variants: isValidPackageName just returns a bool.  validatePackageName
// throws an error marked with 'versionParserError'. validatePackageNameOrExit
// (which should only be used inside the implementation of a command, not
// eg package-client.js) prints and throws the "exit with code 1" exception
// on failure.

exports.isValidPackageName = function (packageName) {
  try {
    exports.validatePackageName(packageName);
    return true;
  } catch (e) {
    if (!e.versionParserError) {
      throw e;
    }
    return false;
  }
};

exports.validatePackageNameOrExit = function (packageName, options) {
  try {
    exports.validatePackageName(packageName, options);
  } catch (e) {
    if (!e.versionParserError) {
      throw e;
    }
    var Console = require('../console/console.js').Console;
    Console.error(e.message, Console.options({ bulletPoint: "Error: " }));
    // lazy-load main: old bundler tests fail if you add a circular require to
    // this file
    var main = require('../tests/apps/app-using-stylus/main.js');
    throw new main.ExitWithCode(1);
  }
};

// True if this looks like a valid email address. We deliberately
// don't support
// - quoted usernames (eg, "foo"@bar.com, " "@bar.com, "@"@bar.com)
// - IP addresses in domains (eg, foo@1.2.3.4 or the IPv6 equivalent)
// because they're weird and we don't want them in our database.
exports.validEmail = function (address) {
  return /^[^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/.test(address);
};

// Like Perl's quotemeta: quotes all regexp metacharacters. See
//   https://github.com/substack/quotemeta/blob/master/index.js
exports.quotemeta = function (str) {
    return String(str).replace(/(\W)/g, '\\$1');
};

// Allow a simple way to scale up all timeouts from the command line
var timeoutScaleFactor = 1.0;
if (process.env.TIMEOUT_SCALE_FACTOR) {
  timeoutScaleFactor = parseFloat(process.env.TIMEOUT_SCALE_FACTOR);
}
exports.timeoutScaleFactor = timeoutScaleFactor;

// If the given version matches a template (essentially, semver-style, but with
// a bounded number of digits per number part, and with no restriction on the
// amount of number parts, and some restrictions on legal prerelease labels),
// then return an orderKey for it. Otherwise return null.
//
// This conventional orderKey pads each part (with 0s for numbers, and ! for
// prerelease tags), and appends a $. (Because ! sorts before $, this means that
// the prerelease for a given release will sort before it. Because $ sorts
// before '.', this means that 1.2 will sort before 1.2.3.)
exports.defaultOrderKeyForReleaseVersion = function (v) {
  var m = v.match(/^(\d{1,4}(?:\.\d{1,4})*)(?:-([-A-Za-z.]{1,15})(\d{0,4}))?$/);
  if (!m) {
    return null;
  }
  var numberPart = m[1];
  var prereleaseTag = m[2];
  var prereleaseNumber = m[3];

  var hasRedundantLeadingZero = function (x) {
    return x.length > 1 && x[0] === '0';
  };
  var leftPad = function (chr, len, str) {
    if (str.length > len) {
      throw Error("too long to pad!");
    }
    var padding = new Array(len - str.length + 1).join(chr);
    return padding + str;
  };
  var rightPad = function (chr, len, str) {
    if (str.length > len) {
      throw Error("too long to pad!");
    }
    var padding = new Array(len - str.length + 1).join(chr);
    return str + padding;
  };

  // Versions must have no redundant leading zeroes, or else this encoding would
  // be ambiguous.
  var numbers = numberPart.split('.');
  if (_.any(numbers, hasRedundantLeadingZero)) {
    return null;
  }
  if (prereleaseNumber && hasRedundantLeadingZero(prereleaseNumber)) {
    return null;
  }

  // First, put together the non-prerelease part.
  var ret = _.map(numbers, _.partial(leftPad, '0', 4)).join('.');

  if (!prereleaseTag) {
    return ret + '$';
  }

  ret += '!' + rightPad('!', 15, prereleaseTag);
  if (prereleaseNumber) {
    ret += leftPad('0', 4, prereleaseNumber);
  }

  return ret + '$';
};

// XXX should be in files.js
exports.isDirectory = function (dir) {
  try {
    // use stat rather than lstat since symlink to dir is OK
    var stats = files.stat(dir);
  } catch (e) {
    return false;
  }
  return stats.isDirectory();
};

// Calls cb with each subset of the array "total", with non-decreasing size,
// until all subsets have been used or cb returns true. The array passed
// to cb may be safely mutated or retained by cb.
exports.generateSubsetsOfIncreasingSize = function (total, cb) {
  // We'll throw this if cb ever returns true, which is a simple way to pop us
  // out of our recursion.
  var Done = function () {};

  // Generates all subsets of size subsetSize which contain the indices already
  // in chosenIndices (and no indices that are "less than" any of them).
  var generateSubsetsOfFixedSize = function (goalSize, chosenIndices) {
    // If we've found a subset of the size we're looking for, output it.
    if (chosenIndices.length === goalSize) {
      // Change from indices into the actual elements. Note that 'elements' is
      // a newly allocated array which cb may mutate or retain.
      var elements = [];
      _.each(chosenIndices, function (index) {
        elements.push(total[index]);
      });
      if (cb(elements)) {
        throw new Done();  // unwind all the recursion
      }
      return;
    }

    // Otherwise try adding another index and call this recursively.  We're
    // trying to produce a sorted list of indices, so if there are already
    // indices, we start with the one after the biggest one we already have.
    var firstIndexToConsider = chosenIndices.length ?
          chosenIndices[chosenIndices.length - 1] + 1 : 0;
    for (var i = firstIndexToConsider; i < total.length; ++i) {
      var withThisChoice = _.clone(chosenIndices);
      withThisChoice.push(i);
      generateSubsetsOfFixedSize(goalSize, withThisChoice);
    }
  };

  try {
    for (var goalSize = 0; goalSize <= total.length; ++goalSize) {
      generateSubsetsOfFixedSize(goalSize, []);
    }
  } catch (e) {
    if (!(e instanceof Done)) {
      throw e;
    }
  }
};

exports.isUrlWithFileScheme = function (x) {
  return /^file:\/\/.+/.test(x);
};

exports.isUrlWithSha = function (x) {
  // Is a URL with a fixed SHA? We use this for Cordova -- although theoretically we could use
  // a URL like isNpmUrl(), there are a variety of problems with this,
  // see https://github.com/meteor/meteor/pull/5562
  return /^https?:\/\/.*[0-9a-f]{40}/.test(x);
}

exports.isNpmUrl = function (x) {
  // These are the various protocols that NPM supports, which we use to download NPM dependencies
  // See https://docs.npmjs.com/files/package.json#git-urls-as-dependencies
  return exports.isUrlWithSha(x) ||
    /^(git|git\+ssh|git\+http|git\+https|https|http)?:\/\//.test(x);
};

exports.isPathRelative = function (x) {
  return x.charAt(0) !== '/';
};

// If there is a version that isn't valid, throws an Error with a
// human-readable message that is suitable for showing to the user.
// dependencies may be falsey or empty.
//
// This is talking about NPM/Cordova versions specifically, not Meteor versions.
// It does not support the wrap number syntax.
exports.ensureOnlyValidVersions = function (dependencies, {forCordova}) {
  _.each(dependencies, function (version, name) {
    // We want a given version of a smart package (package.js +
    // .npm/npm-shrinkwrap.json) to pin down its dependencies precisely, so we
    // don't want anything too vague. For now, we support semvers and urls that
    // name a specific commit by SHA.
    if (! exports.isValidVersion(version, {forCordova})) {
      throw new Error(
        "Must declare valid version of dependency: " + name + '@' + version);
    }
  });
};
exports.isValidVersion = function (version, {forCordova}) {
  return semver.valid(version) || exports.isUrlWithFileScheme(version)
    || (forCordova ? exports.isUrlWithSha(version): exports.isNpmUrl(version));
};

exports.execFileSync = function (file, args, opts) {
  var child_process = require('child_process');
  var { eachline } = require('./eachline');

  opts = opts || {};
  if (! _.has(opts, 'maxBuffer')) {
    opts.maxBuffer = 1024 * 1024 * 10;
  }

  if (opts && opts.pipeOutput) {
    var p = child_process.spawn(file, args, opts);

    eachline(p.stdout, function (line) {
      process.stdout.write(line + '\n');
    });

    eachline(p.stderr, function (line) {
      process.stderr.write(line + '\n');
    });

    return {
      success: ! new Promise(function (resolve) {
        p.on('exit', resolve);
      }).await(),
      stdout: "",
      stderr: ""
    };
  }

  return new Promise(function (resolve) {
    child_process.execFile(file, args, opts, function (err, stdout, stderr) {
      resolve({
        success: ! err,
        stdout: stdout,
        stderr: stderr
      });
    });
  }).await();
};

exports.execFileAsync = function (file, args, opts) {
  opts = opts || {};
  var child_process = require('child_process');
  var { eachline } = require('./eachline');
  var p = child_process.spawn(file, args, opts);
  var mapper = opts.lineMapper || _.identity;

  function logOutput(line) {
    if (opts.verbose) {
      line = mapper(line);
      if (line) {
        console.log(line);
      }
    }
  }

  eachline(p.stdout, logOutput);
  eachline(p.stderr, logOutput);

  return p;
};

exports.runGitInCheckout = function (...args) {
  args.unshift(
    '--git-dir=' +
    files.convertToOSPath(files.pathJoin(files.getCurrentToolsDir(), '.git')));

  return exports.execFileSync('git', args).stdout;
};

exports.Throttled = function (options) {
  var self = this;

  options = _.extend({ interval: 150 }, options || {});
  self.interval = options.interval;
  var now = +(new Date);

  self.next = now;
};

_.extend(exports.Throttled.prototype, {
  isAllowed: function () {
    var self = this;
    var now = +(new Date);

    if (now < self.next) {
      return false;
    }

    self.next = now + self.interval;
    return true;
  }
});

// ThrottledYield just regulates the frequency of calling yield.
// It should behave similarly to calling yield on every iteration of a loop,
// except that it won't actually yield if there hasn't been a long enough time interval
//
// options:
//   interval: minimum interval of time between yield calls
//             (more frequent calls are simply dropped)
exports.ThrottledYield = function (options) {
  var self = this;

  self._throttle = new exports.Throttled(options);
};

_.extend(exports.ThrottledYield.prototype, {
  yield: function () {
    var self = this;
    if (self._throttle.isAllowed()) {
      // setImmediate allows signals and IO to be processed but doesn't
      // otherwise add time-based delays. It is better for yielding than
      // process.nextTick (which doesn't allow signals or IO to be processed) or
      // setTimeout 1 (which adds a minimum of 1 ms and often more in delays).
      // XXX Actually, setImmediate is so fast that we might not even need
      // to use the throttler at all?
      new Promise(setImmediate).await();
    }
  }
});

// Use this to convert dates into our preferred human-readable format.
//
// Takes in either null, a raw date string (ex: 2014-12-09T18:37:48.977Z) or a
// date object and returns a long-form human-readable date (ex: December 9th,
// 2014) or unknown for null.
exports.longformDate = function (date) {
  if (! date) {
    return "Unknown";
  }
  var moment = require('moment');
  var pubDate = moment(date).format('MMMM Do, YYYY');
  return pubDate;
};

// Length of the longest possible string that could come out of longformDate
// (September is the longest month name, so "September 24th, 2014" would be an
// example).
exports.maxDateLength = "September 24th, 2014".length;

// Returns a sha256 hash of a given string.
exports.sha256 = function (contents) {
  var crypto = require('crypto');
  var hash = crypto.createHash('sha256');
  hash.update(contents);
  return hash.digest('base64');
};

exports.sourceMapLength = function (sm) {
  if (! sm) {
    return 0;
  }
  // sum the length of sources and the mappings, the size of
  // metadata is ignored, but it is not a big deal
  return sm.mappings.length
       + (sm.sourcesContent || []).reduce((soFar, current) => {
         return soFar + (current ? current.length : 0);
       }, 0);
};

// Find and return the current OS architecture, in "uname -m" format.
//
// For Linux and macOS (Darwin) this means first getting the current
// architecture reported by Node using "os.arch()" (e.g. ia32, x64), then
// converting it to a "uname -m" matching architecture label (e.g. i686,
// x86_64).
//
// For Windows things are handled differently. Node's "os.arch()" will return
// "ia32" for both 32-bit and 64-bit versions of Windows (since we're using
// a 32-bit version of Node on Windows). Instead we'll look for the presence
// of the PROCESSOR_ARCHITEW6432 environment variable to determine if the
// Windows architecture is 64-bit, then convert to a "uname -m" matching
// architecture label (e.g. i386, x86_64).
export function architecture() {
  const supportedArchitectures = {
    Darwin: {
      x64: 'x86_64',
    },
    Linux: {
      ia32: 'i686',
      x64: 'x86_64',
    },
    Windows_NT: {
      ia32: process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')
              ? 'x86_64'
              : 'i386',
      x64: 'x86_64'
    }
  };

  const osType = os.type();
  const osArch = os.arch();

  if (!supportedArchitectures[osType]) {
    throw new Error(`Unsupported OS ${osType}`);
  }

  if (!supportedArchitectures[osType][osArch]) {
    throw new Error(`Unsupported architecture ${osArch}`);
  }

  return supportedArchitectures[osType][osArch];
};

let emacsDetected;
export function isEmacs() {
  // Checking `process.env` is expensive, so only check once.
  if (typeof emacsDetected === "boolean") {
    return emacsDetected;
  }

  // Prior to v22, Emacs only set EMACS. After v27, it only sets INSIDE_EMACS.
  emacsDetected = !! (process.env.EMACS === "t" || process.env.INSIDE_EMACS);
  return emacsDetected;
}


const hmac = crypto.createHmac('sha256', OAuth.openSecret(config.secret));

keep keep keep keep keep replace keep keep keep


// XXX should probably nudge people toward the CSS Flexible Box Model
// flexie, rather than this

Package.describe({
  name: "jquery-layout",
  summary: "Deprecated package for JS layout",
  version: "1.0.3"
});

Package.onUse(function (api) {
  api.versionsFrom("1.0");
  api.use('jquery');
  api.addFiles('jquery.layout.js', 'client');
});


import LocalCollection_ from './local_collection.js';
import Matcher from './matcher.js';
import Sorter from './sorter.js';

LocalCollection = LocalCollection_;
Minimongo = {
    LocalCollection: LocalCollection_,
    Matcher,
    Sorter
};