jason-rails 0.5.1 → 0.6.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +14 -5
- data/app/controllers/jason/api/pusher_controller.rb +15 -0
- data/app/controllers/jason/api_controller.rb +44 -2
- data/client/lib/JasonProvider.js +1 -1
- data/client/lib/createJasonReducers.js +7 -0
- data/client/lib/createPayloadHandler.d.ts +6 -3
- data/client/lib/createPayloadHandler.js +8 -4
- data/client/lib/createTransportAdapter.d.ts +5 -0
- data/client/lib/createTransportAdapter.js +20 -0
- data/client/lib/pruneIdsMiddleware.js +9 -11
- data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
- data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/pusherAdapter.js +68 -0
- data/client/lib/useJason.js +14 -34
- data/client/lib/useJason.test.js +8 -2
- data/client/package.json +2 -1
- data/client/src/JasonProvider.tsx +1 -1
- data/client/src/createJasonReducers.ts +7 -0
- data/client/src/createPayloadHandler.ts +9 -4
- data/client/src/createTransportAdapter.ts +13 -0
- data/client/src/pruneIdsMiddleware.ts +11 -11
- data/client/src/restClient.ts +1 -0
- data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
- data/client/src/transportAdapters/pusherAdapter.ts +72 -0
- data/client/src/useJason.test.ts +8 -2
- data/client/src/useJason.ts +15 -36
- data/client/yarn.lock +12 -0
- data/config/routes.rb +5 -1
- data/lib/jason.rb +29 -8
- data/lib/jason/broadcaster.rb +19 -0
- data/lib/jason/channel.rb +6 -2
- data/lib/jason/graph_helper.rb +165 -0
- data/lib/jason/includes_helper.rb +108 -0
- data/lib/jason/lua_generator.rb +23 -1
- data/lib/jason/publisher.rb +16 -16
- data/lib/jason/subscription.rb +208 -183
- data/lib/jason/version.rb +1 -1
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 453e1a0d649445d973de2e3adf27f88f91c5bf983e65b3c2d993f090816db72b
|
4
|
+
data.tar.gz: ec2600988c60bcb6f661b8835cd3bcb7d3a4b740749ab0f21109e9b6a513b4ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc2ba8529669a1fe4c48d2ee2ba595a7e3ae907a0d10234f12d68d797de63177143187c5c3767cf32ab35bd053c3c9f3bfa14461f8b6be71d8df437e3e997c7d
|
7
|
+
data.tar.gz: c93e0002f3a7e2379ed29483b6a8323b4b492a319ccc4f9948f0638c47e62a9eeb8b416fad2bbe2cc6b5c13f11a103eab462b7a35f5a36145037981e49c99cc6
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -54,7 +54,7 @@ end
|
|
54
54
|
|
55
55
|
First you need to wrap your root component in a `JasonProvider`.
|
56
56
|
|
57
|
-
```
|
57
|
+
```jsx
|
58
58
|
import { JasonProvider } from '@jamesr2323/jason'
|
59
59
|
|
60
60
|
return <JasonProvider>
|
@@ -67,7 +67,8 @@ This is a wrapper around `react-redux` Provider component. This accepts the foll
|
|
67
67
|
- `reducers` - An object of reducers that will be included in `configureStore`. Make sure these do not conflict with the names of any of the models you are configuring for use with Jason
|
68
68
|
- `extraActions` - Extra actions you want to be available via the `useAct` hook. (See below)
|
69
69
|
This must be a function which returns an object which will be merged with the main Jason actions. The function will be passed a dispatch function, store, axios instance and the Jason actions. For example you can add actions for one of your custom slices:
|
70
|
-
|
70
|
+
|
71
|
+
```js
|
71
72
|
function extraActions(dispatch, store, restClient, act) {
|
72
73
|
return {
|
73
74
|
local: {
|
@@ -109,7 +110,7 @@ export default function PostCreator() {
|
|
109
110
|
This subscribes your Redux store to a model or set of models. It will automatically unsubscribe when the component unmounts.
|
110
111
|
|
111
112
|
Example
|
112
|
-
```
|
113
|
+
```jsx
|
113
114
|
import React from 'react'
|
114
115
|
import { useSelector } from 'react-redux'
|
115
116
|
import { useSub } from '@jamesr2323/jason'
|
@@ -125,14 +126,22 @@ export default function PostsList() {
|
|
125
126
|
}
|
126
127
|
```
|
127
128
|
|
129
|
+
## Authorization
|
130
|
+
|
131
|
+
By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
|
132
|
+
|
133
|
+
### Authorizing subscriptions
|
134
|
+
You can do this by providing an class to Jason in the initializer under the `subscription_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `conditions`, `sub_models` and return true or false for whether the user is allowed to access a subscription with those parameters. You can decide the implementation details of this to be as simple or complex as your app requires.
|
135
|
+
|
136
|
+
### Authorizing updates
|
137
|
+
Similarly to authorizing subscriptions, you can do this by providing an class to Jason in the initializer under the `update_authorization_service` key. This must be a class receiving a message `call` with the parameters `user`, `model`, `instance`, `update`, `remove` and return true or false for whether the user is allowed to access a subscription with those parameters.
|
128
138
|
|
129
139
|
## Roadmap
|
130
140
|
|
131
141
|
Development is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:
|
132
142
|
- Failure handling - rolling back local state in case of an error on the server
|
133
|
-
- Authorization -
|
143
|
+
- Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.
|
134
144
|
- Utilities for "Draft editing" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)
|
135
|
-
- Integration with pub/sub-as-a-service tools, such as Pusher
|
136
145
|
|
137
146
|
## Development
|
138
147
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Jason::Api::PusherController < ApplicationController
|
2
|
+
skip_before_action :verify_authenticity_token
|
3
|
+
|
4
|
+
def auth
|
5
|
+
channel_main_name = params[:channel_name].remove("private-#{Jason.pusher_channel_prefix}-")
|
6
|
+
subscription_id = channel_main_name.remove('jason-')
|
7
|
+
|
8
|
+
if Jason::Subscription.find_by_id(subscription_id).user_can_access?(current_user)
|
9
|
+
response = Pusher.authenticate(params[:channel_name], params[:socket_id])
|
10
|
+
return render json: response
|
11
|
+
else
|
12
|
+
return head :forbidden
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,6 +1,21 @@
|
|
1
1
|
class Jason::ApiController < ::ApplicationController
|
2
|
-
|
3
|
-
|
2
|
+
before_action :load_and_authorize_subscription, only: [:create_subscription, :remove_subscription, :get_payload]
|
3
|
+
# config seems to be a reserved name, resulting in infinite loop
|
4
|
+
def configuration
|
5
|
+
payload = {
|
6
|
+
schema: Jason.schema,
|
7
|
+
transportService: Jason.transport_service,
|
8
|
+
}
|
9
|
+
|
10
|
+
if Jason.transport_service == :pusher
|
11
|
+
payload.merge!({
|
12
|
+
pusherKey: Jason.pusher_key,
|
13
|
+
pusherRegion: Jason.pusher_region,
|
14
|
+
pusherChannelPrefix: Jason.pusher_channel_prefix
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
render json: payload
|
4
19
|
end
|
5
20
|
|
6
21
|
def action
|
@@ -33,4 +48,31 @@ class Jason::ApiController < ::ApplicationController
|
|
33
48
|
|
34
49
|
return head :ok
|
35
50
|
end
|
51
|
+
|
52
|
+
def create_subscription
|
53
|
+
@subscription.add_consumer(params[:consumer_id])
|
54
|
+
render json: { channelName: @subscription.channel }
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_subscription
|
58
|
+
@subscription.remove_consumer(params[:consumer_id])
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_payload
|
62
|
+
if params[:options].try(:[], :force_refresh)
|
63
|
+
@subscription.set_ids_for_sub_models
|
64
|
+
end
|
65
|
+
|
66
|
+
render json: @subscription.get
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def load_and_authorize_subscription
|
72
|
+
config = params[:config].to_unsafe_h
|
73
|
+
@subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
74
|
+
if !@subscription.user_can_access?(current_user)
|
75
|
+
return head :forbidden
|
76
|
+
end
|
77
|
+
end
|
36
78
|
end
|
data/client/lib/JasonProvider.js
CHANGED
@@ -8,7 +8,7 @@ const useJason_1 = __importDefault(require("./useJason"));
|
|
8
8
|
const react_redux_1 = require("react-redux");
|
9
9
|
const JasonContext_1 = __importDefault(require("./JasonContext"));
|
10
10
|
const JasonProvider = ({ reducers, middleware, extraActions, children }) => {
|
11
|
-
const [store, value
|
11
|
+
const [store, value] = useJason_1.default({ reducers, middleware, extraActions });
|
12
12
|
if (!(store && value))
|
13
13
|
return react_1.default.createElement("div", null); // Wait for async fetch of schema to complete
|
14
14
|
return react_1.default.createElement(react_redux_1.Provider, { store: store },
|
@@ -42,6 +42,7 @@ function generateJasonSlices(models) {
|
|
42
42
|
setSubscriptionIds(s, a) {
|
43
43
|
const { payload } = a;
|
44
44
|
const { subscriptionId, model, ids } = payload;
|
45
|
+
console.log({ initialState });
|
45
46
|
s[model][subscriptionId] = ids;
|
46
47
|
},
|
47
48
|
addSubscriptionId(s, a) {
|
@@ -53,6 +54,12 @@ function generateJasonSlices(models) {
|
|
53
54
|
const { payload } = a;
|
54
55
|
const { subscriptionId, model, id } = payload;
|
55
56
|
s[model][subscriptionId] = lodash_1.default.remove(s[model][subscriptionId] || [], id);
|
57
|
+
},
|
58
|
+
removeSubscription(s, a) {
|
59
|
+
const { payload: { subscriptionId } } = a;
|
60
|
+
lodash_1.default.map(models, model => {
|
61
|
+
delete s[model][subscriptionId];
|
62
|
+
});
|
56
63
|
}
|
57
64
|
}
|
58
65
|
}).reducer;
|
@@ -1,6 +1,9 @@
|
|
1
|
-
export default function createPayloadHandler({ dispatch, serverActionQueue,
|
1
|
+
export default function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }: {
|
2
2
|
dispatch: any;
|
3
3
|
serverActionQueue: any;
|
4
|
-
|
4
|
+
transportAdapter: any;
|
5
5
|
config: any;
|
6
|
-
}):
|
6
|
+
}): {
|
7
|
+
handlePayload: (data: any) => void;
|
8
|
+
tearDown: () => void;
|
9
|
+
};
|
@@ -11,7 +11,7 @@ function diffSeconds(dt2, dt1) {
|
|
11
11
|
var diff = (dt2.getTime() - dt1.getTime()) / 1000;
|
12
12
|
return Math.abs(Math.round(diff));
|
13
13
|
}
|
14
|
-
function createPayloadHandler({ dispatch, serverActionQueue,
|
14
|
+
function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, config }) {
|
15
15
|
const subscriptionId = uuid_1.v4();
|
16
16
|
let idx = {};
|
17
17
|
let patchQueue = {};
|
@@ -19,7 +19,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
19
19
|
let updateDeadline = null;
|
20
20
|
let checkInterval;
|
21
21
|
function getPayload() {
|
22
|
-
setTimeout(() =>
|
22
|
+
setTimeout(() => transportAdapter.getPayload(config), 1000);
|
23
23
|
}
|
24
24
|
function camelizeKeys(item) {
|
25
25
|
return deepCamelizeKeys_1.default(item, key => uuid_1.validate(key));
|
@@ -41,7 +41,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
41
41
|
dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
|
42
42
|
}
|
43
43
|
else if (destroy) {
|
44
|
-
|
44
|
+
// Middleware will determine if this model should be removed if it isn't in any other subscriptions
|
45
45
|
dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
|
46
46
|
}
|
47
47
|
else {
|
@@ -87,6 +87,10 @@ function createPayloadHandler({ dispatch, serverActionQueue, subscription, confi
|
|
87
87
|
}
|
88
88
|
}
|
89
89
|
tGetPayload();
|
90
|
-
|
90
|
+
// Clean up after ourselves
|
91
|
+
function tearDown() {
|
92
|
+
dispatch({ type: `jasonModels/removeSubscription`, payload: { subscriptionId } });
|
93
|
+
}
|
94
|
+
return { handlePayload, tearDown };
|
91
95
|
}
|
92
96
|
exports.default = createPayloadHandler;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const actionCableAdapter_1 = __importDefault(require("./transportAdapters/actionCableAdapter"));
|
7
|
+
const pusherAdapter_1 = __importDefault(require("./transportAdapters/pusherAdapter"));
|
8
|
+
function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
|
9
|
+
const { transportService } = jasonConfig;
|
10
|
+
if (transportService === 'action_cable') {
|
11
|
+
return actionCableAdapter_1.default(jasonConfig, handlePayload, dispatch, onConnect);
|
12
|
+
}
|
13
|
+
else if (transportService === 'pusher') {
|
14
|
+
return pusherAdapter_1.default(jasonConfig, handlePayload, dispatch);
|
15
|
+
}
|
16
|
+
else {
|
17
|
+
throw (`Transport adapter does not exist for ${transportService}`);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
exports.default = createTransportAdapter;
|
@@ -6,20 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const lodash_1 = __importDefault(require("lodash"));
|
7
7
|
const pluralize_1 = __importDefault(require("pluralize"));
|
8
8
|
const pruneIdsMiddleware = schema => store => next => action => {
|
9
|
-
const { type } = action;
|
9
|
+
const { type, payload } = action;
|
10
10
|
const result = next(action);
|
11
11
|
const state = store.getState();
|
12
|
-
if (type === 'jasonModels/setSubscriptionIds') {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
lodash_1.default.
|
17
|
-
ids = lodash_1.default.union(ids, subscribedIds);
|
18
|
-
});
|
19
|
-
// Find IDs currently in Redux that aren't in any subscription
|
20
|
-
const idsToRemove = lodash_1.default.difference(state[pluralize_1.default(model)].ids, ids);
|
21
|
-
store.dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
|
12
|
+
if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionIds') {
|
13
|
+
const { model, ids } = payload;
|
14
|
+
let idsInSubs = [];
|
15
|
+
lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
|
16
|
+
idsInSubs = lodash_1.default.union(idsInSubs, subscribedIds);
|
22
17
|
});
|
18
|
+
// Find IDs currently in Redux that aren't in any subscription
|
19
|
+
const idsToRemove = lodash_1.default.difference(state[pluralize_1.default(model)].ids, idsInSubs);
|
20
|
+
store.dispatch({ type: `${pluralize_1.default(model)}/removeMany`, payload: idsToRemove });
|
23
21
|
}
|
24
22
|
return result;
|
25
23
|
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const actioncable_1 = require("@rails/actioncable");
|
4
|
+
function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected) {
|
5
|
+
const consumer = actioncable_1.createConsumer();
|
6
|
+
const subscription = (consumer.subscriptions.create({
|
7
|
+
channel: 'Jason::Channel'
|
8
|
+
}, {
|
9
|
+
connected: () => {
|
10
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } });
|
11
|
+
console.debug('Connected to ActionCable');
|
12
|
+
// When AC loses connection - all state is lost, so we need to re-initialize all subscriptions
|
13
|
+
onConnected();
|
14
|
+
},
|
15
|
+
received: payload => {
|
16
|
+
handlePayload(payload);
|
17
|
+
console.debug("ActionCable Payload received: ", payload);
|
18
|
+
},
|
19
|
+
disconnected: () => {
|
20
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } });
|
21
|
+
console.warn('Disconnected from ActionCable');
|
22
|
+
}
|
23
|
+
}));
|
24
|
+
function getPayload(config, options) {
|
25
|
+
subscription.send(Object.assign({ getPayload: config }, options));
|
26
|
+
}
|
27
|
+
function createSubscription(config) {
|
28
|
+
subscription.send({ createSubscription: config });
|
29
|
+
}
|
30
|
+
function removeSubscription(config) {
|
31
|
+
subscription.send({ removeSubscription: config });
|
32
|
+
}
|
33
|
+
return { getPayload, createSubscription, removeSubscription };
|
34
|
+
}
|
35
|
+
exports.default = actionCableAdapter;
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const pusher_js_1 = __importDefault(require("pusher-js"));
|
7
|
+
const restClient_1 = __importDefault(require("../restClient"));
|
8
|
+
const uuid_1 = require("uuid");
|
9
|
+
const lodash_1 = __importDefault(require("lodash"));
|
10
|
+
function pusherAdapter(jasonConfig, handlePayload, dispatch) {
|
11
|
+
let consumerId = uuid_1.v4();
|
12
|
+
const { pusherKey, pusherRegion, pusherChannelPrefix } = jasonConfig;
|
13
|
+
const pusher = new pusher_js_1.default(pusherKey, {
|
14
|
+
cluster: 'eu',
|
15
|
+
forceTLS: true,
|
16
|
+
authEndpoint: '/jason/api/pusher/auth'
|
17
|
+
});
|
18
|
+
pusher.connection.bind('state_change', ({ current }) => {
|
19
|
+
if (current === 'connected') {
|
20
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } });
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } });
|
24
|
+
}
|
25
|
+
});
|
26
|
+
pusher.connection.bind('error', error => {
|
27
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } });
|
28
|
+
});
|
29
|
+
const configToChannel = {};
|
30
|
+
function createSubscription(config) {
|
31
|
+
restClient_1.default.post('/jason/api/create_subscription', { config, consumerId })
|
32
|
+
.then(({ data: { channelName } }) => {
|
33
|
+
configToChannel[JSON.stringify(config)] = channelName;
|
34
|
+
subscribeToChannel(channelName);
|
35
|
+
})
|
36
|
+
.catch(e => console.error(e));
|
37
|
+
}
|
38
|
+
function removeSubscription(config) {
|
39
|
+
const channelName = configToChannel[JSON.stringify(config)];
|
40
|
+
unsubscribeFromChannel(fullChannelName(channelName));
|
41
|
+
restClient_1.default.post('/jason/api/remove_subscription', { config, consumerId })
|
42
|
+
.catch(e => console.error(e));
|
43
|
+
}
|
44
|
+
function getPayload(config, options) {
|
45
|
+
restClient_1.default.post('/jason/api/get_payload', {
|
46
|
+
config,
|
47
|
+
options
|
48
|
+
})
|
49
|
+
.then(({ data }) => {
|
50
|
+
lodash_1.default.map(data, (payload, modelName) => {
|
51
|
+
handlePayload(payload);
|
52
|
+
});
|
53
|
+
})
|
54
|
+
.catch(e => console.error(e));
|
55
|
+
}
|
56
|
+
function subscribeToChannel(channelName) {
|
57
|
+
const channel = pusher.subscribe(fullChannelName(channelName));
|
58
|
+
channel.bind('changed', message => handlePayload(message));
|
59
|
+
}
|
60
|
+
function unsubscribeFromChannel(channelName) {
|
61
|
+
const channel = pusher.unsubscribe(fullChannelName(channelName));
|
62
|
+
}
|
63
|
+
function fullChannelName(channelName) {
|
64
|
+
return `private-${pusherChannelPrefix}-${channelName}`;
|
65
|
+
}
|
66
|
+
return { getPayload, createSubscription, removeSubscription };
|
67
|
+
}
|
68
|
+
exports.default = pusherAdapter;
|
data/client/lib/useJason.js
CHANGED
@@ -10,7 +10,7 @@ const createOptDis_1 = __importDefault(require("./createOptDis"));
|
|
10
10
|
const createServerActionQueue_1 = __importDefault(require("./createServerActionQueue"));
|
11
11
|
const restClient_1 = __importDefault(require("./restClient"));
|
12
12
|
const pruneIdsMiddleware_1 = __importDefault(require("./pruneIdsMiddleware"));
|
13
|
-
const
|
13
|
+
const createTransportAdapter_1 = __importDefault(require("./createTransportAdapter"));
|
14
14
|
const toolkit_1 = require("@reduxjs/toolkit");
|
15
15
|
const makeEager_1 = __importDefault(require("./makeEager"));
|
16
16
|
const humps_1 = require("humps");
|
@@ -20,14 +20,13 @@ const react_1 = require("react");
|
|
20
20
|
function useJason({ reducers, middleware = [], extraActions }) {
|
21
21
|
const [store, setStore] = react_1.useState(null);
|
22
22
|
const [value, setValue] = react_1.useState(null);
|
23
|
-
const [connected, setConnected] = react_1.useState(false);
|
24
23
|
react_1.useEffect(() => {
|
25
|
-
restClient_1.default.get('/jason/api/
|
26
|
-
.then(({ data:
|
24
|
+
restClient_1.default.get('/jason/api/config')
|
25
|
+
.then(({ data: jasonConfig }) => {
|
26
|
+
const { schema: snakey_schema } = jasonConfig;
|
27
27
|
const schema = humps_1.camelizeKeys(snakey_schema);
|
28
28
|
console.debug({ schema });
|
29
29
|
const serverActionQueue = createServerActionQueue_1.default();
|
30
|
-
const consumer = actioncable_1.createConsumer();
|
31
30
|
const allReducers = Object.assign(Object.assign({}, reducers), createJasonReducers_1.default(schema));
|
32
31
|
console.debug({ allReducers });
|
33
32
|
const store = toolkit_1.configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware_1.default(schema)] });
|
@@ -40,49 +39,29 @@ function useJason({ reducers, middleware = [], extraActions }) {
|
|
40
39
|
let subOptions = {};
|
41
40
|
function handlePayload(payload) {
|
42
41
|
const { md5Hash } = payload;
|
43
|
-
const
|
44
|
-
if (
|
45
|
-
|
42
|
+
const { handlePayload } = payloadHandlers[md5Hash];
|
43
|
+
if (handlePayload) {
|
44
|
+
handlePayload(payload);
|
46
45
|
}
|
47
46
|
else {
|
48
47
|
console.warn("Payload arrived with no handler", payload, payloadHandlers);
|
49
48
|
}
|
50
49
|
}
|
51
|
-
const
|
52
|
-
channel: 'Jason::Channel'
|
53
|
-
}, {
|
54
|
-
connected: () => {
|
55
|
-
setConnected(true);
|
56
|
-
dispatch({ type: 'jason/upsert', payload: { connected: true } });
|
57
|
-
console.debug('Connected to ActionCable');
|
58
|
-
// When AC loses connection - all state is lost, so we need to re-initialize all subscriptions
|
59
|
-
lodash_1.default.keys(configs).forEach(md5Hash => createSubscription(configs[md5Hash], subOptions[md5Hash]));
|
60
|
-
},
|
61
|
-
received: payload => {
|
62
|
-
handlePayload(payload);
|
63
|
-
console.debug("ActionCable Payload received: ", payload);
|
64
|
-
},
|
65
|
-
disconnected: () => {
|
66
|
-
setConnected(false);
|
67
|
-
dispatch({ type: 'jason/upsert', payload: { connected: false } });
|
68
|
-
console.warn('Disconnected from ActionCable');
|
69
|
-
}
|
70
|
-
}));
|
50
|
+
const transportAdapter = createTransportAdapter_1.default(jasonConfig, handlePayload, dispatch, () => lodash_1.default.keys(configs).forEach(md5Hash => createSubscription(configs[md5Hash], subOptions[md5Hash])));
|
71
51
|
function createSubscription(config, options = {}) {
|
72
52
|
// We need the hash to be consistent in Ruby / Javascript
|
73
53
|
const hashableConfig = lodash_1.default(Object.assign({ conditions: {}, includes: {} }, config)).toPairs().sortBy(0).fromPairs().value();
|
74
54
|
const md5Hash = blueimp_md5_1.default(JSON.stringify(hashableConfig));
|
75
|
-
payloadHandlers[md5Hash] = createPayloadHandler_1.default({ dispatch, serverActionQueue,
|
55
|
+
payloadHandlers[md5Hash] = createPayloadHandler_1.default({ dispatch, serverActionQueue, transportAdapter, config });
|
76
56
|
configs[md5Hash] = hashableConfig;
|
77
57
|
subOptions[md5Hash] = options;
|
78
|
-
setTimeout(() =>
|
58
|
+
setTimeout(() => transportAdapter.createSubscription(hashableConfig), 500);
|
79
59
|
let pollInterval = null;
|
80
|
-
console.log("createSubscription", { config, options });
|
81
60
|
// This is only for debugging / dev - not prod!
|
82
61
|
// @ts-ignore
|
83
62
|
if (options.pollInterval) {
|
84
63
|
// @ts-ignore
|
85
|
-
pollInterval = setInterval(() =>
|
64
|
+
pollInterval = setInterval(() => transportAdapter.getPayload(hashableConfig, { forceRefresh: true }), options.pollInterval);
|
86
65
|
}
|
87
66
|
return {
|
88
67
|
remove() {
|
@@ -94,8 +73,9 @@ function useJason({ reducers, middleware = [], extraActions }) {
|
|
94
73
|
};
|
95
74
|
}
|
96
75
|
function removeSubscription(config) {
|
97
|
-
|
76
|
+
transportAdapter.removeSubscription(config);
|
98
77
|
const md5Hash = blueimp_md5_1.default(JSON.stringify(config));
|
78
|
+
payloadHandlers[md5Hash].tearDown();
|
99
79
|
delete payloadHandlers[md5Hash];
|
100
80
|
delete configs[md5Hash];
|
101
81
|
delete subOptions[md5Hash];
|
@@ -109,6 +89,6 @@ function useJason({ reducers, middleware = [], extraActions }) {
|
|
109
89
|
setStore(store);
|
110
90
|
});
|
111
91
|
}, []);
|
112
|
-
return [store, value
|
92
|
+
return [store, value];
|
113
93
|
}
|
114
94
|
exports.default = useJason;
|