@whooshbang/contract-mock 2.0.0-rc.4 → 2.0.0-rc.5
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.
- package/CHANGELOG.md +6 -0
- package/dist/core.js +68 -16
- package/dist/core.js.map +1 -1
- package/package.json +3 -3
- package/scenarios/v1/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ recorded here.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 2.0.0-rc.5
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Route a message the way the service does: a chosen `connection_id` is honored, and with none the subscriber's preferred channel, else the default connection when connected there, else the most recently connected one. A chosen connection that is unknown, archived or paused is `request_invalid` at `/connection_id`; one the subscriber never connected through is `subscriber_not_connected` (FXO-2865).
|
|
13
|
+
|
|
8
14
|
## 2.0.0-rc.4
|
|
9
15
|
|
|
10
16
|
### Changed
|
package/dist/core.js
CHANGED
|
@@ -1413,6 +1413,10 @@ function problem(code, status, diagnosticId, overrides = {}) {
|
|
|
1413
1413
|
...(overrides.provider_failure === undefined
|
|
1414
1414
|
? {}
|
|
1415
1415
|
: { provider_failure: overrides.provider_failure }),
|
|
1416
|
+
...(overrides.channel === undefined ? {} : { channel: overrides.channel }),
|
|
1417
|
+
...(overrides.subscriber_id === undefined
|
|
1418
|
+
? {}
|
|
1419
|
+
: { subscriber_id: overrides.subscriber_id }),
|
|
1416
1420
|
};
|
|
1417
1421
|
if (!validateProblem(value)) {
|
|
1418
1422
|
throw new Error("Internal contract-mock Problem Details violation.");
|
|
@@ -2032,6 +2036,8 @@ function normalizeMessageRequest(request) {
|
|
|
2032
2036
|
};
|
|
2033
2037
|
}
|
|
2034
2038
|
function messageDeliveryTarget(request, environment) {
|
|
2039
|
+
if (request.connection_id !== undefined)
|
|
2040
|
+
return "connection";
|
|
2035
2041
|
return (request.delivery_target ??
|
|
2036
2042
|
(environment === "test" &&
|
|
2037
2043
|
SIMULATOR_DESTINATIONS.has(request.to.subscriber_id)
|
|
@@ -2055,21 +2061,20 @@ function simulatorBinding(state, credential, subscriberId, notifierId) {
|
|
|
2055
2061
|
return binding;
|
|
2056
2062
|
}
|
|
2057
2063
|
/**
|
|
2058
|
-
* Resolves one
|
|
2064
|
+
* Resolves the one binding a send reaches, by the service's rule (FXO-2865).
|
|
2059
2065
|
*
|
|
2060
|
-
*
|
|
2061
|
-
*
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
2066
|
+
* A named connection is the only one used. Otherwise the recipient's
|
|
2067
|
+
* preference is the only one used; otherwise the default connection when the
|
|
2068
|
+
* subscriber is connected there; otherwise the connection they connected
|
|
2069
|
+
* through most recently, which here is the last one recorded. A converted-v1
|
|
2070
|
+
* binding remains eligible only when the subscriber has no connection-bound
|
|
2071
|
+
* candidate.
|
|
2064
2072
|
*/
|
|
2065
2073
|
function connectionBinding(state, credential, subscriberId, notifierId, connectionId) {
|
|
2066
2074
|
const routeKey = notifierConnectionKey(credential.projectId, credential.environment, notifierId);
|
|
2067
2075
|
const defaultConnectionId = state.notifierDefaultConnections.get(routeKey);
|
|
2068
2076
|
const isExecutable = (binding) => {
|
|
2069
|
-
if (binding.connection_id === undefined
|
|
2070
|
-
binding.connection_id !== (connectionId ?? defaultConnectionId) ||
|
|
2071
|
-
!(state.notifierConnections.get(routeKey)?.has(binding.connection_id) ??
|
|
2072
|
-
false)) {
|
|
2077
|
+
if (binding.connection_id === undefined) {
|
|
2073
2078
|
return false;
|
|
2074
2079
|
}
|
|
2075
2080
|
const record = state.channelConnections.get(binding.connection_id);
|
|
@@ -2107,9 +2112,13 @@ function connectionBinding(state, credential, subscriberId, notifierId, connecti
|
|
|
2107
2112
|
state.recipientConnectionIds.has(subscriberGroupKey(credential.projectId, credential.environment, preference.connection_id)));
|
|
2108
2113
|
}
|
|
2109
2114
|
const connectionBound = candidates.filter((binding) => binding.connection_id !== undefined);
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
2115
|
+
const executable = connectionBound.filter((binding) => isExecutable(binding));
|
|
2116
|
+
const onDefault = executable.find((binding) => binding.connection_id === defaultConnectionId);
|
|
2117
|
+
if (onDefault !== undefined) {
|
|
2118
|
+
return onDefault;
|
|
2119
|
+
}
|
|
2120
|
+
if (executable.length > 0) {
|
|
2121
|
+
return executable[executable.length - 1];
|
|
2113
2122
|
}
|
|
2114
2123
|
if (connectionBound.length > 0) {
|
|
2115
2124
|
return undefined;
|
|
@@ -6022,8 +6031,52 @@ function addPublicRoutes(app, getState, options) {
|
|
|
6022
6031
|
}
|
|
6023
6032
|
const matchingBinding = deliveryTarget === "simulator"
|
|
6024
6033
|
? simulatorBinding(state, credential, normalized.to.subscriber_id, notifierId)
|
|
6025
|
-
: connectionBinding(state, credential, normalized.to.subscriber_id, notifierId,
|
|
6034
|
+
: connectionBinding(state, credential, normalized.to.subscriber_id, notifierId, normalized.connection_id);
|
|
6026
6035
|
if (matchingBinding === undefined) {
|
|
6036
|
+
// A chosen connection is the sender's, not the recipient's: say which
|
|
6037
|
+
// one the subscriber is not connected through (FXO-2865).
|
|
6038
|
+
const chosen = normalized.connection_id === undefined || exactConnectionTest
|
|
6039
|
+
? undefined
|
|
6040
|
+
: state.channelConnections.get(normalized.connection_id);
|
|
6041
|
+
// Not connected anywhere is still subscriber_unbound below, as on the
|
|
6042
|
+
// service; subscriber_not_connected names a subscriber connected
|
|
6043
|
+
// somewhere else.
|
|
6044
|
+
const connectedElsewhere = normalized.connection_id !== undefined &&
|
|
6045
|
+
!exactConnectionTest &&
|
|
6046
|
+
connectionBinding(state, credential, normalized.to.subscriber_id, notifierId) !== undefined;
|
|
6047
|
+
if (normalized.connection_id !== undefined && !exactConnectionTest) {
|
|
6048
|
+
if (chosen === undefined ||
|
|
6049
|
+
chosen.ownerOrganizationId !== credential.organizationId ||
|
|
6050
|
+
chosen.connection.project_id !== credential.projectId ||
|
|
6051
|
+
chosen.connection.status === "archived")
|
|
6052
|
+
return problemResponse({
|
|
6053
|
+
...problem("request_invalid", 400, state.idGenerator("diagnostic")),
|
|
6054
|
+
field_errors: [
|
|
6055
|
+
{
|
|
6056
|
+
code: "connection_not_found",
|
|
6057
|
+
detail: "No connection with this ID exists in this environment.",
|
|
6058
|
+
field: "/connection_id",
|
|
6059
|
+
},
|
|
6060
|
+
],
|
|
6061
|
+
});
|
|
6062
|
+
if (chosen.connection.status !== "active" &&
|
|
6063
|
+
chosen.connection.status !== "degraded")
|
|
6064
|
+
return problemResponse({
|
|
6065
|
+
...problem("request_invalid", 400, state.idGenerator("diagnostic")),
|
|
6066
|
+
field_errors: [
|
|
6067
|
+
{
|
|
6068
|
+
code: "connection_not_sending",
|
|
6069
|
+
detail: "This connection is paused or not finished setting up, so it cannot send.",
|
|
6070
|
+
field: "/connection_id",
|
|
6071
|
+
},
|
|
6072
|
+
],
|
|
6073
|
+
});
|
|
6074
|
+
if (connectedElsewhere)
|
|
6075
|
+
return problemResponse(problem("subscriber_not_connected", 422, state.idGenerator("diagnostic"), {
|
|
6076
|
+
channel: chosen.connection.provider,
|
|
6077
|
+
subscriber_id: normalized.to.subscriber_id,
|
|
6078
|
+
}));
|
|
6079
|
+
}
|
|
6027
6080
|
if (state.preferences.has(recipientPreferenceKey(credential.projectId, credential.environment, normalized.to.subscriber_id, notifierId))) {
|
|
6028
6081
|
return problemResponse(problem("preferred_binding_unavailable", 422, state.idGenerator("diagnostic")));
|
|
6029
6082
|
}
|
|
@@ -7263,11 +7316,10 @@ function addPublicRoutes(app, getState, options) {
|
|
|
7263
7316
|
record.binding.connection_id === connection.connection.id &&
|
|
7264
7317
|
record.binding.status === "active")?.binding;
|
|
7265
7318
|
if (!binding)
|
|
7266
|
-
return problemResponse({
|
|
7267
|
-
...problem("subscriber_not_connected", 422, state.idGenerator("diagnostic")),
|
|
7319
|
+
return problemResponse(problem("subscriber_not_connected", 422, state.idGenerator("diagnostic"), {
|
|
7268
7320
|
channel: connection.connection.provider,
|
|
7269
7321
|
subscriber_id: input.to.subscriber_id,
|
|
7270
|
-
});
|
|
7322
|
+
}));
|
|
7271
7323
|
// A connection test is authorized by connections:write. The internal
|
|
7272
7324
|
// message admission carries the same tenant and exact bound connection;
|
|
7273
7325
|
// it does not grant a reusable messages:write credential to the caller.
|