jason-rails 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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;
|