|
| 1 | +# Alternative Approaches |
| 2 | + |
| 3 | +*This document maintains the list of alternatives that have been considered to achieve better beaconing.* |
| 4 | +*Note that only [PendingBeacon API](#pendingbeacon-api) has ever been implemented.* |
| 5 | + |
| 6 | +## fetch() with PendingRequest API |
| 7 | + |
| 8 | +See the [fetch() with PendingRequest API explainer](fetch-with-pending-request-api.md). |
| 9 | + |
| 10 | +## PendingBeacon API |
| 11 | + |
| 12 | +See the [PendingBeacon API explainer](pending-beacon-api.md). |
| 13 | + |
| 14 | +## DOM-Based API |
| 15 | + |
| 16 | +A DOM-based API was considered as an alternative. |
| 17 | +This API would consist of a new possible `beacon` value for the `rel` attribute |
| 18 | +on the link tag, which developers could use to indicate a beacon, |
| 19 | +and then use standard DOM manipulation calls to change the data, cancel the beacon, etc. |
| 20 | + |
| 21 | +The stateful JS API was preferred to avoid beacon concerns intruding into the DOM, |
| 22 | +and because a ‘DOM-based’ API would still require scripting in many cases anyway |
| 23 | +(populating beacon data as the user interacts with the page, for example). |
| 24 | + |
| 25 | +## BFCache-supported `unload`-like event |
| 26 | + |
| 27 | +Another alternative is to introduce (yet) another page lifecycle event, |
| 28 | +that would be essentially the `unload` event, but supported by the BFCache - |
| 29 | +that is, its presence would not disable the BFCache, and the browser would execute this callback even on eviction from the BFCache. |
| 30 | +This was rejected because it would require allowing pages frozen in the BFCache to execute a JavaScript callback, |
| 31 | +and it would not be possible to restrict what that callback does |
| 32 | +(so, a callback could do things other than sending a beacon, which is not safe). |
| 33 | +It also doesn’t allow for other niceties such as resilience against crashes or batching of beacons, |
| 34 | +and complicates the already sufficiently complicated page lifecycle. |
| 35 | + |
| 36 | +## Extending `fetch()` API |
| 37 | + |
| 38 | +> **NOTE:** Discussions in [#52] and [#50]. |
| 39 | +
|
| 40 | +Another alternative is to extend the [Fetch API] to support the [requirements](../README.md#requirements). |
| 41 | + |
| 42 | +The existing Fetch with `keepalive` option, combined with `visibilitychagne` listener, can approximate part of (1): |
| 43 | + |
| 44 | +```js |
| 45 | +document.addEventListener('visibilitychange', () => { |
| 46 | + if (document.visibilityState === 'hidden') { |
| 47 | + fetch('/send_beacon', {keepalive: true}); |
| 48 | + // response may be dropped. |
| 49 | + } |
| 50 | +}); |
| 51 | +``` |
| 52 | + |
| 53 | +or a new option `deferSend` may be introduced to cover the entire (1): |
| 54 | + |
| 55 | +```js |
| 56 | +// defer request sending on `hidden` or bfcahce eviction etc. |
| 57 | +fetch('/send_beacon', {deferSend: true}); |
| 58 | +// Promise may not resolve and response may be dropped. |
| 59 | +``` |
| 60 | + |
| 61 | +### Problem |
| 62 | + |
| 63 | +However, there are several problem with this approach: |
| 64 | + |
| 65 | +1. **The Fetch API shape is not designed for this (1) purpose.** Fundamentally, `window.fetch` returns a Promise with Response to resolve, which don't make sense for beaconing at page discard that doesn't expect to process response. |
| 66 | +2. **The (1) mechanism is too unrelated to be added to the Fetch API**. Even just with a new option, bundling it with a visibility event-specific behavior just seems wrong in terms of the API's scope. |
| 67 | +3. **The Fetch API does not support updating request URL or data.** This is simply not possible with its API shape. Users have to re-fetch if any update happens. |
| 68 | + |
| 69 | +The above problems suggest that a new API is neccessary for our purpose. |
| 70 | + |
| 71 | +## Extending `navigator.sendBeacon()` API |
| 72 | + |
| 73 | +> **NOTE:** Discussions in [WebKit's standard position](https://github.com/WebKit/standards-positions/issues/85#issuecomment-1418381239). |
| 74 | +
|
| 75 | +Another alternative is to extend the [`navigator.sendBeacon`] API: |
| 76 | + |
| 77 | +```ts |
| 78 | +navigator.sendBeacon(url): bool |
| 79 | +navigator.sendBeacon(url, data): bool |
| 80 | +``` |
| 81 | + |
| 82 | +To meet the [requirements](../README.md#requirements) and to make the new API backward compatible, we propose the following shape: |
| 83 | + |
| 84 | +```ts |
| 85 | +navigator.sendBeacon(url, data, fetchOptions): PendingBeacon |
| 86 | +``` |
| 87 | + |
| 88 | +An optional dictionary argument `fetchOptions` can be passed in, which changes the return value from `bool` to `PendingBeacon` proposed in the [above section](#pendingbeacon-api). Some details to note: |
| 89 | + |
| 90 | +1. The proposal would like to support both `POST` and `GET` requests. As the existing API only support `POST` beacons, passing in `fetchOptions` with `method: GET` should enable queuing `GET` beacons. |
| 91 | +2. `fetchOptions` can only be a subset of the [Fetch API]'s [`RequestInit`] object: |
| 92 | + 1. `method`: one of `GET` or `POST`. |
| 93 | + 2. `headers`: supports custom headers, which unblocks [#50]. |
| 94 | + 3. `body`: **not supported**. POST body should be in `data` argument. |
| 95 | + 4. `credentials`: enforcing `same-origin` to be consistent. |
| 96 | + 5. `cache`: not supported. |
| 97 | + 6. `redirect`: enforcing `follow`. |
| 98 | + 7. `referrer`: enforcing same-origin URL. |
| 99 | + 8. `referrerPolicy`: enforcing `same-origin`. |
| 100 | + 9. `keepalive`: enforcing `true`. |
| 101 | + 10. `integrity`: not supported. |
| 102 | + 11. `signal`: **not supported**. |
| 103 | + * The reason why `signal` and `AbortController` are not desired is that we needs more than just aborting the requests. It is essential to check a beacon's pending states and to update or accumulate data. Supporting these requirements via the returned `PendingBeacon` object allows more flexibility. |
| 104 | + 12. `priority`: enforcing `auto`. |
| 105 | +3. `data`: For `GET` beacon, it must be `null` or `undefined`. |
| 106 | +4. The return value must supports updating request URL or data, hence `PendingBeacon` object. |
| 107 | + |
| 108 | +### Problem |
| 109 | + |
| 110 | +* The above API itself is enough for the [requirements](../README.md#requirements) (2) and (3), but cannot achieve the requirement (1), delaying the request. |
| 111 | +* The function name `sendBeacon` semantic doesn't make sense for the "delaying" behavior. |
| 112 | +* Combing the subset of `fetchOptions` along with the existing `data` parameter are error-proning. |
| 113 | + |
| 114 | +## Introducing `navigator.queueBeacon()` API |
| 115 | + |
| 116 | +To imprvoe from "Extending `navigator.sendBeacon()` API, it's better with a new function: |
| 117 | + |
| 118 | +```ts |
| 119 | +navigator.queueBeacon(url, fetchOptions, beaconOptions): PendingBeacon |
| 120 | +``` |
| 121 | + |
| 122 | +This proposal gets rid of the `data` parameter, and request body should be put into `fetchOptions.body` directly. |
| 123 | + |
| 124 | +The extra `beaconOptions` is a dictionary taking `backgroundTimeout` and `timeout` to support the optional timeout after bfcache or hidden requirement. |
| 125 | + |
| 126 | +At the end, this proposal also requires an entirely new API, just under the existing `navigator` namespace. The advantage is that we might be able to merge this proposal into [w3c/beacon] and eliminate the burden to maintain a new spec. |
| 127 | + |
| 128 | + |
| 129 | +## Write-only API |
| 130 | + |
| 131 | +This is similar to the [proposed PendingBeacon API](#pendingbeacon-api) but there is no `pending` and no `setData()`. |
| 132 | +There are 2 classes of beacon with a base class that has |
| 133 | + |
| 134 | +* `url` |
| 135 | +* `method` |
| 136 | +* `sendNow()` |
| 137 | +* `deactivate()` |
| 138 | +* API for specifying timeouts |
| 139 | + |
| 140 | +With these APIs, the page cannot check whether the beacon has been sent already. |
| 141 | + |
| 142 | +It's unclear that these APIs can satisfy all use cases. |
| 143 | +If they can, they have the advantage of being easier to implement |
| 144 | +and simple to use. |
| 145 | + |
| 146 | +## High-Level APIs |
| 147 | + |
| 148 | +### AppendableBeacon |
| 149 | + |
| 150 | +Has `appendData(data)` which appends new data to the beacon's payload. |
| 151 | +The beacon will flush queued payload according to the timeouts and the browser state. |
| 152 | + |
| 153 | +The use-case is for continuously logging events that are accumulated on the server-side. |
| 154 | + |
| 155 | +### ReplaceableBeacon |
| 156 | + |
| 157 | +Has `replaceData(data)` which replaces the current the beacon's payload. |
| 158 | +The beacon will send the payload according to the timeouts and the browser state. |
| 159 | +If a payload has been sent already, replaceData simply stores a new payload to be sent in the future. |
| 160 | + |
| 161 | +The use case is for logging a total-so-far. |
| 162 | +The server would typically only pay attention to the latest value. |
| 163 | + |
| 164 | + |
| 165 | +[#50]: https://github.com/WICG/pending-beacon/issues/50 |
| 166 | +[#52]: https://github.com/WICG/pending-beacon/issues/52 |
| 167 | +[Fetch API]: https://fetch.spec.whatwg.org/#fetch-api |
| 168 | +[`RequestInit`]: https://fetch.spec.whatwg.org/#requestinit |
| 169 | +[w3c/beacon]: https://github.com/w3c/beacon |
0 commit comments