fix(modal): cancel stale purchase flights on scope change
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa865e24e1
commit
6a7e994f07
1 file changed
+69
-19
@@ -17,6 +17,7 @@
|
|||||||
var createFlights = Object.create(null);
|
var createFlights = Object.create(null);
|
||||||
var polls = Object.create(null);
|
var polls = Object.create(null);
|
||||||
var lastScope = "";
|
var lastScope = "";
|
||||||
|
var scopeGeneration = 0;
|
||||||
var SUCCESS_STATUSES = {
|
var SUCCESS_STATUSES = {
|
||||||
paid: true,
|
paid: true,
|
||||||
delivered: true,
|
delivered: true,
|
||||||
@@ -40,6 +41,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearFlights() {
|
function clearFlights() {
|
||||||
|
scopeGeneration += 1;
|
||||||
|
Object.keys(createFlights).forEach(function (key) {
|
||||||
|
var flight = createFlights[key];
|
||||||
|
if (flight && typeof flight.cancel === "function") flight.cancel();
|
||||||
|
});
|
||||||
createFlights = Object.create(null);
|
createFlights = Object.create(null);
|
||||||
Object.keys(polls).forEach(function (pollKey) {
|
Object.keys(polls).forEach(function (pollKey) {
|
||||||
var active = polls[pollKey];
|
var active = polls[pollKey];
|
||||||
@@ -87,6 +93,9 @@
|
|||||||
function callback(options, name) {
|
function callback(options, name) {
|
||||||
var args = Array.prototype.slice.call(arguments, 2);
|
var args = Array.prototype.slice.call(arguments, 2);
|
||||||
if (!options || typeof options[name] !== "function") return;
|
if (!options || typeof options[name] !== "function") return;
|
||||||
|
if (options.scope && userScope() !== options.scope) return;
|
||||||
|
if (options.generation !== undefined && scopeGeneration !== options.generation) return;
|
||||||
|
if (typeof options.isActive === "function" && !options.isActive()) return;
|
||||||
try {
|
try {
|
||||||
options[name].apply(null, args);
|
options[name].apply(null, args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -105,21 +114,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var key = userScope() + ":" + String(packageId) + ":" + method;
|
var key = userScope() + ":" + String(packageId) + ":" + method;
|
||||||
if (createFlights[key]) return createFlights[key];
|
if (createFlights[key]) return createFlights[key].promise;
|
||||||
|
|
||||||
var promise = request("/pay/vip", {
|
var requestController = !options.signal && window.AbortController
|
||||||
method: "POST",
|
? new window.AbortController()
|
||||||
body: JSON.stringify({
|
: null;
|
||||||
package_id: packageId,
|
var flight = {
|
||||||
payment_method: method,
|
promise: request("/pay/vip", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
package_id: packageId,
|
||||||
|
payment_method: method,
|
||||||
|
}),
|
||||||
|
signal: options.signal || (requestController ? requestController.signal : undefined),
|
||||||
}),
|
}),
|
||||||
signal: options.signal,
|
cancel: function () {
|
||||||
|
if (typeof options.cancel === "function") {
|
||||||
|
try { options.cancel(); } catch (error) {}
|
||||||
|
}
|
||||||
|
if (requestController) {
|
||||||
|
try { requestController.abort(); } catch (error) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
createFlights[key] = flight;
|
||||||
|
flight.promise.then(function () {
|
||||||
|
if (createFlights[key] === flight) delete createFlights[key];
|
||||||
|
}, function () {
|
||||||
|
if (createFlights[key] === flight) delete createFlights[key];
|
||||||
});
|
});
|
||||||
createFlights[key] = promise;
|
return flight.promise;
|
||||||
promise.then(function () {
|
|
||||||
if (createFlights[key] === promise) delete createFlights[key];
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function exchange(options) {
|
function exchange(options) {
|
||||||
@@ -149,7 +173,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function poll(orderNumber, options) {
|
function poll(orderNumber, options) {
|
||||||
options = options || {};
|
options = Object.assign({}, options || {});
|
||||||
orderNumber = String(orderNumber || "");
|
orderNumber = String(orderNumber || "");
|
||||||
if (!orderNumber) {
|
if (!orderNumber) {
|
||||||
return {
|
return {
|
||||||
@@ -158,6 +182,9 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
var pollScope = userScope();
|
var pollScope = userScope();
|
||||||
|
var pollGeneration = scopeGeneration;
|
||||||
|
if (!options.scope) options.scope = pollScope;
|
||||||
|
if (options.generation === undefined) options.generation = pollGeneration;
|
||||||
var pollKey = pollScope + ":" + orderNumber;
|
var pollKey = pollScope + ":" + orderNumber;
|
||||||
if (polls[pollKey]) return polls[pollKey].handle;
|
if (polls[pollKey]) return polls[pollKey].handle;
|
||||||
|
|
||||||
@@ -201,6 +228,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function terminal(data, response, status) {
|
function terminal(data, response, status) {
|
||||||
|
if ((options.scope && userScope() !== options.scope)
|
||||||
|
|| (options.generation !== undefined && scopeGeneration !== options.generation)) {
|
||||||
|
finish({ terminal: false, aborted: true, status: "scope_changed" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
var success = !!(data && data.paid) || !!SUCCESS_STATUSES[status];
|
var success = !!(data && data.paid) || !!SUCCESS_STATUSES[status];
|
||||||
invalidate(options.tags || ["vip", "orders"]);
|
invalidate(options.tags || ["vip", "orders"]);
|
||||||
callback(options, "onTerminal", {
|
callback(options, "onTerminal", {
|
||||||
@@ -232,8 +264,8 @@
|
|||||||
function run() {
|
function run() {
|
||||||
state.timer = null;
|
state.timer = null;
|
||||||
if (state.done || state.aborted) return;
|
if (state.done || state.aborted) return;
|
||||||
// Detect an account transition even when no auth event reached this page.
|
// Detect account transitions and explicit auth resets even when no event reached this page.
|
||||||
if (userScope() !== pollScope || state.done || state.aborted) return;
|
if (userScope() !== pollScope || scopeGeneration !== pollGeneration || state.done || state.aborted) return;
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
state.waitingForVisible = true;
|
state.waitingForVisible = true;
|
||||||
return;
|
return;
|
||||||
@@ -307,9 +339,23 @@
|
|||||||
var activePoll = null;
|
var activePoll = null;
|
||||||
var stopped = false;
|
var stopped = false;
|
||||||
var controller = window.AbortController ? new window.AbortController() : null;
|
var controller = window.AbortController ? new window.AbortController() : null;
|
||||||
|
var startScope = userScope();
|
||||||
|
var startGeneration = scopeGeneration;
|
||||||
if (controller) options.signal = controller.signal;
|
if (controller) options.signal = controller.signal;
|
||||||
|
options.scope = startScope;
|
||||||
|
options.generation = startGeneration;
|
||||||
|
options.isActive = function () {
|
||||||
|
return !stopped && scopeGeneration === startGeneration && userScope() === startScope;
|
||||||
|
};
|
||||||
|
options.cancel = function () {
|
||||||
|
if (controller) {
|
||||||
|
try { controller.abort(); } catch (error) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
var promise = purchase(options).then(function (response) {
|
var promise = purchase(options).then(function (response) {
|
||||||
if (stopped) return { ok: false, aborted: true, status: "aborted" };
|
if (!options.isActive()) {
|
||||||
|
return { ok: false, aborted: true, status: stopped ? "aborted" : "scope_changed" };
|
||||||
|
}
|
||||||
var data = response.data || response.json || {};
|
var data = response.data || response.json || {};
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
callback(options, "onError", response);
|
callback(options, "onError", response);
|
||||||
@@ -322,6 +368,9 @@
|
|||||||
callback(options, "onError", missing);
|
callback(options, "onError", missing);
|
||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
|
if (!options.isActive()) {
|
||||||
|
return { ok: false, aborted: true, status: "scope_changed" };
|
||||||
|
}
|
||||||
activePoll = poll(data.order_number, options);
|
activePoll = poll(data.order_number, options);
|
||||||
activePoll.promise.then(function (result) {
|
activePoll.promise.then(function (result) {
|
||||||
if (result.success) callback(options, "onSuccess", result);
|
if (result.success) callback(options, "onSuccess", result);
|
||||||
@@ -334,6 +383,9 @@
|
|||||||
poll: activePoll,
|
poll: activePoll,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (!options.isActive()) {
|
||||||
|
return { ok: false, aborted: true, status: "scope_changed" };
|
||||||
|
}
|
||||||
invalidate(options.tags || ["vip", "orders"]);
|
invalidate(options.tags || ["vip", "orders"]);
|
||||||
callback(options, "onSuccess", { terminal: true, success: true, data: data, response: response });
|
callback(options, "onSuccess", { terminal: true, success: true, data: data, response: response });
|
||||||
return { ok: true, pending: false, data: data, response: response };
|
return { ok: true, pending: false, data: data, response: response };
|
||||||
@@ -343,9 +395,7 @@
|
|||||||
promise: promise,
|
promise: promise,
|
||||||
abort: function () {
|
abort: function () {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
if (controller) {
|
options.cancel();
|
||||||
try { controller.abort(); } catch (error) {}
|
|
||||||
}
|
|
||||||
if (activePoll && typeof activePoll.abort === "function") activePoll.abort();
|
if (activePoll && typeof activePoll.abort === "function") activePoll.abort();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user