forked from GPII/universal
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLifecycleManager.js
More file actions
1541 lines (1429 loc) · 86.2 KB
/
LifecycleManager.js
File metadata and controls
1541 lines (1429 loc) · 86.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*!
* Lifecycle Manager
*
* Copyright 2012 Antranig Basman
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The research leading to these results has received funding from the European Union's
* Seventh Framework Programme (FP7/2007-2013)
* under grant agreement no. 289016.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = fluid || require("infusion"),
gpii = fluid.registerNamespace("gpii"),
$ = fluid.registerNamespace("jQuery");
(function () {
fluid.defaults("gpii.lifecycleManager", {
gradeNames: ["fluid.modelComponent", "fluid.contextAware"],
retryOptions: { // Options governing how often to recheck whether settings are set (and in future, to check for running processes)
rewriteEvery: 0, // This feature is now disabled except in integration tests, causing settings handler instability via GPII-2522
numRetries: 12, // Make 12 attempts over a period of 12 seconds to discover whether settings are set
retryInterval: 1000
},
debounceTimeMs: 1500, // The debounce time in milliseconds for /proximityTriggered endpoint to reject the following request with the same GPII key
components: {
// Note: The subcomponent "privateMatchMaker" is distributed from flowManager so it doesn't disturb lifecycle manager unit tests.
// TODO: privateMatchMaker will eventually be factored into the UserLogonRequest where key-in matchmaker requests are handled.
// privateMatchMaker: {
// type: "gpii.lifecycleManager.privateMatchMaker"
// },
userSession: {
type: "gpii.lifecycleManager.userSession"
},
restoreSession: {
type: "gpii.lifecycleManager.restoreSession",
options: {
model: {
gpiiKey: "restore"
}
}
},
variableResolver: {
type: "gpii.lifecycleManager.variableResolver"
},
nameResolver: {
type: "gpii.lifecycleManager.nameResolver"
},
userErrors: {
type: "gpii.userErrors"
}
},
dynamicComponents: {
userLogonRequests: {
type: "{arguments}.0",
createOnEvent: "onUserLogonRequestReceived",
options: {
gpiiKey: "{arguments}.1",
requestPromise: "{arguments}.2"
}
}
},
members: {
/* action queue for high-level lifecycle manager tasks (start, stop or update).
* The entries in the action queue are of the format { func: <functionToCall>, arg: <argument> } where
* <functionToCall> is a single-argument function that returns a promise. The promise should be resolved when
* the function is complete (including side-effects).
* A more detailed description: https://github.com/GPII/universal/tree/master/documentation/LifecycleMananger.md
*/
actionQueue: [],
userLogonRequestQueue: []
},
model: {
// Holds the GPII key from the last environmental login
lastEnvironmentalLoginGpiiKey: undefined,
currentUserLogon: { // Holds information about the last non-noUser login/logout action
// for /proximityTriggered endpoint to verify its debounce rule.
type: undefined, // "login"/"logout"
gpiiKey: undefined, // string with GPII key
timeStamp: 0
},
logonChange: { // Holds information about the most recent login/logout action so its progress may be tracked in a modelised way
type: undefined, // "login"/"logout"
inProgress: false, // boolean
gpiiKey: undefined, // string with GPII key
timeStamp: 0
}
},
events: {
onSessionStart: null, // fired with [gradeName, gpiiKey] - notify listeners
onSessionSnapshotUpdate: null, // fired with [{lifecycleManager}, {session}, originalSettings]
onSessionStop: null, // fired with [{lifecycleManager}, {session}]
onAutoSaveRequired: null, // fired with [gpiiKey, {session}.model.preferences]
onClearActionQueue: null,
onUserLogonRequestReceived: null, // fired with [gradeName, reqeustId, gpiiKey, requestPromise]
onQueueEmpty: null,
onClearUserLogonRequestQueue: null
},
listeners: {
"onSessionSnapshotUpdate.log": "gpii.lifecycleManager.logSnapshotUpdate",
"onCreate.createActionFunctions": "gpii.lifecycleManager.createActionQueueFunctions",
"onClearActionQueue.clearQueue": "gpii.lifecycleManager.clearQueue({that}.actionQueue, 'action')",
"onClearUserLogonRequestQueue.clearQueue": "gpii.lifecycleManager.clearQueue({that}.userLogonRequestQueue, 'user logon request')"
},
actionQueueFunctions: {
expander: { // manually expanded via the createActionQueueFunctions function
type: "fluid.noexpand",
value: {
stop: "{that}.processStop",
start: "{that}.processStart",
read: "{that}.processRead",
update: "{that}.processUpdate"
}
}
},
invokers: {
// Handle login, logout and proximityTriggered requests
performLogin: {
funcName: "gpii.lifecycleManager.performLogin",
args: ["{that}", "{arguments}.0", "{arguments}.1"]
// gpiiKey, loginInfo
},
performLogout: {
funcName: "gpii.lifecycleManager.performLogout",
args: ["{that}", "{arguments}.0"]
// gpiiKey
},
performProximityTriggered: {
funcName: "gpii.lifecycleManager.performProximityTriggered",
args: ["{that}", "{arguments}.0"]
// gpiiKey
},
replayEnvironmentalLogin: {
funcName: "gpii.lifecycleManager.replayEnvironmentalLogin",
args: ["{that}", "{that}.model.lastEnvironmentalLoginGpiiKey"]
},
isBounce: {
funcName: "gpii.lifecycleManager.isBounce",
args: ["{that}.model.logonChange", "{that}.model.currentUserLogon", "{that}.options.debounceTimeMs", "{arguments}.0"]
// gpiiKey
},
errorResponse: {
funcName: "gpii.lifecycleManager.errorResponse",
args: ["{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3"]
// requestPromise or requestComponent, message, statusCode, ignoreUserErrors
},
// get the current active GPII key
getActiveSessionGpiiKey: {
funcName: "gpii.lifecycleManager.getActiveSessionGpiiKey",
args: ["{that}.userSession"]
},
addLifecycleInstructionsToPayload: {
funcName: "gpii.lifecycleManager.addLifecycleInstructionsToPayload",
args: ["{arguments}.0"]
// fullPayload
},
updateActivePrefsSetName: {
funcName: "gpii.lifecycleManager.updateActivePrefsSetName",
args: ["{that}", "{arguments}.0", "{arguments}.1"]
// fullPayload, newPrefsSetName
},
prefsSetNameChanged: {
funcName: "gpii.lifecycleManager.prefsSetNameChanged",
args: ["{that}", "{arguments}.0"]
// newPrefsSetName
},
applyPreferences: {
funcName: "gpii.lifecycleManager.applyPreferences",
args: ["{that}", "{arguments}.0", "{arguments}.1"]
// preferences, prefsAppliedLocalEvent
},
readPreferences: {
funcName: "gpii.lifecycleManager.readPreferences",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
// preferences, preferenceReadSuccessEvent, preferenceReadFailEvent
},
// Return lifecycleManager.userSession by default. If a GPII key is provided, return the session of the given key.
getSession: {
funcName: "gpii.lifecycleManager.getSession",
args: ["{that}.userSession", "{that}.restoreSession", "{arguments}.0"]
// GPII key
},
// Handle the action queue
addToActionQueue: {
funcName: "gpii.lifecycleManager.addToActionQueue",
args: ["{that}", "{that}.actionQueue", "{arguments}.0"]
},
processActionQueue: {
funcName: "gpii.lifecycleManager.processActionQueue",
args: ["{that}", "{that}.actionQueue"]
},
// Handle the user logon request queue
addToUserLogonRequestQueue: {
funcName: "gpii.lifecycleManager.addToUserLogonRequestQueue",
args: ["{that}", "{that}.userLogonRequestQueue", "{arguments}.0"]
// queueItem
},
processUserLogonRequestQueue: {
funcName: "gpii.lifecycleManager.processUserLogonRequestQueue",
args: ["{that}", "{that}.userLogonRequestQueue"]
},
endProcessOneUserLogonRequest: {
funcName: "gpii.lifecycleManager.endProcessOneUserLogonRequest",
args: ["{lifecycleManager}"]
// queueItem
},
// start: manually created function which should be called on user login (i.e. configuring the system)
// stop: manually created function which should be called on user logout (i.e. restoring the system)
// read: manually created function which should be called on reading settings of an already configured system
// update: manually created function which should be called on update (i.e. changes in the setting of an already configured system)
processStart: { // should not be used directly, use the manually created 'start' invoker instead
funcName: "gpii.lifecycleManager.processStart",
args: [ "{that}", "{that}.userSession", "{arguments}.0"]
// Use IoC declaration "{that}.userSession" to force the instantiation of "userSession" subcomponent
},
processStop: { // should not be used directly, use the manually created 'stop' invoker instead
funcName: "gpii.lifecycleManager.processStop",
args: [ "{that}", "{arguments}.0"]
},
processRead: { // should not be used directly, use the manually created 'read' invoker instead
funcName: "gpii.lifecycleManager.processRead",
args: ["{that}", "{arguments}.0"]
// solutions
},
processUpdate: { // should not be used directly, use the manually created 'update' invoker instead
funcName: "gpii.lifecycleManager.processUpdate",
args: [ "{that}", "{arguments}.0"]
// finalPayload
},
applySolution: {
funcName: "gpii.lifecycleManager.applySolution",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3", "{arguments}.4"]
// solutionId, solutionRecord, session, lifecycleBlocksKeys, rootAction
},
executeActions: {
funcName: "gpii.lifecycleManager.executeActions",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3", "{arguments}.4"]
// solutionId, settingsHandlers, actions, session, rootAction
},
invokeSettingsHandlerGet: {
funcName: "gpii.lifecycleManager.invokeSettingsHandlerGet",
args: ["{that}", "{arguments}.0", "{arguments}.1"]
// solutionId, settingsHandlers
},
invokeSettingsHandlerSet: {
funcName: "gpii.lifecycleManager.invokeSettingsHandlerSet",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
// solutionId, settingsHandlers, handlerType
},
restoreSnapshot: {
funcName: "gpii.lifecycleManager.restoreSnapshot",
args: ["{that}", "{arguments}.0"]
// originalSettings
},
getSolutionRunningState: {
funcName: "gpii.lifecycleManager.getSolutionRunningState",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
// solutionId, solution, session
}
}
});
fluid.defaults("gpii.test.lifecycleManager.integration", {
// Test this only in integration testing scenarios
retryOptions: {
rewriteEvery: 3
}
});
fluid.contextAware.makeAdaptation({
distributionName: "gpii.test.lifecycleManager.integration.adaptation",
targetName: "gpii.lifecycleManager",
adaptationName: "integrationTest",
checkName: "integrationTest",
record: {
contextValue: "{gpii.contexts.test.integration}",
gradeNames: "gpii.test.lifecycleManager.integration"
}
});
/**
* Function to check whether a proximity trigger should bounce.
* Debounce rule: any RFID actions is ignored for <myGpiiKey> if a login/logout for <myGpiiKey> is in progress
* OR if the last login/logout process for <myGpiiKey> finished less than 1.5 seconds ago
* If no user is logged in and debounce doesn't apply, log in <myGpiiKey>
* If <myGpiiKey> is logged in and debounce doesn't apply, log out <myGpiiKey>
* If another user is already logged in or in the process of logging in or out, log that user out and log in <myGpiiKey>
*
* @param {Component} currentLogonChange - The value of lifecycleManager.model.logonChange.
* @param {Component} currentUserLogon - The value of lifecycleManager.model.currentUserLogon.
* @param {Integer} debounceTimeMs - The time in milliseconds for which to debounce after last proximityTrigger was fired.
* @param {String} gpiiKey - The GPII key of the user for whom the proximityTrigger was fired.
* @return {Boolean} Returns true if the proximityTrigger should be ignored (i.e. is considered a bounce). This is done according
* to the following rule: any RFID actions is ignored for <myGpiiKey> if a login/logout for <myGpiiKey> is in progress
* OR if the last login/logout process for <myGpiiKey> finished less than {debounceTimeMs} milliseconds ago
*/
gpii.lifecycleManager.isBounce = function (currentLogonChange, currentUserLogon, debounceTimeMs, gpiiKey) {
// if login/logout is in progress for <gpiiKey> we consider it a bounce
var now = Date.now();
if (currentLogonChange.gpiiKey === "noUser" && currentLogonChange.type === "login" && !currentLogonChange.inProgress) {
return currentUserLogon.gpiiKey === gpiiKey && now - currentUserLogon.timeStamp < debounceTimeMs;
} else if (gpiiKey === currentLogonChange.gpiiKey) {
return currentLogonChange.inProgress || now - currentLogonChange.timeStamp < debounceTimeMs;
} else {
return false;
}
};
/**
* Function to perform proximityTriggered keyin and keyout.
*
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {String} gpiiKey - The GPII key of the user for whom the keyin or keyout was performed.
* @return {Promise} The promise whose resolved value contains the logon success message and rejected value contains
* the fail message.
*/
gpii.lifecycleManager.performProximityTriggered = function (that, gpiiKey) {
fluid.log("proximity triggered by GPII key: ", gpiiKey);
var promiseTogo = fluid.promise();
// find currently logged in user:
var activeGpiiKey = that.getActiveSessionGpiiKey();
if (that.isBounce(gpiiKey)) {
fluid.log("Proximity trigger ignored due to bounce rules");
that.errorResponse(promiseTogo, "Proximity trigger ignored due to bounce rules. Please wait current logon change is complete", 429);
return promiseTogo;
}
// if the login user is "reset", trigger the reset process via login with "reset" key
if (gpiiKey === "reset") {
promiseTogo = that.addToUserLogonRequestQueue({
gpiiKey: "reset",
logonState: "login"
});
return promiseTogo;
}
// if user is already logged in, log them out. If it's a different user who is logged in
// log that user out before logging in with the new user
if (activeGpiiKey) {
var previousUser = activeGpiiKey;
if (previousUser !== gpiiKey) {
that.addToUserLogonRequestQueue({
gpiiKey: previousUser,
logonState: "logout"
});
promiseTogo = that.addToUserLogonRequestQueue({
gpiiKey: gpiiKey,
logonState: "login"
});
} else {
promiseTogo = that.addToUserLogonRequestQueue({
gpiiKey: previousUser,
logonState: "logout"
});
}
}
return promiseTogo;
};
/**
* Shared function to perform keyin and keyout.
*
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {String} gpiiKey - The GPII key of the user for whom the keyin or keyout was performed.
* @param {Object} loginInfo - [optional] Extra login information
* @param {Boolean} loginInfo.isEnvironmentalLogin - Whether this login is an environmental login. For example,
* logins via USB or NFC are not environmental login while the windows auto logins are environmental login. When
* this argument is not provided, this login is considered as a non-environmental login.
* @return {Promise} The promise whose resolved value contains the logon success message and rejected value contains
* the fail message.
*/
gpii.lifecycleManager.performLogin = function (that, gpiiKey, loginInfo) {
// If the active key is "noUser", log it out before logging in another key
var activeGpiiKey = that.getActiveSessionGpiiKey();
var lastLogonRequest = that.userLogonRequestQueue[that.userLogonRequestQueue.length - 1];
loginInfo = loginInfo || {};
// Add the logout of noUser to the user logon request queue when:
// 1. There is a current active key;
// 2. The last logon request in the queue is to key in this current active key. This case sometimes occurs
// when a key in request is still in progress or is waiting in the logon request queue to be processed.
if (activeGpiiKey || lastLogonRequest && lastLogonRequest.gpiiKey === activeGpiiKey && lastLogonRequest.logonState === "login") {
that.addToUserLogonRequestQueue({
gpiiKey: activeGpiiKey,
logonState: "logout"
});
}
if (loginInfo.isEnvironmentalLogin) {
that.applier.change("lastEnvironmentalLoginGpiiKey", gpiiKey);
}
// Login the requested key
var logonPromise = that.addToUserLogonRequestQueue({
gpiiKey: gpiiKey,
logonState: "login"
});
return logonPromise;
};
/**
* Shared function to perform keyout.
*
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {String} gpiiKey - The GPII key of the user for whom the keyin or keyout was performed.
* @return {Promise} The promise whose resolved value contains the logon success message and rejected value contains
* the fail message.
*/
gpii.lifecycleManager.performLogout = function (that, gpiiKey) {
var logonPromise = that.addToUserLogonRequestQueue({
gpiiKey: gpiiKey,
logonState: "logout"
});
return logonPromise;
};
/**
* To re-apply the last environmental login.
* This function is used by gpii-app to easily trigger a re-login of the last environmental login.
* The replay is only performed when:
* 1. The active GPII key is "noUser", in which case for some reason (e.g. the "reset to standard" function has
* been activated) the user has become logged out.
* 2. The active GPII key is the environmental login key.
*
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {String} lastEnvironmentalLoginGpiiKey - The GPII key of the last environmental login.
* @return {Promise} The promise whose resolved value contains the login success message and rejected value contains
* the fail message.
*/
gpii.lifecycleManager.replayEnvironmentalLogin = function (that, lastEnvironmentalLoginGpiiKey) {
var promiseTogo = fluid.promise();
var activeGpiiKey = that.getActiveSessionGpiiKey();
if (activeGpiiKey === "noUser" || activeGpiiKey === lastEnvironmentalLoginGpiiKey) {
promiseTogo = that.performLogin(lastEnvironmentalLoginGpiiKey, true);
} else {
promiseTogo.reject({
isError: true,
message: "Replay environmental login ignored because the current active GPII key is not \"noUser\" or the last GPII key used for the environmental login"
});
}
return promiseTogo;
};
/**
* This function is used in 2 cases:
* 1. Report an error at the early stage of a logon request processing before a dynamic user logon request component
* is created. At this moment, the report is onto a request promise by calling requestPromise.reject(). An typical
* example is when a proximityTriggered request is bounced. See performProximityTriggered();
* 2. Report an error after a dynamic user logon request component ("gpii.lifecycleManager.loginRequest" or
* "gpii.lifecycleManager.logoutRequest") has been constructed and the error occurs when the request component is processing
* the logon. This error will be reported to the request component via request.events.onError.fire(). The example of this
* type spreads out on the handler grade gpii.lifecycleManager.userLogonHandling.
*
* @param {Promise|Component} togo - When `togo` is a requst promise, its reject() is invoked with the constructed message.
* When `togo` is an instance of "gpii.lifecycleManager.loginRequest" or "gpii.lifecycleManager.logoutRequest", the error is
* reported by firing onError event with the constructed message.
* @param {String} message - The error message to be included in the constructed message.
* @param {Number} statusCode - The status code to be included in the constructed message.
* @param {Boolean} ignoreUserErrors - The flag that indicate whether this error should be reported to users via userErrors.
*/
gpii.lifecycleManager.errorResponse = function (togo, message, statusCode, ignoreUserErrors) {
var error = {
statusCode: statusCode || 500,
message: message || "Unknown error occurred on logon change action",
ignoreUserErrors: ignoreUserErrors || false
};
if (fluid.isPromise(togo)) {
togo.reject(error);
} else {
togo.events.onError.fire(error);
}
};
/**
* Manually constructs the functions in the actionQueueFunctions options.
* This is done manually to avoid resolving the "{that}" part of the function to be called (e.g. "{that}.processStart")
* @param {Component} that - An instance of gpii.lifecycleManager.
*/
gpii.lifecycleManager.createActionQueueFunctions = function (that) {
fluid.each(that.options.actionQueueFunctions, function (queueFunc, invokerName) {
that[invokerName] = function (arg) {
return that.addToActionQueue({
func: queueFunc,
invokerName: invokerName,
arg: arg
});
};
});
};
/**
* Add lifecycle instructions to the payload.
* @param {Object} fullPayload - The payload to add lifecycle instructions.
* @return {Object} - The payload that has lifecycle instructions added.
*/
gpii.lifecycleManager.addLifecycleInstructionsToPayload = function (fullPayload) {
fullPayload.activeConfiguration = {
inferredConfiguration: fluid.extend(true, {}, fullPayload.matchMakerOutput.inferredConfiguration[fullPayload.activePrefsSetName])
};
var lifecycleInstructions = gpii.transformer.configurationToSettings(fullPayload.activeConfiguration.inferredConfiguration, fullPayload.solutionsRegistryEntries);
fluid.set(fullPayload, "activeConfiguration.lifecycleInstructions", lifecycleInstructions);
return fullPayload;
};
/**
* Transform preferences to final payload and apply it to the local computer.
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {Object} preferences - The preferences to be transformed and applied.
* @param {String|Array} prefsAppliedLocalEvent - Fired once the preferences are applied to the local computer.
*/
gpii.lifecycleManager.applyPreferences = function (that, preferences, prefsAppliedLocalEvent) {
fluid.log("Lifecycle Manager: Received updated preferences ", preferences);
// Grab current session
var userSession = that.getSession();
// tweak payload to be in preferences set format and find out if the changed preferences contain new ones
var parsedPrefs = {};
var hasNewPrefs = false;
var previousPrefs = fluid.get(userSession.model.preferences, ["contexts", userSession.model.activePrefsSetName, "preferences"]);
var oldPrefsPaths = gpii.transformer.computeDemandedSettingsPaths(previousPrefs);
fluid.each(preferences, function (spec, pref) {
// Filter out preferences in the pspChannel request that have no value change but were initially recorded
// in "pspChannel.model.settingControls" for reporting default schema values
if (spec.value !== undefined) {
var prefPath = fluid.pathUtil.parseEL(pref);
fluid.set(parsedPrefs, prefPath, spec.value);
if (oldPrefsPaths.indexOf(prefPath[0]) === -1) {
hasNewPrefs = true;
}
}
});
// GPII-3378: 2 routes for applying incoming preferences based on whether they contain new preferences that are not
// part of userSession.model.preferences:
// 1. When there are new preferences, preform a round of local matchmaking and use its result to apply preferences;
// 2. When there aren't new preferences, use the previous matchmaking result from userSession.model to apply preferences.
gpii.lifecycleManager[hasNewPrefs ? "applyWithNewPrefs" : "applyExistingPrefs"](that, userSession, parsedPrefs, prefsAppliedLocalEvent);
};
// This function is called when preferences to be applied contain new preferences that are not in session.model.preferences.
// It performs a round of local matchmaking and uses the matchmaking result to apply all preferences including new ones.
// This is because when GPII runs in the untrusted config, for the security reason, the matchamking results fetched from
// the cloud for the previous preferences only contains the filtered results on solution registry and inferred configurations
// (etc) that are relevant to the previous preferences. This result is not enough for applying new preferences.
gpii.lifecycleManager.applyWithNewPrefs = function (that, session, parsedPrefs, prefsAppliedLocalEvent) {
var newPrefsSet = {};
fluid.set(newPrefsSet, ["contexts", session.model.activePrefsSetName, "preferences"], parsedPrefs);
var updatedPrefSet = fluid.extend({}, session.model.preferences, newPrefsSet);
var activeGpiiKey = that.getActiveSessionGpiiKey();
// Perform a local matchmaking to get matched solutions for existing and new preferences.
var matchMakingPromise = that.privateMatchMaker.doMatch(activeGpiiKey, updatedPrefSet);
matchMakingPromise.then(function (newPayload) {
var fullPayload = fluid.extend(true, {}, session.model);
fluid.set(fullPayload, ["preferences"], updatedPrefSet);
fluid.set(fullPayload, ["matchMakerOutput", "inferredConfiguration"], newPayload.matchMakerOutput.inferredConfiguration);
fluid.set(fullPayload, ["solutionsRegistryEntries"], newPayload.solutionsRegistryEntries);
fluid.set(fullPayload, ["currentPreferences"], gpii.lifecycleManager.getPreferencesForSet(fullPayload.preferences, fullPayload.activePrefsSetName));
that.addLifecycleInstructionsToPayload(fullPayload);
session.applier.change("", fullPayload, "ADD", "userUpdate");
that.update(fullPayload).then(prefsAppliedLocalEvent.fire);
});
};
// Being the counterpart of applyWithNewPrefs(), this function is called when preferences to be applied only contain
// existing preferences that are in session.model.preferences. It uses the existing matchmaking result stored in
// session.model to apply these preferences.
gpii.lifecycleManager.applyExistingPrefs = function (that, session, parsedPrefs, prefsAppliedLocalEvent) {
// Only the user updated preferences will be saved to the cloud, which is identified by firing the model change request
// with source === "userUpdate". Note that changes requests on model.preferences can also be triggered by initial
// key-in or the change of the preferences set. The preferences change from these actions should not be saved to the cloud.
session.applier.change(["preferences", "contexts", session.model.activePrefsSetName, "preferences"], parsedPrefs, "ADD", "userUpdate");
// get new inferred configuration to be applied:
var newInferred = gpii.matchMakerFramework.utils.updateInferredConfiguration(session.model.preferences, session.model.matchMakerOutput.inferredConfiguration, session.model.solutionsRegistryEntries);
// full payload from session:
var fullPayload = fluid.extend(true, {}, session.model);
fluid.set(fullPayload, ["matchMakerOutput", "inferredConfiguration"], newInferred);
that.addLifecycleInstructionsToPayload(fullPayload);
session.applier.change("", {
activeConfiguration: fullPayload.activeConfiguration,
currentPreferences: gpii.lifecycleManager.getPreferencesForSet(fullPayload.preferences, fullPayload.activePrefsSetName)
});
that.update(fullPayload).then(prefsAppliedLocalEvent.fire);
};
/**
* Read corresponding setting values of the requested preferences. The read result is applied as
* a model change to userSession.model.currentPreferences.
* @param {Component} that - An instance of gpii.lifecycleManager.
* @param {Object} preferences - The preferences to read settings for. An example is:
* "http://registry\\.gpii\\.net/common/magnification": {
* value: 3
* }
* @param {Event} preferenceReadSuccessEvent - The event fired when the read succeeds.
* @param {Event} preferenceReadFailEvent - The event fired when the read fails.
*/
gpii.lifecycleManager.readPreferences = function (that, preferences, preferenceReadSuccessEvent, preferenceReadFailEvent) {
fluid.log("Lifecycle Manager: Received preferences to read ", preferences);
var session = that.getSession();
// Mock values for running preferences thru matchMaker
var parsedPreferences = {};
var activePrefsSetName = "gpii-default";
fluid.each(preferences, function (spec, pref) {
var prefPath = fluid.pathUtil.parseEL(pref);
fluid.set(parsedPreferences, prefPath, spec.value);
});
var onePrefsSet = {};
fluid.set(onePrefsSet, ["contexts", activePrefsSetName, "preferences"], parsedPreferences);
// Perform a local matchmaking to get matched solutions for preferences.
var matchMakingPromise = that.privateMatchMaker.doMatch("readSetting", onePrefsSet);
matchMakingPromise.then(function (payload) {
fluid.set(payload, ["activePrefsSetName"], activePrefsSetName);
fluid.set(payload, ["preferences"], onePrefsSet);
that.addLifecycleInstructionsToPayload(payload);
// compile a list of solution registry entries for all matched solutoins
var matchMakerSolutionRegistryEntries = fluid.get(payload, ["activeConfiguration", "lifecycleInstructions"]);
var settingsPromise = that.read(matchMakerSolutionRegistryEntries);
settingsPromise.then(function (allSettingValues) {
// As lifecycleManager.read() returns values for all settings that matched solutions support,
// only filter out setting values that are in the matchmaker result.
var matchMakerSettings = gpii.lifecycleManager.getMatchMakerSettings(allSettingValues, matchMakerSolutionRegistryEntries);
// Transform settings to preferences
var transformed = gpii.lifecycleManager.transformSettingsToPrefs(matchMakerSettings, matchMakerSolutionRegistryEntries);
// Filter matched preferences
var matchedPrefs = gpii.lifecycleManager.filterMatchedPrefs(transformed, parsedPreferences);
// Use cases discovered that will result in "matchedPrefs" being an empty object include:
// 1. Settings handler that have get() defined but not implemented - https://issues.gpii.net/browse/GPII-4217
// 2. "inverseCapabilitiesTransformations" blocks for one or more settings are not defined. In this case,
// although setting values are read successfully, when these values are transformed into preference values via
// gpii.lifecycleManager.transformSettingsToPrefs(), an empty object will return - https://issues.gpii.net/browse/GPII-4216
if ($.isEmptyObject(matchedPrefs)) {
preferenceReadFailEvent.fire();
} else {
session.applier.change(["currentPreferences"], fluid.extend(true, fluid.copy(session.model.currentPreferences), matchedPrefs));
preferenceReadSuccessEvent.fire();
}
}, preferenceReadFailEvent.fire);
});
};
/*
* Get preferences block for a certain context.
* @param {Object} prefsSets - An object containing one or more preferences sets.
* @param {String} prefsSetName - A name of a preferences set.
* @return {Object} The actual preferences for the active context.
*/
gpii.lifecycleManager.getPreferencesForSet = function (prefsSets, prefsSetName) {
var prefs = fluid.get(prefsSets, ["contexts", prefsSetName, "preferences"]);
return prefs || {};
};
/**
* Return the user's GPII key that is associated with the active session.
* @param {Component} userSession - An instance of gpii.lifecycleManager.userSession.
* @return {String} The GPII key of the active session.
*/
gpii.lifecycleManager.getActiveSessionGpiiKey = function (userSession) {
return userSession.model.gpiiKey;
};
/**
* Return the active session. The logic is:
* 1. If a GPII key is not provided, return the instance of gpii.lifecycleManager.userSession;
* 2. If a GPII key is provided:
* 2.1 The given key is "restore", return the instance of gpii.lifecycleManager.restoreSession;
* 2.2 The given key is NOT "restore", checks if gpii.lifecycleManager.userSession is handling the given GPII key.
* If yes, return gpii.lifecycleManager.userSession. Otherwise, return undefined.
* @param {Component} userSession - An instance of gpii.lifecycleManager.userSession.
* @param {Component} restoreSession - An instance of gpii.lifecycleManager.restoreSession.
* @param {String} [gpiiKey] - [optional] A GPII key. If provided, return the matched session for the given GPII
* key. If no matched session is found, return undefined.
* @return {Component} Could be an instance of gpii.lifecycleManager.userSession or an instance of
* gpii.lifecycleManager.restoreSession or undefined.
*/
gpii.lifecycleManager.getSession = function (userSession, restoreSession, gpiiKey) {
return gpiiKey ? userSession.model.gpiiKey === gpiiKey ? userSession :
gpiiKey === "restore" ? restoreSession : undefined
: userSession;
};
/* Transforms the handlerSpec (handler part of the transformer's response payload) to a form
* accepted by a settingsHandler - we use a 1-element array holding the payload for a single solution
* per handler
*/
gpii.lifecycleManager.specToSettingsHandler = function (solutionId, handlerSpec) {
var returnObj = {},
settings = {};
if (handlerSpec.supportedSettings === undefined) {
// if supportedSettings directive is not present, pass all settings:
settings = handlerSpec.settings;
} else {
// we cant simply use fluid.filterKeys because that wont handle the cases where
// there are 'undefined' values for the keys in handlerSpec.settings
// TODO: Kaspar believes that the reason for filtering happening here rather than in the MatchMaker is
// that the transformation of common terms into application specific settings doesn't occur until the
// transformation stage - so we don't have the full list of app-specific settings to filter until now.
for (var settingName in handlerSpec.supportedSettings) {
if (handlerSpec.settings && settingName in handlerSpec.settings) {
settings[settingName] = handlerSpec.settings[settingName];
}
}
}
returnObj[solutionId] = [{
settings: settings,
options: handlerSpec.options
}];
// GPII-3119 Optionally include supportedSettings if there are any
if (handlerSpec.supportedSettings !== undefined) {
returnObj[solutionId][0].supportedSettings = handlerSpec.supportedSettings;
}
return returnObj;
};
// Transform the response from the handler SET to a format that we can persist in models before passing to handler SET on restore
// - "oldValue" becomes {type: "ADD", value: <oldValue>}
// - `undefined` value becomes {type: "DELETE"}
gpii.lifecycleManager.responseToSnapshot = function (solutionId, handlerResponse) {
var unValued = gpii.settingsHandlers.setResponseToSnapshot(handlerResponse);
var armoured = gpii.settingsHandlers.settingsPayloadToChanges(unValued);
// Note - we deal in these 1-element arrays just for simplicity in the LifecycleManager. A more efficient
// implementation might send settings for multiple solutions to the same settingsHandler in a single request.
// Note that in the session's snapshots, this level of array containment has been removed.
return fluid.get(armoured, [solutionId, 0]);
};
gpii.lifecycleManager.invokeSettingsHandlerGet = function (that, solutionId, handlerSpec) {
// first prepare the payload for the settingsHandler in question - a more efficient
// implementation might bulk together payloads destined for the same handler
var settingsHandlerPayload = gpii.lifecycleManager.specToSettingsHandler(solutionId, handlerSpec);
var resolvedName = that.nameResolver.resolveName(handlerSpec.type, "settingsHandler");
return gpii.settingsHandlers.dispatchSettingsHandlerGet(resolvedName, settingsHandlerPayload);
};
/**
* @param {Component} that - The gpii.lifecycleManager
* @param {String} solutionId - The id of the solution for which the settings handler should be invoked
* @param {Object} handlerSpec - A single settings handler specification.
* @param {String} handlerType - The name of the handler block (i.e. "settings" or "launchers")
* @return {Promise} - A promise that will be resolved with a settings snapshot.
* Payload example:
* http://wiki.gpii.net/index.php/Settings_Handler_Payload_Examples
* Transformer output:
* http://wiki.gpii.net/index.php/Transformer_Payload_Examples
*/
gpii.lifecycleManager.invokeSettingsHandlerSet = function (that, solutionId, handlerSpec, handlerType) {
// first prepare the payload for the settingsHandler in question - a more efficient
// implementation might bulk together payloads destined for the same handler
var settingsHandlerPayload = gpii.lifecycleManager.specToSettingsHandler(solutionId, handlerSpec);
var resolvedName = that.nameResolver.resolveName(handlerSpec.type, "settingsHandler");
var setSettingsPromise = gpii.settingsHandlers.dispatchSettingsHandlerSet(resolvedName, settingsHandlerPayload, that.options.retryOptions);
var togo = fluid.promise();
var setSettingsFail = function (error) {
togo.reject(error);
var userMessageKey = handlerType === "launchHandlers" ?
handlerSpec.settings.running ? "StartApplicationFail" : "StopApplicationFail"
: "WriteSettingFail";
that.userErrors.events.userError.fire({
isError: true,
messageKey: userMessageKey,
originalError: error
});
};
setSettingsPromise.then(function (handlerResponse) {
// update the settings section of our snapshot to contain the new information
var settingsSnapshot = gpii.lifecycleManager.responseToSnapshot(solutionId, handlerResponse);
// Settings handlers may or may not return options (currently it seems they all do) - gain resistance to this by restoring the
// original "options" supplied to them.
fluid.each(handlerSpec, function (entry, key) {
if (key !== "settings") {
settingsSnapshot[key] = fluid.copy(entry);
}
});
togo.resolve(settingsSnapshot);
}, setSettingsFail);
return togo;
};
gpii.lifecycleManager.invokeAction = function (action, nameResolver) {
var resolvedName = nameResolver.resolveName(action.type, "action");
return fluid.invokeGradedFunction(resolvedName, action);
};
/* Compensate for the effect of simpleminded merging when applied to a snapshot where an "DELETE" is merged on top of an "ADD" */
gpii.lifecycleManager.cleanDeletes = function (value) {
if (value.type === "DELETE") {
delete value.value;
}
return value;
};
/* Remove all the settings blocks from a solutions registry entry */
gpii.lifecycleManager.removeSettingsBlocks = function (solutionEntry) {
fluid.each(["settingsHandlers", "launchHandlers"], function (handlersBlock) {
solutionEntry[handlersBlock] = fluid.transform(solutionEntry[handlersBlock], function (handler) {
return fluid.filterKeys(handler, "settings", true);
});
});
return solutionEntry;
};
/* Applies snapshotted settings from a single settingsHandler block attached to a single solution into the "originalSettings"
* model snapshot area in the LifecycleManager's session. Tightly bound to executeSettingsAction, executes one-to-one with it
* with almost identical argument list.
*/
gpii.lifecycleManager.recordSnapshotInSession = function (that, snapshot, solutionId, solutionRecord, session, handlerType, settingsHandlerBlockName, rootAction) {
if (rootAction === "start" || rootAction === "update") {
var toSnapshot = gpii.lifecycleManager.removeSettingsBlocks(fluid.copy(solutionRecord));
// do not remove the settingshandlers blocks, since we need them when restoring the system.
// This is particularly relevant for launch handlers, where we will need to run the "get" directives on logout to decide
// whether we an application is running or not, and consequently, whether to run the "update" or "stop" block
toSnapshot[handlerType][settingsHandlerBlockName] = snapshot;
// keep the settings that are already stored from the
// original snapshot, but augment it with any settings from the new snapshot
// that were not present in the original snapshot.
//
// This is relevant when doing an update for obvious reasons
// (and tested) in LifecycleManagerTests.js "Updating with normal reference to settingsHandler block, and 'undefined' value stored in snapshot"
//
// It is also relevant for logins ("start" root action), in case a solution is already running.
// This would trigger a call to its "update" block. If that in turn eg. looks like the following
// [ "stop", "configure", "start" ] we would want the original state recorded during the "stop"
// action to persist - even when the "start" block is later run
var mergedSettings = fluid.extend(true, {}, toSnapshot, session.model.originalSettings[solutionId]);
var cleanedSettings = gpii.lifecycleManager.transformSolutionSettings(mergedSettings, gpii.lifecycleManager.cleanDeletes);
session.applier.change(["originalSettings", solutionId], cleanedSettings);
} else if (rootAction === "restore" || rootAction === "stop") {
// no-op - during a restore action we don't attempt to create a further snapshot
} else {
fluid.fail("Unrecognised rootAction " + rootAction);
}
};
/* In the case we are servicing a "restore" rootAction, wrap "dangerous" promises which perform system actions so
* that they do not reject, to ensure that we continue to try to restore the system come what may - GPII-2160
*/
gpii.lifecycleManager.wrapRestorePromise = function (promise, rootAction) {
return rootAction === "restore" && fluid.isPromise(promise) ?
gpii.rejectToLog(promise, " while restoring journal snapshot") : promise;
};
/**
* @param {Object} that - The lifecycle manager component.
* @param {String} solutionId - The ID of the solution for which to execute the settings.
* @param {Object} solutionRecord - The solution registry entry for the solution.
* @param {Component} session - The current session component. This function will attach the
* solution record to the 'currentSettings' of the session's model (if successful).
* @param {String} handlerType - The name of the handler block (i.e. "settings" or "launchers").
* @param {String} settingsHandlerBlockName - Should be a reference to a settings block from the
* settingsHandlers section.
* @param {String} rootAction - The root action on the LifecycleManager which is being serviced: "start", "stop",
* "update", "restore" or "isRunning".
* @return {Function} A nullary function (a task), that once executed will set the settings returning a promise
* that will be resolved once the settings are successfully set.
*/
gpii.lifecycleManager.executeSettingsAction = function (that, solutionId, solutionRecord, session, handlerType, settingsHandlerBlockName, rootAction) {
var settingsHandlerBlock = solutionRecord[handlerType][settingsHandlerBlockName];
if (settingsHandlerBlock === undefined) {
fluid.fail("Reference to non-existing settingsHandler block named " + settingsHandlerBlockName +
" in solution " + solutionId);
}
return function () {
var expanded = session.localResolver(settingsHandlerBlock);
if (rootAction === "isRunning") { // only run get and return directly if where checking for running applications
return that.invokeSettingsHandlerGet(solutionId, expanded);
} else {
var settingsPromise = that.invokeSettingsHandlerSet(solutionId, expanded, handlerType);
settingsPromise.then(function (snapshot) {
session.applier.change(["currentSettings", solutionId], solutionRecord);
gpii.lifecycleManager.recordSnapshotInSession(that, snapshot, solutionId, solutionRecord, session,
handlerType, settingsHandlerBlockName, rootAction);
});
return gpii.lifecycleManager.wrapRestorePromise(settingsPromise, rootAction);
}
};
};
/** For the complete entry for a single solution, transform each settingsHandler block by a supplied function - traditionally
* either gpii.settingsHandlers.changesToSettings or gpii.settingsHandlers.settingsToChanges .
* This is traditionally called during the "stop" action to unarmour all the settingsHandler blocks by converting from changes
* back to settings. In a future version of the SettingsHandler API, this will not be necessary.
* This is called during the "stop" action to convert the snapshotted "originalSettings" model material back to material
* suitable for being sent to executeSettingsAction, as well as at the corresponding point during the "journal restore"
* operation
* @param {Object} solutionSettings - A settings block for a single solution, holding a member named `settingsHandlers`.
* @param {Function} transformer - A function which will transform one settingsHandlers block in the supplied `solutionSettings`.
* @return {Promise} A promise that will be resolved with the results of the transformation or rejected on error.
*/
gpii.lifecycleManager.transformSolutionSettings = function (solutionSettings, transformer) {
var togo = fluid.copy(solutionSettings); // safe since armoured
fluid.each(["settingsHandlers", "launchHandlers"], function (handlersBlock) {
togo[handlersBlock] = fluid.transform(solutionSettings[handlersBlock], function (handler) {
return gpii.settingsHandlers.transformOneSolutionSettings(handler, transformer);
});
// avoid the handlerBlock to be `undefined` if it's not defined in solutionsRegistry entry. This is necessary for testing assertion purposes
if (togo[handlersBlock] === undefined) {
delete togo[handlersBlock];
}
});
return togo;
};
// TODO: THIS IS CALLED ONLY FROM TEST CASES!
/* As for gpii.lifecycleManager.transformSolutionSettings, only transforms the complete collection of stored solutions
* (e.g. a value like session.model.originalSettings)
*/
gpii.lifecycleManager.transformAllSolutionSettings = function (allSettings, transformer) {
return fluid.transform(allSettings, function (solutionSettings) {
return gpii.lifecycleManager.transformSolutionSettings(solutionSettings, transformer);
});
};
/** Upgrades a promise rejection payload (or Error) by suffixing an additional "while" reason into its `message` field. If the
* payload is already an Error, its `message` field will be updated in place, otherwise a shallow clone of the original payload
* will be taken to perform the update.
* @param {Object|Error} originError - A rejection payload. This should (at least) have the member `isError: true` set, as well
* as a String `message` holding a rejection reason.
* @param {String} whileMsg - A message describing the activity which led to this error.
* @return {Object} The rejected payload formed by shallow cloning the supplied argument (if it is not an `Error`) and
* suffixing its `message` member.
*/
// TODO: Duplicate of kettle.upgradeError to avoid dependence on Kettle in this file. This needs to go into a new module once
// Kettle is factored up.
gpii.upgradeError = function (originError, whileMsg) {
var error = originError instanceof Error ? originError : fluid.extend({}, originError);
error.message = originError.message + whileMsg;
return error;
};
/** Transform a promise into one that always resolves, by intercepting its reject action and converting it to a logging action
* plus a resolve with an optionally supplied value.
* The error payload's message will be logged to `fluid.log` with the priority `fluid.logLevel.WARN`.
* @param {Promise} promise - The promise to be transformed.
* @param {String} whileMsg - A suffix to be applied to the message, by the action of the utility `gpii.upgradeError`.
* This will typically begin with the text " while".
* @param {Any} [resolveValue] - [optional] An optional value to be supplied to `resolve` of the returned promise, when the
* underlying promise rejects.
* @return {Promise} The wrapped promise which will resolve whether the supplied promise resolves or rejects.
*/
// TODO: In theory this is a fairly generic promise algorithm, but in practice there are some further subtleties - for
// example, a better system would instead replace fluid.promise.sequence with a version that allowed some form of
// "soft rejection" so that there might be a chance to signal failures to the user.
gpii.rejectToLog = function (promise, whileMsg, resolveValue) {
var togo = fluid.promise();
promise.then(function (value) {
togo.resolve(value);
}, function (error) {
gpii.upgradeError(error, whileMsg);
fluid.log(fluid.logLevel.WARN, error.message);
fluid.log(fluid.logLevel.WARN, error.stack);
togo.resolve(resolveValue);
});
return togo;
};
/*
* Infer an action block such as "start", "stop", "configure", etc.
*
* If some lifecycle block is not present, the system will use default actions for each block. These are as follows:
* * "start", "stop" and "isRunning": will by default be references to all launch handler blocks
* * "configure" and "restore": will by default be references to all settings handler blocks
* * "update": depends on the solutions "liveness" value where "live" means set settings only and