Skip to content

Commit

Permalink
[Deprecation]: Remove internal objects from public API
Browse files Browse the repository at this point in the history
The `FetchRequest`, `FetchResponse`, and `FormSubmission` objects are
internal abstractions that facilitate Turbo's management of HTTP and
Form Submission life cycles.

Currently, references to instances of those classes are available
through `event.detail` for the following events:

* `turbo:frame-render`
* `turbo:before-fetch-request`
* `turbo:before-fetch-response`
* `turbo:submit-start`
* `turbo:submit-end`

Similarly, the `turbo:before-fetch-request` exposes a `fetchOptions`
object that is a separate instance from the one used to submit the
request. This means that **any** modifications to **any** value made
from within an event listener are ineffective and **do not change** the
ensuing request.

This commit deprecates those properties in favor of their built-in
foundations, namely:

* [Request][]
* [Response][]

The properties that expose those instances will remain as deprecations,
but will be inaccessible after an `8.0` release.

[Request]: https://developer.mozilla.org/en-US/docs/Web/API/Request
[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
  • Loading branch information
seanpdoyle committed Sep 12, 2023
1 parent 4570b9e commit ed669b8
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 126 deletions.
54 changes: 48 additions & 6 deletions src/core/drive/form_submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class FormSubmission {
if (!fetchRequest.isSafe) {
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token")
if (token) {
fetchRequest.headers["X-CSRF-Token"] = token
fetchRequest.headers.set("X-CSRF-Token", token)
}
}

Expand All @@ -114,12 +114,23 @@ export class FormSubmission {
}

requestStarted(fetchRequest) {
const formSubmission = this

this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
this.setSubmitsWith()
dispatch("turbo:submit-start", {
target: this.formElement,
detail: { formSubmission: this }
detail: {
request: fetchRequest.request,
submitter: this.submitter,

get formSubmission() {
console.warn("`event.detail.formSubmission` is deprecated. Use `event.target`, `event.detail.submitter`, and `event.detail.request` instead")

return formSubmission
}
}
})
this.delegate.formSubmissionStarted(this)
}
Expand All @@ -136,13 +147,31 @@ export class FormSubmission {
this.delegate.formSubmissionErrored(this, error)
} else {
this.state = FormSubmissionState.receiving
this.result = { success: true, fetchResponse: fetchResponse }
this.result = {
success: true,
response: fetchResponse.response,

get fetchResponse() {
console.warn("`event.detail.fetchResponse` is deprecated. Use `event.detail.response` instead")

return fetchResponse
}
}
this.delegate.formSubmissionSucceededWithResponse(this, fetchResponse)
}
}

requestFailedWithResponse(fetchRequest, fetchResponse) {
this.result = { success: false, fetchResponse: fetchResponse }
this.result = {
success: false,
response: fetchResponse.response,

get fetchResponse() {
console.warn("`event.detail.fetchResponse` is deprecated. Use `event.detail.response` instead")

return fetchResponse
}
}
this.delegate.formSubmissionFailedWithResponse(this, fetchResponse)
}

Expand All @@ -151,13 +180,26 @@ export class FormSubmission {
this.delegate.formSubmissionErrored(this, error)
}

requestFinished(_fetchRequest) {
requestFinished(fetchRequest) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
this.resetSubmitterText()
const { formSubmission } = this

dispatch("turbo:submit-end", {
target: this.formElement,
detail: { formSubmission: this, ...this.result }
detail: {
request: fetchRequest.request,
submitter: this.submitter,

get formSubmission() {
console.warn("`event.detail.formSubmission` is deprecated. Use `event.target`, `event.detail.submitter`, and `event.detail.request` instead")

return formSubmission
},

...this.result
}
})
this.delegate.formSubmissionFinished(this)
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class FrameController {
// Fetch request delegate

prepareRequest(request) {
request.headers["Turbo-Frame"] = this.id
request.headers.set("Turbo-Frame", this.id)

if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
request.acceptResponseType(StreamMessage.contentType)
Expand Down
10 changes: 9 additions & 1 deletion src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,15 @@ export class Session {

notifyApplicationAfterFrameRender(fetchResponse, frame) {
return dispatch("turbo:frame-render", {
detail: { fetchResponse },
detail: {
response: fetchResponse.response,

get fetchResponse() {
console.warn("`event.detail.fetchResponse` is deprecated. Use `event.detail.response` instead")

return fetchResponse
}
},
target: frame,
cancelable: true
})
Expand Down
73 changes: 37 additions & 36 deletions src/http/fetch_request.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,58 +47,38 @@ export class FetchRequest {
#resolveRequestPromise = (_value) => {}

constructor(delegate, method, location, requestBody = new URLSearchParams(), target = null, enctype = FetchEnctype.urlEncoded) {
method = fetchMethodFromString(method)

const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype)

this.delegate = delegate
this.url = url
this.target = target
this.fetchOptions = {
this.request = new Request(url.href, {
credentials: "same-origin",
redirect: "follow",
method: method,
headers: { ...this.defaultHeaders },
body: body,
signal: this.abortSignal,
referrer: this.delegate.referrer?.href
}
})
this.enctype = enctype
}

get method() {
return this.fetchOptions.method
}

set method(value) {
const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData()
const fetchMethod = fetchMethodFromString(value) || FetchMethod.get

this.url.search = ""

const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype)

this.url = url
this.fetchOptions.body = body
this.fetchOptions.method = fetchMethod
return this.request.method
}

get headers() {
return this.fetchOptions.headers
}

set headers(value) {
this.fetchOptions.headers = value
return this.request.headers
}

get body() {
if (this.isSafe) {
return this.url.searchParams
} else {
return this.fetchOptions.body
}
return this.request.body
}

set body(value) {
this.fetchOptions.body = value
get url() {
return this.request.url
}

get location() {
Expand All @@ -123,7 +103,7 @@ export class FetchRequest {
await this.#allowRequestToBeIntercepted(fetchOptions)
try {
this.delegate.requestStarted(this)
const response = await fetch(this.url.href, fetchOptions)
const response = await fetch(this.request)
return await this.receive(response)
} catch (error) {
if (error.name !== "AbortError") {
Expand All @@ -138,10 +118,20 @@ export class FetchRequest {
}

async receive(response) {
const { request } = this
const fetchResponse = new FetchResponse(response)
const event = dispatch("turbo:before-fetch-response", {
cancelable: true,
detail: { fetchResponse },
detail: {
request,
response,

get fetchResponse() {
console.warn("`event.detail.fetchResponse` is deprecated. Use `event.detail.response` instead")

return fetchResponse
}
},
target: this.target
})
if (event.defaultPrevented) {
Expand Down Expand Up @@ -169,29 +159,40 @@ export class FetchRequest {
}

acceptResponseType(mimeType) {
this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", ")
this.headers.set("Accept", [mimeType, this.headers.get("Accept")].join(", "))
}

async #allowRequestToBeIntercepted(fetchOptions) {
const { url } = this.request
const requestInterception = new Promise((resolve) => (this.#resolveRequestPromise = resolve))
const event = dispatch("turbo:before-fetch-request", {
cancelable: true,
detail: {
fetchOptions,
url: this.url,
get fetchOptions() {
console.warn("`event.detail.fetchOptions` is deprecated. Use `event.detail.request` instead")

return fetchOptions
},

get url() {
console.warn("`event.detail.url` is deprecated. Use `event.detail.request.url` instead")

return url
},

request: this.request,
resume: this.#resolveRequestPromise
},
target: this.target
})
this.url = event.detail.url
if (event.defaultPrevented) await requestInterception
}

#willDelegateErrorHandling(error) {
const event = dispatch("turbo:fetch-request-error", {
target: this.target,
cancelable: true,
detail: { request: this, error: error }
detail: { request: this.request, error: error }
})

return !event.defaultPrevented
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/form.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html id="html" data-skip-event-details="turbo:submit-start turbo:submit-end turbo:fetch-request-error">
<html id="html">
<head>
<meta charset="utf-8">
<title>Form</title>
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/navigation.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html id="html" data-skip-event-details="turbo:submit-start turbo:submit-end">
<html id="html">
<head>
<meta charset="utf-8">
<meta name="csp-nonce" content="123">
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/stream.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-skip-event-details="turbo:submit-start turbo:submit-end">
<html>
<head>
<meta charset="utf-8">
<title>Turbo Streams</title>
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/tabs.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-skip-event-details="turbo:fetch-request-error">
<html>
<head>
<meta charset="utf-8">
<title>Tabs</title>
Expand Down
14 changes: 14 additions & 0 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
returned[key] = value.toJSON()
} else if (value instanceof Element) {
returned[key] = value.outerHTML
} else if (value instanceof Headers) {
returned[key] = Object.fromEntries(value.entries())
} else if (value instanceof Request) {
const { method, url, headers } = value

returned[key] = serializeToChannel({ method, url, headers })
} else if (value instanceof Response) {
const { url, status, headers } = value

returned[key] = serializeToChannel({ url, status, headers })
} else if (value instanceof AbortSignal) {
returned[key] = "cannot encode AbortSignal instance"
} else if (key === "formSubmission") {
returned[key] = "cannot encode FormSubmission instance"
} else if (typeof value == "object") {
if (visited.has(value)) {
returned[key] = "skipped to prevent infinitely recursing"
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/visit.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html id="html" data-skip-event-details="turbo:fetch-request-error">
<html id="html">
<head>
<meta charset="utf-8">
<title>Turbo</title>
Expand Down
Loading

0 comments on commit ed669b8

Please sign in to comment.