session_timeout_prompter 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +8 -0
- data/app/assets/javascripts/session_timeout_prompter.js +283 -0
- data/app/assets/stylesheets/session_timeout_prompter.css +7 -0
- data/app/controllers/session_timeout_prompter/server_pings_controller.rb +10 -0
- data/app/controllers/session_timeout_prompter/sessions_controller.rb +13 -0
- data/app/helpers/session_timeout_prompter/prompt_helper.rb +40 -0
- data/app/views/session_timeout_prompter/_modal_dialogs.html.erb +39 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/build +61 -0
- data/config/locales/en.yml +9 -0
- data/config/routes.rb +8 -0
- data/lib/session_timeout_prompter.rb +14 -0
- data/lib/session_timeout_prompter/version.rb +3 -0
- data/session_timeout_prompter.gemspec +41 -0
- metadata +304 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6be9fd633cab048831eaf8e2a706ef4dc8f87620
|
4
|
+
data.tar.gz: 11c086142b91fb883a39d4fdd3b7a81a26ea3a1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72d21ca10cd794f5d5b9001692a28e96a4e251cb7d54ecba662f67d51d915aa22574f02b9447f22006dbbeff907a0c0e13e86c5ce8735e614d77ee9e4bbacb8e
|
7
|
+
data.tar.gz: 16d1882386159825f8c1e41465ef3b45ab8c9c3ffe6804ccea53e56d8531d70cf3d9dc596e78a24859ce14ac5803c7f1e0fb6b12d6ca006924821d4d6c85ccef
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.3
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Ant Nettleship
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# Session Timeout Prompter
|
2
|
+
|
3
|
+
A Rails Engine to prompt the user when their session is about to timeout and allow them to extend it.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'session_timeout_prompter'
|
11
|
+
```
|
12
|
+
|
13
|
+
Mount the engine in your routes:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
mount SessionTimeoutPrompter::Engine at: "/session_timeout_prompter"`
|
17
|
+
```
|
18
|
+
|
19
|
+
Require the js:
|
20
|
+
|
21
|
+
```
|
22
|
+
//= session_timeout_prompter
|
23
|
+
```
|
24
|
+
|
25
|
+
Require the css:
|
26
|
+
|
27
|
+
```
|
28
|
+
*= require session_timeout_prompter
|
29
|
+
```
|
30
|
+
|
31
|
+
**Note:** Currently depends on jQuery for ajax / event handling.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Add the following after the body tag in your layout or on any page you wish to display the timeout prompt:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
= session_timeout_prompter(session_timeout_in_seconds: User.timeout_in.to_i, timeout_warning_in_seconds: 305, scope: :user)
|
39
|
+
```
|
40
|
+
(The example values assume you are using Devise timeoutable and are using a scope/model called User. The scope is purely so you can use multiple in the same application if necessary.)
|
41
|
+
|
42
|
+
|
43
|
+
### Configurables
|
44
|
+
|
45
|
+
#### "Log in again" path
|
46
|
+
By default, clicking the "Log in again" button will go to your application's root path. Due to the inherent inaccuracy of timing in Javascript it is advisable to make sure they're actually logged out to avoid confusion. To do this, simple override the controller action in your application:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# app/controllers/session_timeout_prompter/sessions_controller.rb
|
50
|
+
module SessionTimeoutPrompter
|
51
|
+
class SessionsController < ActionController::Base
|
52
|
+
|
53
|
+
def new
|
54
|
+
sign_out current_user
|
55
|
+
redirect_to new_user_session_path
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
#### Keeping the session alive
|
63
|
+
You can bind your own events to ping the server as follows:
|
64
|
+
|
65
|
+
```javascript
|
66
|
+
$(function(){
|
67
|
+
if(sessionTimeoutPrompter) {
|
68
|
+
|
69
|
+
// Ping server on scroll
|
70
|
+
$(window).on('scroll', function() {
|
71
|
+
serverPinger.pingServerWithThrottling();
|
72
|
+
});
|
73
|
+
|
74
|
+
// Ping server when typing or clicking
|
75
|
+
$(document).on('keydown click', function() {
|
76
|
+
serverPinger.pingServerWithThrottling();
|
77
|
+
});
|
78
|
+
|
79
|
+
// Ping server when scrolling inside a modal window
|
80
|
+
// (the ajax-modal-show event in this example is from ajax_modal in the epiJs gem)
|
81
|
+
$(document).on('ajax-modal-show', function() {
|
82
|
+
$('#modalWindow').scroll( function() {
|
83
|
+
serverPinger.pingServerWithThrottling();
|
84
|
+
});
|
85
|
+
});
|
86
|
+
|
87
|
+
// Ping server when a key is pressed in CKEditor
|
88
|
+
CKEDITOR.on('instanceCreated', function(e) {
|
89
|
+
e.editor.on('change', function() {
|
90
|
+
serverPinger.pingServerWithThrottling();
|
91
|
+
});
|
92
|
+
});
|
93
|
+
|
94
|
+
}
|
95
|
+
});
|
96
|
+
```
|
97
|
+
|
98
|
+
#### I18n
|
99
|
+
All of the text is rendered via Rails' standard I18n library. See `config/locales/en.yml`
|
100
|
+
|
101
|
+
## Development
|
102
|
+
|
103
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
104
|
+
|
105
|
+
### ES6
|
106
|
+
|
107
|
+
The Javascript is written in ES6 and transpiled with Babel. The ES6 should be modified in `src/javascripts` and then transpiled to `app/assets/javascripts/session_timeout_prompter.js`. Currently we don't make use of modules (we'd need webpack or similar), but this would probably be an improvement going forward to control scope.
|
108
|
+
|
109
|
+
There is a handy script `./build` that will do the transpilation and run the tests. Run with --help for options.
|
110
|
+
|
111
|
+
### Testing
|
112
|
+
|
113
|
+
Uses RSpec and Jasmine for automated testing. To run the Jasmine tests for the Javascript run `rake jasmine` and go to `localhost:8888` in your browser. The suite will run every time the page is reloaded.
|
114
|
+
|
115
|
+
### Deployment
|
116
|
+
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/session_timeout_prompter.
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
4
|
+
|
5
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
|
6
|
+
|
7
|
+
var Bootstrap3PromptRenderer = (function () {
|
8
|
+
|
9
|
+
// timeoutWarningModal: the jquery object for the Bootstrap3 modal to display
|
10
|
+
// when the session is about to time out
|
11
|
+
// timedOutModal: the jquery object for the Bootstrap3 modal to display
|
12
|
+
// when the session has timed out
|
13
|
+
// remainingTextContainer: the jquery object for the display of the time remaining
|
14
|
+
|
15
|
+
function Bootstrap3PromptRenderer(timeoutWarningModal, timedOutModal, remainingTextContainer) {
|
16
|
+
_classCallCheck(this, Bootstrap3PromptRenderer);
|
17
|
+
|
18
|
+
this.timeoutWarningModal = timeoutWarningModal;
|
19
|
+
this.timedOutModal = timedOutModal;
|
20
|
+
this.remainingTextContainer = remainingTextContainer;
|
21
|
+
this.currentlyShowingWarningPrompt = false;
|
22
|
+
}
|
23
|
+
|
24
|
+
_createClass(Bootstrap3PromptRenderer, [{
|
25
|
+
key: 'renderTimedOut',
|
26
|
+
value: function renderTimedOut() {
|
27
|
+
this.timeoutWarningModal.modal('hide');
|
28
|
+
this.timedOutModal.modal('show');
|
29
|
+
}
|
30
|
+
}, {
|
31
|
+
key: 'renderTimeoutWarning',
|
32
|
+
value: function renderTimeoutWarning(timeLeftInSeconds) {
|
33
|
+
this.updateRemainingTimeText(timeLeftInSeconds);
|
34
|
+
if (!this.currentlyShowingWarningPrompt) {
|
35
|
+
this.currentlyShowingWarningPrompt = true;
|
36
|
+
this.timeoutWarningModal.modal('show');
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}, {
|
40
|
+
key: 'hideAll',
|
41
|
+
value: function hideAll() {
|
42
|
+
this.timeoutWarningModal.modal('hide');
|
43
|
+
this.timedOutModal.modal('hide');
|
44
|
+
}
|
45
|
+
}, {
|
46
|
+
key: 'updateRemainingTimeText',
|
47
|
+
value: function updateRemainingTimeText(timeLeftInSeconds) {
|
48
|
+
var wholeMinutesRemaining = Math.floor(timeLeftInSeconds / 60);
|
49
|
+
var additionalSecondsRemaining = Math.floor(timeLeftInSeconds - wholeMinutesRemaining * 60);
|
50
|
+
this.remainingTextContainer.text(wholeMinutesRemaining + 'm ' + additionalSecondsRemaining + 's');
|
51
|
+
}
|
52
|
+
}]);
|
53
|
+
|
54
|
+
return Bootstrap3PromptRenderer;
|
55
|
+
})();
|
56
|
+
"use strict";
|
57
|
+
|
58
|
+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
59
|
+
|
60
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
61
|
+
|
62
|
+
var ServerPinger = (function () {
|
63
|
+
function ServerPinger(serverPingPath) {
|
64
|
+
_classCallCheck(this, ServerPinger);
|
65
|
+
|
66
|
+
this.serverPingPath = serverPingPath;
|
67
|
+
this.lastPingedAt = undefined;
|
68
|
+
}
|
69
|
+
|
70
|
+
_createClass(ServerPinger, [{
|
71
|
+
key: "pingServerNow",
|
72
|
+
value: function pingServerNow() {
|
73
|
+
var _this = this;
|
74
|
+
|
75
|
+
var callback = function callback() {
|
76
|
+
_this.setLastPingedAt();
|
77
|
+
};
|
78
|
+
jQuery.post(this.serverPingPath, callback);
|
79
|
+
}
|
80
|
+
}, {
|
81
|
+
key: "pingServerWithThrottling",
|
82
|
+
value: function pingServerWithThrottling() {
|
83
|
+
var seconds_to_throttle = arguments.length <= 0 || arguments[0] === undefined ? 10 : arguments[0];
|
84
|
+
|
85
|
+
if (!this.lastPingedAt || this.currentTimestamp() - this.lastPingedAt > seconds_to_throttle) {
|
86
|
+
this.pingServerNow();
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Private
|
91
|
+
}, {
|
92
|
+
key: "setLastPingedAt",
|
93
|
+
value: function setLastPingedAt() {
|
94
|
+
this.lastPingedAt = this.currentTimestamp();
|
95
|
+
}
|
96
|
+
}, {
|
97
|
+
key: "currentTimestamp",
|
98
|
+
value: function currentTimestamp() {
|
99
|
+
return Math.floor(new Date().getTime() / 1000);
|
100
|
+
}
|
101
|
+
}]);
|
102
|
+
|
103
|
+
return ServerPinger;
|
104
|
+
})();
|
105
|
+
'use strict';
|
106
|
+
|
107
|
+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
108
|
+
|
109
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
|
110
|
+
|
111
|
+
var SessionTimeoutPrompter = (function () {
|
112
|
+
function SessionTimeoutPrompter(configData) {
|
113
|
+
_classCallCheck(this, SessionTimeoutPrompter);
|
114
|
+
|
115
|
+
var serverPingPath = configData.serverPingPath;
|
116
|
+
var timeoutWarningInSeconds = configData.timeoutWarningInSeconds;
|
117
|
+
var sessionTimeoutInSeconds = configData.sessionTimeoutInSeconds;
|
118
|
+
var sessionKey = configData.sessionKey;
|
119
|
+
|
120
|
+
var timeoutWarningModal = jQuery('#session-timeout-prompter-timeout-warning-modal');
|
121
|
+
var timedOutModal = jQuery('#session-timeout-prompter-session-timed-out-modal');
|
122
|
+
var remainingTimeContainer = jQuery('#session-timeout-prompter-warning-timeout-in');
|
123
|
+
|
124
|
+
var promptRenderer = new Bootstrap3PromptRenderer(timeoutWarningModal, timedOutModal, remainingTimeContainer);
|
125
|
+
|
126
|
+
this.timeoutTimer = new TimeoutTimer(timeoutWarningInSeconds, sessionTimeoutInSeconds, sessionKey, promptRenderer);
|
127
|
+
this.serverPinger = new ServerPinger(serverPingPath);
|
128
|
+
this.remainLoggedInButton = jQuery('#session-timeout-prompter-remain-logged-in-btn');
|
129
|
+
}
|
130
|
+
|
131
|
+
_createClass(SessionTimeoutPrompter, [{
|
132
|
+
key: 'start',
|
133
|
+
value: function start() {
|
134
|
+
this.bindDefaultEvents();
|
135
|
+
this.timeoutTimer.start();
|
136
|
+
}
|
137
|
+
|
138
|
+
// Private
|
139
|
+
|
140
|
+
}, {
|
141
|
+
key: 'bindDefaultEvents',
|
142
|
+
value: function bindDefaultEvents() {
|
143
|
+
var _this = this;
|
144
|
+
|
145
|
+
// Restart the timer: This is triggered by any jquery ajax request completing,
|
146
|
+
// including pinging the server via our other events.
|
147
|
+
jQuery(document).ajaxComplete(function () {
|
148
|
+
_this.timeoutTimer.restart();
|
149
|
+
});
|
150
|
+
|
151
|
+
// When the user clicks the button to say they want to remain logged in we
|
152
|
+
// stop the timer to wait until it is restarted via via the ajaxComplete()
|
153
|
+
// event triggered by the ping
|
154
|
+
this.remainLoggedInButton.on('click', function () {
|
155
|
+
_this.serverPinger.pingServerNow();
|
156
|
+
_this.timeoutTimer.stop();
|
157
|
+
});
|
158
|
+
|
159
|
+
// Listen to the storage event fired in TimeoutTimer to synchronise browser tabs
|
160
|
+
// if a user extends their session in one tab but has another open for example.
|
161
|
+
jQuery(window).on('storage', function (e) {
|
162
|
+
var event = e.originalEvent;
|
163
|
+
_this.timeoutTimer.localStorageUpdated(event.key, event.newValue);
|
164
|
+
});
|
165
|
+
}
|
166
|
+
}]);
|
167
|
+
|
168
|
+
return SessionTimeoutPrompter;
|
169
|
+
})();
|
170
|
+
'use strict';
|
171
|
+
|
172
|
+
var sessionTimeoutPrompter = undefined;
|
173
|
+
jQuery(function () {
|
174
|
+
|
175
|
+
var timeoutPrompterContainer = jQuery('#session-timeout-prompter-container');
|
176
|
+
|
177
|
+
// If the container cannot be found then assume we don't need timeout prompting on this page.
|
178
|
+
if (timeoutPrompterContainer.length) {
|
179
|
+
var configData = timeoutPrompterContainer.data();
|
180
|
+
sessionTimeoutPrompter = new SessionTimeoutPrompter(configData);
|
181
|
+
sessionTimeoutPrompter.start();
|
182
|
+
}
|
183
|
+
});
|
184
|
+
"use strict";
|
185
|
+
|
186
|
+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
187
|
+
|
188
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
189
|
+
|
190
|
+
var TimeoutTimer = (function () {
|
191
|
+
|
192
|
+
// timeoutWarningInSeconds: Warning that their session is about to timeout
|
193
|
+
// when there are this many minutes left.
|
194
|
+
// sessionTimeoutInSeconds: Tell them their session has timed out when this
|
195
|
+
// many minutes have elapsed.
|
196
|
+
// sessionKey: Unique key for this session - used in local storage
|
197
|
+
// to make sure multiple browser tabs are synched.
|
198
|
+
|
199
|
+
function TimeoutTimer(timeoutWarningInSeconds, sessionTimeoutInSeconds, sessionKey, promptRenderer) {
|
200
|
+
_classCallCheck(this, TimeoutTimer);
|
201
|
+
|
202
|
+
this.sessionTimeoutInSeconds = sessionTimeoutInSeconds;
|
203
|
+
this.timeoutWarningInSeconds = timeoutWarningInSeconds;
|
204
|
+
this.sessionKey = sessionKey;
|
205
|
+
this.promptRenderer = promptRenderer;
|
206
|
+
this.tickInterval = undefined;
|
207
|
+
this.timeoutAt = undefined;
|
208
|
+
this.recalculateTimeoutAt();
|
209
|
+
}
|
210
|
+
|
211
|
+
_createClass(TimeoutTimer, [{
|
212
|
+
key: "start",
|
213
|
+
value: function start() {
|
214
|
+
var _this = this;
|
215
|
+
|
216
|
+
this.tick();
|
217
|
+
this.tickInterval = setInterval(function () {
|
218
|
+
_this.tick();
|
219
|
+
}, 1000);
|
220
|
+
}
|
221
|
+
}, {
|
222
|
+
key: "stop",
|
223
|
+
value: function stop() {
|
224
|
+
this.promptRenderer.hideAll();
|
225
|
+
clearInterval(this.tickInterval);
|
226
|
+
}
|
227
|
+
}, {
|
228
|
+
key: "restart",
|
229
|
+
value: function restart() {
|
230
|
+
this.stop();
|
231
|
+
this.recalculateTimeoutAt();
|
232
|
+
this.start();
|
233
|
+
}
|
234
|
+
}, {
|
235
|
+
key: "localStorageUpdated",
|
236
|
+
value: function localStorageUpdated(key, newTimeoutAt) {
|
237
|
+
if (key === this.sessionKey) {
|
238
|
+
this.stop();
|
239
|
+
this.timeoutAt = newTimeoutAt;
|
240
|
+
this.start();
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
// Private
|
245
|
+
}, {
|
246
|
+
key: "tick",
|
247
|
+
value: function tick() {
|
248
|
+
var timeLeftInSeconds = this.timeoutAt - this.currentTimestamp();
|
249
|
+
if (timeLeftInSeconds <= 0) {
|
250
|
+
this.showTimedOutPrompt();
|
251
|
+
} else if (timeLeftInSeconds <= this.timeoutWarningInSeconds) {
|
252
|
+
this.showTimeoutWarningPrompt(timeLeftInSeconds);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}, {
|
256
|
+
key: "showTimedOutPrompt",
|
257
|
+
value: function showTimedOutPrompt() {
|
258
|
+
this.stop();
|
259
|
+
this.promptRenderer.renderTimedOut();
|
260
|
+
}
|
261
|
+
}, {
|
262
|
+
key: "showTimeoutWarningPrompt",
|
263
|
+
value: function showTimeoutWarningPrompt(timeLeftInSeconds) {
|
264
|
+
this.promptRenderer.renderTimeoutWarning(timeLeftInSeconds);
|
265
|
+
}
|
266
|
+
|
267
|
+
// We need to use the system time rather than the setTimeout function as it
|
268
|
+
// is inherently innacurate.
|
269
|
+
}, {
|
270
|
+
key: "recalculateTimeoutAt",
|
271
|
+
value: function recalculateTimeoutAt() {
|
272
|
+
this.timeoutAt = this.currentTimestamp() + this.sessionTimeoutInSeconds;
|
273
|
+
localStorage.setItem(this.sessionKey, this.timeoutAt);
|
274
|
+
}
|
275
|
+
}, {
|
276
|
+
key: "currentTimestamp",
|
277
|
+
value: function currentTimestamp() {
|
278
|
+
return Math.floor(new Date().getTime() / 1000);
|
279
|
+
}
|
280
|
+
}]);
|
281
|
+
|
282
|
+
return TimeoutTimer;
|
283
|
+
})();
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SessionTimeoutPrompter
|
2
|
+
class SessionsController < ActionController::Base
|
3
|
+
|
4
|
+
# We arrive here when clicking "Log in again"
|
5
|
+
# Note: Due to inherent inaccuracies in javascript timing it is a good idea to
|
6
|
+
# override this in your app and destroy the user session to make sure they are
|
7
|
+
# actually logged out as this may cause confusion.
|
8
|
+
def new
|
9
|
+
redirect_to main_app.root_path
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SessionTimeoutPrompter
|
2
|
+
module PromptHelper
|
3
|
+
|
4
|
+
# session_timeout_in_seconds: The session timeout length set for your app
|
5
|
+
# (most often from User.timeout_in.to_i if using Devise)
|
6
|
+
#
|
7
|
+
# timeout_warning_in_seconds: Show a warning this many seconds before the session times out.
|
8
|
+
#
|
9
|
+
# scope: e.g. :user - most often the name of the Devise scope/model
|
10
|
+
#
|
11
|
+
# session_key: Unique key for this app and scope - used to enable multi-tab support
|
12
|
+
def init_session_timeout_prompter(session_timeout_in_seconds:, timeout_warning_in_seconds:, scope:)
|
13
|
+
render(
|
14
|
+
partial: "session_timeout_prompter/modal_dialogs",
|
15
|
+
locals: {
|
16
|
+
container_data_attributes: {
|
17
|
+
server_ping_path: session_timeout_prompter.server_pings_path,
|
18
|
+
session_timeout_in_seconds: session_timeout_in_seconds,
|
19
|
+
timeout_warning_in_seconds: timeout_warning_in_seconds,
|
20
|
+
session_key: "#{::Rails.application.class.parent_name.downcase}-#{scope}"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def session_timeout_prompter_translate(key)
|
27
|
+
I18n.t("session_timeout_prompter.#{key}")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Don't create a dependency on FontAwesome
|
31
|
+
def session_timeout_prompter_icon(name, text:)
|
32
|
+
if defined?(fa_icon)
|
33
|
+
fa_icon name, text: text
|
34
|
+
else
|
35
|
+
text
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<%= content_tag :div, id: "session-timeout-prompter-container", data: container_data_attributes do %>
|
2
|
+
<div class="session-timeout-prompter-modal modal fade" id="session-timeout-prompter-timeout-warning-modal" data-backdrop="static">
|
3
|
+
<div class="modal-dialog modal-sm">
|
4
|
+
<div class="modal-content">
|
5
|
+
<div class="modal-body text-center">
|
6
|
+
<p>
|
7
|
+
<%= session_timeout_prompter_translate :your_session_will_expire_in %>
|
8
|
+
<br/>
|
9
|
+
<span class="lead" id="session-timeout-prompter-warning-timeout-in">3m 0s</span>
|
10
|
+
</p>
|
11
|
+
<p>
|
12
|
+
<%= session_timeout_prompter_translate :do_you_wish_to_remain_logged_in %>
|
13
|
+
</p>
|
14
|
+
</div>
|
15
|
+
<div class="modal-footer">
|
16
|
+
<button class="btn btn-primary btn-block" id="session-timeout-prompter-remain-logged-in-btn" title="<%= session_timeout_prompter_translate(:remain_logged_in_btn_title) %>">
|
17
|
+
<%= session_timeout_prompter_icon 'check', text: session_timeout_prompter_translate(:remain_logged_in_btn_text) %>
|
18
|
+
</button>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
<div class="session-timeout-prompter-modal modal fade" id="session-timeout-prompter-session-timed-out-modal" data-backdrop="static">
|
24
|
+
<div class="modal-dialog modal-sm">
|
25
|
+
<div class="modal-content">
|
26
|
+
<div class="modal-body text-center">
|
27
|
+
<p>
|
28
|
+
<%= session_timeout_prompter_translate :session_timed_out %>
|
29
|
+
</p>
|
30
|
+
</div>
|
31
|
+
<div class="modal-footer">
|
32
|
+
<%= link_to session_timeout_prompter.new_session_path, title: session_timeout_prompter_translate(:log_in_again_btn_title), class: 'btn btn-primary btn-block' do %>
|
33
|
+
<%= session_timeout_prompter_translate :log_in_again_btn_text %>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "session_timeout_prompter"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/build
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'babel/transpiler'
|
3
|
+
require 'rake'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
# A simple script to transpile our source ES6 in src/javascripts to standard ES5
|
7
|
+
# javascript in app/assets/javascripts/session_timeout_prompter.js
|
8
|
+
|
9
|
+
TARGET_JS_PATH = "#{File.dirname(__FILE__)}/app/assets/javascripts/session_timeout_prompter.js"
|
10
|
+
SRC_DIRECTORY = "#{File.dirname(__FILE__)}/src/javascripts"
|
11
|
+
|
12
|
+
options = {}
|
13
|
+
option_parser = OptionParser.new do |opts|
|
14
|
+
|
15
|
+
opts.banner = "Usage: build [options]"
|
16
|
+
opts.on("-j", "--jasmine", "Run Jasmine tests only") do |v|
|
17
|
+
options[:jasmine_only] = v
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on("-r", "--rspec", "Run RSpec tests only") do |v|
|
21
|
+
options[:rspec_only] = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on("-h", "--help", "Show help") do |v|
|
25
|
+
options[:help] = v
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
option_parser.parse!
|
30
|
+
|
31
|
+
if options[:help]
|
32
|
+
puts option_parser.help()
|
33
|
+
else
|
34
|
+
|
35
|
+
es6_files_to_transpile = Dir.glob("#{SRC_DIRECTORY}/**/*.es6")
|
36
|
+
|
37
|
+
File.open TARGET_JS_PATH, 'w' do |output_file|
|
38
|
+
|
39
|
+
es6_files_to_transpile.each do |es6_file_path|
|
40
|
+
puts "Transpiling #{es6_file_path}..."
|
41
|
+
transpiled_es6 = Babel::Transpiler.transform File.read(es6_file_path)
|
42
|
+
output_file.puts transpiled_es6['code']
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
app = Rake.application
|
48
|
+
app.init
|
49
|
+
app.load_rakefile
|
50
|
+
|
51
|
+
unless options[:rspec_only]
|
52
|
+
puts "Running Jasmine..."
|
53
|
+
app['jasmine:ci'].invoke
|
54
|
+
end
|
55
|
+
|
56
|
+
unless options[:jasmine_only]
|
57
|
+
puts "Running RSpec..."
|
58
|
+
app['spec'].invoke
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
en:
|
2
|
+
session_timeout_prompter:
|
3
|
+
your_session_will_expire_in: "Your session will expire and you will be automatically logged out in"
|
4
|
+
do_you_wish_to_remain_logged_in: "Do you wish to remain logged in?"
|
5
|
+
remain_logged_in_btn_title: "Yes keep me logged in - I am still using the system"
|
6
|
+
remain_logged_in_btn_text: "Yes"
|
7
|
+
session_timed_out: "Your session has expired."
|
8
|
+
log_in_again_btn_title: "Log in again"
|
9
|
+
log_in_again_btn_text: "Log in again"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "session_timeout_prompter/version"
|
2
|
+
|
3
|
+
module SessionTimeoutPrompter
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
isolate_namespace SessionTimeoutPrompter
|
6
|
+
|
7
|
+
initializer "session_timeout_prompter.include_prompt_helper" do |app|
|
8
|
+
ActiveSupport.on_load(:action_controller) do
|
9
|
+
helper SessionTimeoutPrompter::PromptHelper
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "session_timeout_prompter/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "session_timeout_prompter"
|
8
|
+
spec.version = SessionTimeoutPrompter::VERSION
|
9
|
+
spec.authors = ["Ant Nettleship"]
|
10
|
+
spec.email = ["anthony.nettleship@epigenesys.org.uk"]
|
11
|
+
|
12
|
+
spec.summary = "A Rails Engine to prompt the user when their session is about to timeout and allow them to extend it."
|
13
|
+
spec.homepage = "https://github.com/epigenesys/session-timeout-prompter"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features|src)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency 'rails', '>= 4.0'
|
24
|
+
spec.add_dependency 'railties', '>= 4.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", ">= 3.0"
|
29
|
+
spec.add_development_dependency "rspec-rails", ">= 3.0"
|
30
|
+
spec.add_development_dependency "rails", ">= 4.0"
|
31
|
+
spec.add_development_dependency 'jquery-rails', '>= 4.0'
|
32
|
+
spec.add_development_dependency 'bootstrap-sass', '~> 3.3.6'
|
33
|
+
spec.add_development_dependency 'sass-rails', '>= 3.2'
|
34
|
+
spec.add_development_dependency "capybara", ">= 2.4"
|
35
|
+
spec.add_development_dependency 'poltergeist'
|
36
|
+
spec.add_development_dependency 'launchy'
|
37
|
+
spec.add_development_dependency "byebug"
|
38
|
+
spec.add_development_dependency 'sqlite3'
|
39
|
+
spec.add_development_dependency "jasmine"
|
40
|
+
spec.add_development_dependency "babel-transpiler"
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: session_timeout_prompter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ant Nettleship
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.15'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.15'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: jquery-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: bootstrap-sass
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.3.6
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.3.6
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sass-rails
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3.2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3.2'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: capybara
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '2.4'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '2.4'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: poltergeist
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: launchy
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: byebug
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: sqlite3
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: jasmine
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: babel-transpiler
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
description:
|
252
|
+
email:
|
253
|
+
- anthony.nettleship@epigenesys.org.uk
|
254
|
+
executables: []
|
255
|
+
extensions: []
|
256
|
+
extra_rdoc_files: []
|
257
|
+
files:
|
258
|
+
- ".gitignore"
|
259
|
+
- ".rspec"
|
260
|
+
- ".ruby-version"
|
261
|
+
- Gemfile
|
262
|
+
- LICENSE.txt
|
263
|
+
- README.md
|
264
|
+
- Rakefile
|
265
|
+
- app/assets/javascripts/session_timeout_prompter.js
|
266
|
+
- app/assets/stylesheets/session_timeout_prompter.css
|
267
|
+
- app/controllers/session_timeout_prompter/server_pings_controller.rb
|
268
|
+
- app/controllers/session_timeout_prompter/sessions_controller.rb
|
269
|
+
- app/helpers/session_timeout_prompter/prompt_helper.rb
|
270
|
+
- app/views/session_timeout_prompter/_modal_dialogs.html.erb
|
271
|
+
- bin/console
|
272
|
+
- bin/setup
|
273
|
+
- build
|
274
|
+
- config/locales/en.yml
|
275
|
+
- config/routes.rb
|
276
|
+
- lib/session_timeout_prompter.rb
|
277
|
+
- lib/session_timeout_prompter/version.rb
|
278
|
+
- session_timeout_prompter.gemspec
|
279
|
+
homepage: https://github.com/epigenesys/session-timeout-prompter
|
280
|
+
licenses:
|
281
|
+
- MIT
|
282
|
+
metadata: {}
|
283
|
+
post_install_message:
|
284
|
+
rdoc_options: []
|
285
|
+
require_paths:
|
286
|
+
- lib
|
287
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
288
|
+
requirements:
|
289
|
+
- - ">="
|
290
|
+
- !ruby/object:Gem::Version
|
291
|
+
version: '0'
|
292
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
293
|
+
requirements:
|
294
|
+
- - ">="
|
295
|
+
- !ruby/object:Gem::Version
|
296
|
+
version: '0'
|
297
|
+
requirements: []
|
298
|
+
rubyforge_project:
|
299
|
+
rubygems_version: 2.6.13
|
300
|
+
signing_key:
|
301
|
+
specification_version: 4
|
302
|
+
summary: A Rails Engine to prompt the user when their session is about to timeout
|
303
|
+
and allow them to extend it.
|
304
|
+
test_files: []
|