phoenix_live_view 1.1.29 → 1.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,7 +30,7 @@ export default class EntryUploader {
30
30
  this.uploadChannel
31
31
  .join()
32
32
  .receive("ok", (_data) => this.readNextChunk())
33
- .receive("error", (reason) => this.error(reason));
33
+ .receive("error", ({ reason }) => this.error(reason));
34
34
  }
35
35
 
36
36
  isDone() {
@@ -264,7 +264,7 @@ Hooks.InfiniteScroll = {
264
264
  updated() {
265
265
  // Check if the scroll container still exists
266
266
  // https://github.com/phoenixframework/phoenix_live_view/issues/4169.
267
- if (!this.scrollContainer.isConnected) {
267
+ if (this.scrollContainer && !this.scrollContainer.isConnected) {
268
268
  this.destroyed();
269
269
  this.mounted();
270
270
  }
@@ -1,5 +1,6 @@
1
1
  import JS from "./js";
2
2
  import LiveSocket from "./live_socket";
3
+ import { ensureSameOrigin } from "./utils";
3
4
 
4
5
  type Transition = string | string[];
5
6
 
@@ -346,6 +347,7 @@ export default (
346
347
  });
347
348
  },
348
349
  navigate(href, opts = {}) {
350
+ ensureSameOrigin(href, "navigate");
349
351
  const customEvent = new CustomEvent("phx:exec");
350
352
  liveSocket.historyRedirect(
351
353
  customEvent,
@@ -356,6 +358,7 @@ export default (
356
358
  );
357
359
  },
358
360
  patch(href, opts = {}) {
361
+ ensureSameOrigin(href, "patch");
359
362
  const customEvent = new CustomEvent("phx:exec");
360
363
  liveSocket.pushHistoryPatch(
361
364
  customEvent,
@@ -4,6 +4,30 @@ import EntryUploader from "./entry_uploader";
4
4
 
5
5
  export const logError = (msg, obj) => console.error && console.error(msg, obj);
6
6
 
7
+ // Live navigation can only stay within the current origin, as it joins the
8
+ // target over the existing socket. A full URL to a different origin (or a
9
+ // non-http(s) scheme, which resolves to an opaque "null" origin) is a
10
+ // programming error, so we fail loudly instead of attempting a broken join.
11
+ export const ensureSameOrigin = (
12
+ href,
13
+ kind,
14
+ ) => {
15
+ let url;
16
+ try {
17
+ url = new URL(href, window.location.href);
18
+ } catch {
19
+ throw new Error(
20
+ `expected ${kind} destination to be a valid URL, got: ${href}`,
21
+ );
22
+ }
23
+ if (url.origin !== window.location.origin) {
24
+ throw new Error(
25
+ `cannot ${kind} to "${href}" because its origin does not match the ` +
26
+ `current origin "${window.location.origin}". Use window.location directly for cross-origin navigation.`,
27
+ );
28
+ }
29
+ };
30
+
7
31
  export const isCid = (cid) => {
8
32
  const type = typeof cid;
9
33
  return type === "number" || (type === "string" && /^(0|[1-9]\d*)$/.test(cid));
@@ -73,38 +73,45 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
73
73
  js(): HookJSCommands;
74
74
 
75
75
  /**
76
- * Pushes an event to the server.
76
+ * Pushes an event to the server and invokes a callback with the server's reply.
77
77
  *
78
- * @param event - The event name.
79
- * @param [payload] - The payload to send to the server. Defaults to an empty object.
80
- * @param [onReply] - A callback to handle the server's reply.
78
+ * **Note:** this version silently ignores push errors.
79
+ * Use the {@link pushEvent | promise-returning version} to handle errors.
81
80
  *
82
- * When onReply is not provided, the method returns a Promise that
83
- * When onReply is provided, the method returns void.
81
+ * @param event - The event name.
82
+ * @param payload - The payload to send to the server. Defaults to an empty object.
83
+ * @param onReply - A callback to handle the server's reply.
84
84
  */
85
85
  pushEvent(event: string, payload: any, onReply: OnReply): void;
86
+ /**
87
+ * Pushes an event to the server and returns a Promise that resolves with the server's reply.
88
+ *
89
+ * The promise will be rejected in case of errors
90
+ * such as a disconnected state, timeout, or the server rejecting the event.
91
+ *
92
+ * @param event - The event name.
93
+ * @param [payload] - The payload to send to the server. Defaults to an empty object.
94
+ * @returns A promise that fulfills or rejects with the server's reply.
95
+ */
86
96
  pushEvent(event: string, payload?: any): Promise<any>;
87
97
 
88
98
  /**
89
- * Pushed a targeted event to the server.
99
+ * Pushes a targeted event to the server and invokes a callback with the server's reply.
90
100
  *
91
101
  * It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
92
102
  * where its value can be either a query selector, an actual DOM element, or a CID (component id)
93
103
  * returned by the `@myself` assign.
94
104
  *
95
- * If the query selector returns more than one element it will send the event to all of them,
96
- * even if all the elements are in the same LiveComponent or LiveView. Because of this,
97
- * if no callback is passed, a promise is returned that matches the return value of
98
- * [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
99
- * Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
105
+ * If the selector matches multiple elements, the event is sent to all of them,
106
+ * even if they belong to the same LiveComponent or LiveView.
107
+ *
108
+ * **Note:** this version silently ignores push errors.
109
+ * Use the {@link pushEventTo | promise-returning version} to handle errors.
100
110
  *
101
111
  * @param selectorOrTarget - The selector, element, or CID to target.
102
112
  * @param event - The event name.
103
- * @param [payload] - The payload to send to the server. Defaults to an empty object.
104
- * @param [onReply] - A callback to handle the server's reply.
105
- *
106
- * When onReply is not provided, the method returns a Promise.
107
- * When onReply is provided, the method returns void.
113
+ * @param payload - The payload to send to the server. Defaults to an empty object.
114
+ * @param onReply - A callback to handle the server's reply.
108
115
  */
109
116
  pushEventTo(
110
117
  selectorOrTarget: PhxTarget,
@@ -112,6 +119,27 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
112
119
  payload: object,
113
120
  onReply: OnReply,
114
121
  ): void;
122
+ /**
123
+ * Pushes a targeted event to the server and returns a Promise that resolves with the server's reply.
124
+ *
125
+ * It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
126
+ * where its value can be either a query selector, an actual DOM element, or a CID (component id)
127
+ * returned by the `@myself` assign.
128
+ *
129
+ * If the selector matches multiple elements, the event is sent to all of them,
130
+ * even if they belong to the same LiveComponent or LiveView.
131
+ * Because of this, it returns a single promise that matches the return value of
132
+ * [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
133
+ * Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
134
+ *
135
+ * The individual promises will be rejected in case of errors
136
+ * such as a disconnected state, timeout, or the server rejecting the event.
137
+ *
138
+ * @param selectorOrTarget - The selector, element, or CID to target.
139
+ * @param event - The event name.
140
+ * @param [payload] - The payload to send to the server. Defaults to an empty object.
141
+ * @returns A promise that resolves when the event has been handled by all targets.
142
+ */
115
143
  pushEventTo(
116
144
  selectorOrTarget: PhxTarget,
117
145
  event: string,
@@ -1,6 +1,7 @@
1
1
  export function detectDuplicateIds(): void;
2
2
  export function detectInvalidStreamInserts(inserts: any): void;
3
3
  export function logError(msg: any, obj: any): void;
4
+ export function ensureSameOrigin(href: any, kind: any): void;
4
5
  export function isCid(cid: any): boolean;
5
6
  export function debug(view: any, kind: any, msg: any, obj: any): void;
6
7
  export function closure(val: any): any;
@@ -59,39 +59,67 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
59
59
  */
60
60
  js(): HookJSCommands;
61
61
  /**
62
- * Pushes an event to the server.
62
+ * Pushes an event to the server and invokes a callback with the server's reply.
63
63
  *
64
- * @param event - The event name.
65
- * @param [payload] - The payload to send to the server. Defaults to an empty object.
66
- * @param [onReply] - A callback to handle the server's reply.
64
+ * **Note:** this version silently ignores push errors.
65
+ * Use the {@link pushEvent | promise-returning version} to handle errors.
67
66
  *
68
- * When onReply is not provided, the method returns a Promise that
69
- * When onReply is provided, the method returns void.
67
+ * @param event - The event name.
68
+ * @param payload - The payload to send to the server. Defaults to an empty object.
69
+ * @param onReply - A callback to handle the server's reply.
70
70
  */
71
71
  pushEvent(event: string, payload: any, onReply: OnReply): void;
72
+ /**
73
+ * Pushes an event to the server and returns a Promise that resolves with the server's reply.
74
+ *
75
+ * The promise will be rejected in case of errors
76
+ * such as a disconnected state, timeout, or the server rejecting the event.
77
+ *
78
+ * @param event - The event name.
79
+ * @param [payload] - The payload to send to the server. Defaults to an empty object.
80
+ * @returns A promise that fulfills or rejects with the server's reply.
81
+ */
72
82
  pushEvent(event: string, payload?: any): Promise<any>;
73
83
  /**
74
- * Pushed a targeted event to the server.
84
+ * Pushes a targeted event to the server and invokes a callback with the server's reply.
85
+ *
86
+ * It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
87
+ * where its value can be either a query selector, an actual DOM element, or a CID (component id)
88
+ * returned by the `@myself` assign.
89
+ *
90
+ * If the selector matches multiple elements, the event is sent to all of them,
91
+ * even if they belong to the same LiveComponent or LiveView.
92
+ *
93
+ * **Note:** this version silently ignores push errors.
94
+ * Use the {@link pushEventTo | promise-returning version} to handle errors.
95
+ *
96
+ * @param selectorOrTarget - The selector, element, or CID to target.
97
+ * @param event - The event name.
98
+ * @param payload - The payload to send to the server. Defaults to an empty object.
99
+ * @param onReply - A callback to handle the server's reply.
100
+ */
101
+ pushEventTo(selectorOrTarget: PhxTarget, event: string, payload: object, onReply: OnReply): void;
102
+ /**
103
+ * Pushes a targeted event to the server and returns a Promise that resolves with the server's reply.
75
104
  *
76
105
  * It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
77
106
  * where its value can be either a query selector, an actual DOM element, or a CID (component id)
78
107
  * returned by the `@myself` assign.
79
108
  *
80
- * If the query selector returns more than one element it will send the event to all of them,
81
- * even if all the elements are in the same LiveComponent or LiveView. Because of this,
82
- * if no callback is passed, a promise is returned that matches the return value of
109
+ * If the selector matches multiple elements, the event is sent to all of them,
110
+ * even if they belong to the same LiveComponent or LiveView.
111
+ * Because of this, it returns a single promise that matches the return value of
83
112
  * [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
84
113
  * Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
85
114
  *
115
+ * The individual promises will be rejected in case of errors
116
+ * such as a disconnected state, timeout, or the server rejecting the event.
117
+ *
86
118
  * @param selectorOrTarget - The selector, element, or CID to target.
87
119
  * @param event - The event name.
88
120
  * @param [payload] - The payload to send to the server. Defaults to an empty object.
89
- * @param [onReply] - A callback to handle the server's reply.
90
- *
91
- * When onReply is not provided, the method returns a Promise.
92
- * When onReply is provided, the method returns void.
121
+ * @returns A promise that resolves when the event has been handled by all targets.
93
122
  */
94
- pushEventTo(selectorOrTarget: PhxTarget, event: string, payload: object, onReply: OnReply): void;
95
123
  pushEventTo(selectorOrTarget: PhxTarget, event: string, payload?: object): Promise<PromiseSettledResult<{
96
124
  reply: any;
97
125
  ref: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "1.1.29",
3
+ "version": "1.1.31",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -172,7 +172,7 @@ var EntryUploader = class {
172
172
  }
173
173
  upload() {
174
174
  this.uploadChannel.onError((reason) => this.error(reason));
175
- this.uploadChannel.join().receive("ok", (_data) => this.readNextChunk()).receive("error", (reason) => this.error(reason));
175
+ this.uploadChannel.join().receive("ok", (_data) => this.readNextChunk()).receive("error", ({ reason }) => this.error(reason));
176
176
  }
177
177
  isDone() {
178
178
  return this.offset >= this.entry.file.size;
@@ -215,6 +215,21 @@ var EntryUploader = class {
215
215
 
216
216
  // js/phoenix_live_view/utils.js
217
217
  var logError = (msg, obj) => console.error && console.error(msg, obj);
218
+ var ensureSameOrigin = (href, kind) => {
219
+ let url;
220
+ try {
221
+ url = new URL(href, window.location.href);
222
+ } catch {
223
+ throw new Error(
224
+ `expected ${kind} destination to be a valid URL, got: ${href}`
225
+ );
226
+ }
227
+ if (url.origin !== window.location.origin) {
228
+ throw new Error(
229
+ `cannot ${kind} to "${href}" because its origin does not match the current origin "${window.location.origin}". Use window.location directly for cross-origin navigation.`
230
+ );
231
+ }
232
+ };
218
233
  var isCid = (cid) => {
219
234
  const type = typeof cid;
220
235
  return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
@@ -1459,7 +1474,7 @@ Hooks.InfiniteScroll = {
1459
1474
  }
1460
1475
  },
1461
1476
  updated() {
1462
- if (!this.scrollContainer.isConnected) {
1477
+ if (this.scrollContainer && !this.scrollContainer.isConnected) {
1463
1478
  this.destroyed();
1464
1479
  this.mounted();
1465
1480
  }
@@ -3882,6 +3897,7 @@ var js_commands_default = (liveSocket, eventType) => {
3882
3897
  });
3883
3898
  },
3884
3899
  navigate(href, opts = {}) {
3900
+ ensureSameOrigin(href, "navigate");
3885
3901
  const customEvent = new CustomEvent("phx:exec");
3886
3902
  liveSocket.historyRedirect(
3887
3903
  customEvent,
@@ -3892,6 +3908,7 @@ var js_commands_default = (liveSocket, eventType) => {
3892
3908
  );
3893
3909
  },
3894
3910
  patch(href, opts = {}) {
3911
+ ensureSameOrigin(href, "patch");
3895
3912
  const customEvent = new CustomEvent("phx:exec");
3896
3913
  liveSocket.pushHistoryPatch(
3897
3914
  customEvent,
@@ -5976,7 +5993,7 @@ var LiveSocket = class {
5976
5993
  }
5977
5994
  // public
5978
5995
  version() {
5979
- return "1.1.29";
5996
+ return "1.1.31";
5980
5997
  }
5981
5998
  isProfileEnabled() {
5982
5999
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";