<commit_before>ComponentUrl = require('./component_url')
Snapshot = require('./snapshot')
DoublyLinkedList = require('./doubly_linked_list')
Utils = require('./utils')
EVENTS = require('./events')
PAGE_CACHE_SIZE = 20
Config = require('./config')
ProgressBar = require('./progress_bar')
Request = require('./request_response').request

class Controller
  constructor: (history)->
    @atomCache = {}
    @queues = {}
    @history = new Snapshot(this, history)
    @unlisten = history.listen(@history.onHistoryChange)

    @transitionCacheEnabled = false
    @requestCachingEnabled = true
    @progressBar = new ProgressBar
  reset: ->
    @transitionCacheEnabled = false
    @atomCache = {}
    @queues = {}
    @unlisten()
    @history.reset()

  setInitialUrl: (url) =>
    @history.setInitialUrl(url)

  setInitialState: (url, state) =>
    @setInitialUrl(url)
    @setCSRFToken(state.csrf_token)
    @replace(state)

  setCSRFToken: (token) =>
    @csrfToken = token

  currentPage: =>
    @history.currentPage

  fetchQueue:(name) =>
    @queues[name] ?= new (Config.fetchQueue(name))

  request: (url, options = {}) =>
    options = Utils.reverseMerge options,
      pushState: true
      queue: 'sync'

    queue = @queue = @fetchQueue(options.queue)
    url = new ComponentUrl url, Config.fetchBaseUrl()

    if url.crossOrigin()
      @onCrossOriginRequest(url)
      return

    @history.cacheCurrentPage()
    if options.queue == 'sync'
      @progressBar.start()
    restorePoint = @history.transitionCacheFor(url.absolute)

    if @transitionCacheEnabled and restorePoint and restorePoint.transition_cache
      @history.reflectNewUrl(url)
      @restore(restorePoint)

    options.cacheRequest ?= @requestCachingEnabled
    options.onRequestStart?(url.absolute)

    queue.push(@createRequest(url, options))

  createRequest: (url, options)=>
    jsAccept = 'text/javascript, application/x-javascript, application/javascript'
    csrfToken = CSRFToken.get().token

    req =
      controller: @
      url: url
      header:
        'accept': jsAccept
        'x-xhr-referer': @getRefererUrl()
        'x-requested-with': 'XMLHttpRequest'
      payload: options.payload
      method: options.requestMethod || 'GET'
      onRequestError: options.onRequestError
      onRequestEnd: options.onRequestEnd
      cacheRequest: options.cacheRequest
      pushState: options.pushState

    if options.silent?
      req.header['x-silent'] =  options.silent

    if options.contentType?
      req.header['content-type'] =  options.contentType

    if csrfToken?
      req.header['x-csrf-token'] = csrfToken

    new Request req

  enableTransitionCache: (enable = true) =>
    @transitionCacheEnabled = enable

  disableRequestCaching: (disable = true) =>
    @requestCachingEnabled = not disable
    disable

  restore: (cachedPage, options = {}) =>
    @progressBar.done()
    @history.changePage(cachedPage, options)

    Utils.emitter.emit EVENTS.RESTORE
    Utils.emitter.emit EVENTS.LOAD, cachedPage

  replace: (nextPage, options = {}) =>
    Utils.withDefaults(nextPage, @history.currentBrowserState)
    @history.changePage(nextPage, options)
    Utils.emitter.emit EVENTS.LOAD, @currentPage()

  cache: (key, value) =>
    return @atomCache[key] if value == null
    @atomCache[key] ||= value

  clearCache: (key, value) =>
    @atomCache = {}

  onProgress: (event)=>
    @progressBar.advanceFromEvent(event)

  onLoad: (rsp) =>
    #react-native might not need the following line.
    redirectedUrl = rsp.header['x-xhr-redirected-to']
    url = new ComponentUrl(redirectedUrl || rsp.url)

    rsp.onRequestEnd?(url.absolute)
    nextPage =  @processResponse(rsp)
    if rsp.status == 0 || rsp.status == 204
      return

    if nextPage
      if rsp.async && url.pathname != @currentPage().pathname # fix rsp.async

        unless rsp.ignoreSamePathConstraint
          @progressBar.done()
          Utils.warn("Async response path is different from current page path")
          return

      if rsp.pushState
        @history.reflectNewUrl url

      Utils.withDefaults(nextPage, @history.currentBrowserState)

      if nextPage.action != 'graft'
        @history.changePage(nextPage)
        Utils.emitter.emit EVENTS.LOAD, @currentPage()
      else
        ##clean this up
        @history.graftByKeypath("data.#{nextPage.path}", nextPage.data)

      @progressBar.done()
      @history.constrainPageCacheTo()
    else
      rsp.onRequestError(rsp) # unify this

  processResponse: (rsp) ->
    if @hasValidResponse(rsp)
      return @responseContent(rsp.body)

  hasValidResponse: (xhr) ->
    not @clientOrServerError(xhr) and @validContent(xhr) and not @downloadingFile(xhr)

  responseContent: (body) ->
    new Function("'use strict'; return " + body)()

  clientOrServerError: (xhr) ->
    400 <= xhr.status < 600

  validContent: (rsp) ->
    contentType = rsp.header['content-type']
    jsContent = /^(?:text\/javascript|application\/x-javascript|application\/javascript)(?:;|$)/

    contentType? and contentType.match jsContent

  downloadingFile: (xhr) ->
    (disposition = xhr.header['content-disposition'])? and
      disposition.match /^attachment/


module.exports = Controller
<commit_msg>Add default callbacks to request<commit_after>ComponentUrl = require('./component_url')
Snapshot = require('./snapshot')
DoublyLinkedList = require('./doubly_linked_list')
Utils = require('./utils')
EVENTS = require('./events')
PAGE_CACHE_SIZE = 20
Config = require('./config')
ProgressBar = require('./progress_bar')
Request = require('./request_response').request

class Controller
  constructor: (history)->
    @atomCache = {}
    @queues = {}
    @history = new Snapshot(this, history)
    @unlisten = history.listen(@history.onHistoryChange)

    @transitionCacheEnabled = false
    @requestCachingEnabled = true
    @progressBar = new ProgressBar
  reset: ->
    @transitionCacheEnabled = false
    @atomCache = {}
    @queues = {}
    @unlisten()
    @history.reset()

  setInitialUrl: (url) =>
    @history.setInitialUrl(url)

  setInitialState: (url, state) =>
    @setInitialUrl(url)
    @setCSRFToken(state.csrf_token)
    @replace(state)

  setCSRFToken: (token) =>
    @csrfToken = token

  currentPage: =>
    @history.currentPage

  fetchQueue:(name) =>
    @queues[name] ?= new (Config.fetchQueue(name))

  request: (url, options = {}) =>
    options = Utils.reverseMerge options,
      onProgress: -> {}
      onRequestStart: -> {}
      onRequestError: -> {}
      onRequestEnd: -> {}
      onProgress: -> {}
      pushState: true
      queue: 'sync'

    queue = @queue = @fetchQueue(options.queue)
    url = new ComponentUrl url, Config.fetchBaseUrl()

    if url.crossOrigin()
      @onCrossOriginRequest(url)
      return

    @history.cacheCurrentPage()
    if options.queue == 'sync'
      @progressBar.start()
    restorePoint = @history.transitionCacheFor(url.absolute)

    if @transitionCacheEnabled and restorePoint and restorePoint.transition_cache
      @history.reflectNewUrl(url)
      @restore(restorePoint)

    options.cacheRequest ?= @requestCachingEnabled
    options.onRequestStart?(url.absolute)

    queue.push(@createRequest(url, options))

  createRequest: (url, options)=>
    jsAccept = 'text/javascript, application/x-javascript, application/javascript'
    csrfToken = CSRFToken.get().token

    req =
      controller: @
      url: url
      header:
        'accept': jsAccept
        'x-xhr-referer': @getRefererUrl()
        'x-requested-with': 'XMLHttpRequest'
      payload: options.payload
      method: options.requestMethod || 'GET'
      onRequestError: options.onRequestError
      onRequestEnd: options.onRequestEnd
      cacheRequest: options.cacheRequest
      pushState: options.pushState

    if options.silent?
      req.header['x-silent'] =  options.silent

    if options.contentType?
      req.header['content-type'] =  options.contentType

    if csrfToken?
      req.header['x-csrf-token'] = csrfToken

    new Request req

  enableTransitionCache: (enable = true) =>
    @transitionCacheEnabled = enable

  disableRequestCaching: (disable = true) =>
    @requestCachingEnabled = not disable
    disable

  restore: (cachedPage, options = {}) =>
    @progressBar.done()
    @history.changePage(cachedPage, options)

    Utils.emitter.emit EVENTS.RESTORE
    Utils.emitter.emit EVENTS.LOAD, cachedPage

  replace: (nextPage, options = {}) =>
    Utils.withDefaults(nextPage, @history.currentBrowserState)
    @history.changePage(nextPage, options)
    Utils.emitter.emit EVENTS.LOAD, @currentPage()

  cache: (key, value) =>
    return @atomCache[key] if value == null
    @atomCache[key] ||= value

  clearCache: (key, value) =>
    @atomCache = {}

  onProgress: (event)=>
    @progressBar.advanceFromEvent(event)

  onLoad: (rsp) =>
    #react-native might not need the following line.
    redirectedUrl = rsp.header['x-xhr-redirected-to']
    url = new ComponentUrl(redirectedUrl || rsp.url)

    rsp.onRequestEnd?(url.absolute)
    nextPage =  @processResponse(rsp)
    if rsp.status == 0 || rsp.status == 204
      return

    if nextPage
      if rsp.async && url.pathname != @currentPage().pathname # fix rsp.async

        unless rsp.ignoreSamePathConstraint
          @progressBar.done()
          Utils.warn("Async response path is different from current page path")
          return

      if rsp.pushState
        @history.reflectNewUrl url

      Utils.withDefaults(nextPage, @history.currentBrowserState)

      if nextPage.action != 'graft'
        @history.changePage(nextPage)
        Utils.emitter.emit EVENTS.LOAD, @currentPage()
      else
        ##clean this up
        @history.graftByKeypath("data.#{nextPage.path}", nextPage.data)

      @progressBar.done()
      @history.constrainPageCacheTo()
    else
      rsp.onRequestError(rsp) # unify this

  processResponse: (rsp) ->
    if @hasValidResponse(rsp)
      return @responseContent(rsp.body)

  hasValidResponse: (xhr) ->
    not @clientOrServerError(xhr) and @validContent(xhr) and not @downloadingFile(xhr)

  responseContent: (body) ->
    new Function("'use strict'; return " + body)()

  clientOrServerError: (xhr) ->
    400 <= xhr.status < 600

  validContent: (rsp) ->
    contentType = rsp.header['content-type']
    jsContent = /^(?:text\/javascript|application\/x-javascript|application\/javascript)(?:;|$)/

    contentType? and contentType.match jsContent

  downloadingFile: (xhr) ->
    (disposition = xhr.header['content-disposition'])? and
      disposition.match /^attachment/


module.exports = Controller

{'fraction_non_alphanumeric': 0.1263326728936692, 'fraction_numerical': 0.0022643645626945937, 'mean_word_length': 3.3177189409368637, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6162275', 'n_tokens_mistral': 3298, 'n_tokens_neox': 3185, 'n_words': 647}