Skip to content

Commit 4c4a3c1

Browse files
committed
Add support for system use notification
1 parent 19808ce commit 4c4a3c1

3 files changed

Lines changed: 150 additions & 75 deletions

File tree

README.rst

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,65 @@ Before all examples, you need:
2828
Global API
2929
~~~~~~~~~~
3030

31-
studio.api.AuthRequest(userID, password)
32-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31+
studio.api.Request(systemName, applicationName, cdpVersion, systemUseNotification)
32+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3333

3434
- Arguments
3535

36-
userID - User id to authenticate.
36+
systemName - System name the application belongs to.
3737

38-
password - Password to use for login.
38+
applicationName - Application name.
39+
40+
cdpVersion - CDP version the application is built with.
41+
42+
systemUseNotification - System use notification message to ask for confirmation to continue.
43+
44+
- Returns
45+
46+
The created Request object.
47+
48+
Instance Methods / Request
49+
~~~~~~~~~~~~~~~~~~~~~~~~~~
50+
51+
request.systemName()
52+
^^^^^^^^^^^^^^^^^^^^
53+
54+
- Returns
55+
56+
System name the application belongs to.
57+
58+
request.applicationName()
59+
^^^^^^^^^^^^^^^^^^^^^^^^^
60+
61+
- Returns
62+
63+
Application name.
64+
65+
request.cdpVersion()
66+
^^^^^^^^^^^^^^^^^^^^
67+
68+
- Returns
69+
70+
CDP version the application is built with.
71+
72+
request.systemUseNotification()
73+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3974

4075
- Returns
4176

42-
The created AuthRequest object with provided login data.
77+
System use notification message to ask for confirmation to continue.
4378

4479

45-
studio.api.Client(uri)
46-
^^^^^^^^^^^^^^^^^^^^^^^
80+
studio.api.Client(uri, notificationListener)
81+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4782

4883
- Arguments
4984

5085
uri - String containing the address and port of StudioAPI server separated by colon character
5186

52-
authenticate - Function authenticate(lastAttemptMessage) returning a Promise.<AuthRequest> containing userID and password for authentication
87+
notificationListener - Object returning two functions: applicationAcceptanceRequested(studio.api.Request) and credentialsRequested(studio.api.Request).
88+
Function applicationAcceptanceRequested must return a Promise of void. Can be used to popup system use notification message and ask for confirmation to continue.
89+
Function credentialsRequested must return a Promise of dictionary containing 'Username' and 'Password' as keys for authentication.
5390

5491
- Returns
5592

@@ -70,15 +107,24 @@ studio.api.Client(uri)
70107
71108
.. code:: javascript
72109
73-
// Create client with authentication connected to uri provided in browser address bar.
74-
// The authenticator is only called when page requires a login.
75-
var authenticator = function(loginMessage) {
110+
// Create client with NotificationListener connected to uri provided in browser address bar.
111+
// The NotificationListener is only called when page requires a login.
112+
113+
class NotificationListener {
114+
applicationAcceptanceRequested(request) {
76115
return new Promise(function(resolve, reject) {
77-
//Do something to get username and password variables
78-
resolve(new studio.api.AuthRequest(username, password));
116+
window.confirm(request.systemUseNotification()) ? resolve() : reject();
79117
});
80-
};
81-
var client = new studio.api.Client(window.location.host, authenticator);
118+
}
119+
120+
credentialsRequested(request) {
121+
return new Promise(function(resolve, reject) {
122+
resolve({Username: "cdpuser", Password: "cdpuser"});
123+
});
124+
}
125+
}
126+
127+
var client = new studio.api.Client(window.location.host, new NotificationListener());
82128
83129
84130
Instance Methods / Client

index.js

Lines changed: 69 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import protobuf from 'protobufjs'
1+
import protobuf, { convertFieldsToCamelCase } from 'protobufjs'
22

33
/**
44
* The studio namespace.
@@ -140,35 +140,48 @@ studio.protocol = (function(ProtoBuf) {
140140
}.bind(this);
141141
}
142142

143-
function AuthHandler(socket, challenge, authenticate, onContainer, onError){
143+
function AuthHandler(socket, hello, notificationListener, onContainer, onError){
144144
this.name = "AuthResponse";
145+
let cdpVersion = hello.cdp_version_major + '.' + hello.cdp_version_minor + '.' + hello.cdp_version_patch
145146

146-
this.sendAuthRequest = function(lastAttemptErrorMessage = ""){
147-
authenticate(lastAttemptErrorMessage).then(function(authRequest){
148-
var credentials = new TextEncoder().encode(authRequest.userID.toLowerCase() + ':' + authRequest.password); // encode to utf-8 byte array
149-
let authReq = new obj.AuthRequest();
150-
authReq.user_id = authRequest.userID.toLowerCase();
151-
var colon = new Int8Array([':'.charCodeAt(0)]);
152-
crypto.subtle.digest('SHA-256', credentials.buffer)
153-
.then(function(digest) {
154-
let buffer = obj.appendBuffer(obj.appendBuffer(challenge, colon.buffer), digest);
155-
crypto.subtle.digest('SHA-256', buffer)
156-
.then(function(challenge_digest) {
157-
let response = new obj.AuthRequestChallengeResponse();
158-
response.type = "PasswordHash";
159-
response.response = new Uint8Array(challenge_digest);
160-
authReq.challenge_response = new Array();
161-
authReq.challenge_response.push(response);
162-
socket.send(authReq.toArrayBuffer());
163-
})
164-
.catch(function(err) {
165-
console.error(err);
166-
});
167-
})
168-
.catch(function(err) {
169-
console.error(err);
170-
});
171-
}).catch(function(){ console.log("Authentication cancelled") });
147+
this.sendAuthRequest = function(){
148+
let request = new studio.api.Request(hello.system_name, hello.application_name, cdpVersion, hello.system_use_notification);
149+
let authReq = new obj.AuthRequest();
150+
let applicationAccepted = {}
151+
152+
if (notificationListener && notificationListener.applicationAcceptanceRequested)
153+
applicationAccepted = notificationListener.applicationAcceptanceRequested(request);
154+
else
155+
applicationAccepted = new Promise(function(resolve, reject) { window.confirm(hello.system_use_notification) ? resolve() : reject(); });
156+
157+
applicationAccepted
158+
.then(function(){
159+
return notificationListener.credentialsRequested(request)
160+
})
161+
.then(function(dict){
162+
let username = dict.Username;
163+
let password = dict.Password;
164+
var credentials = new TextEncoder().encode(username.toLowerCase() + ':' + password); // encode to utf-8 byte array
165+
authReq.user_id = username.toLowerCase();
166+
return crypto.subtle.digest('SHA-256', credentials.buffer)
167+
})
168+
.then(function(digest) {
169+
let challenge = hello.challenge.buffer.slice(hello.challenge.offset,hello.challenge.limit);
170+
var colon = new Int8Array([':'.charCodeAt(0)]);
171+
let buffer = obj.appendBuffer(obj.appendBuffer(challenge, colon.buffer), digest);
172+
return crypto.subtle.digest('SHA-256', buffer)
173+
})
174+
.then(function(challenge_digest) {
175+
let response = new obj.AuthRequestChallengeResponse();
176+
response.type = "PasswordHash";
177+
response.response = new Uint8Array(challenge_digest);
178+
authReq.challenge_response = new Array();
179+
authReq.challenge_response.push(response);
180+
socket.send(authReq.toArrayBuffer());
181+
})
182+
.catch(function(){
183+
console.log("Authentication cancelled.")
184+
});
172185
}.bind(this);
173186

174187
this.handle = function(message){
@@ -189,13 +202,13 @@ studio.protocol = (function(ProtoBuf) {
189202
} else {
190203
console.log("Auth msg:" + authResponse.result_text + "\n");
191204
console.log("Unable to login with existing user, password.\n");
192-
this.sendAuthRequest(authResponse.result_text);
205+
this.sendAuthRequest(/*authResponse.result_text*/);
193206
return this;
194207
}
195208
}.bind(this);
196209
}
197210

198-
function HelloHandler(socket, authenticate, onContainer, onError){
211+
function HelloHandler(socket, notificationListener, onContainer, onError){
199212
this.name = "Hello";
200213
this.handle = function(message){
201214
try {
@@ -207,14 +220,14 @@ studio.protocol = (function(ProtoBuf) {
207220
}
208221

209222
if (hello.challenge) {
210-
console.log(hello.challenge.buffer.slice(hello.challenge.offset,hello.challenge.limit));
211-
if (!authenticate)
223+
224+
if (!notificationListener || !notificationListener.credentialsRequested)
212225
{
213-
console.log("No authenticate callback provided to studio.api.Client constructor. Can't authenticate connection!");
226+
console.log("No notificationListener.credentialsRequested callback provided to studio.api.Client constructor. Can't authenticate connection!");
214227
return new ErrorHandler();
215228
}
216229

217-
let authHandler = new AuthHandler(socket, hello.challenge.buffer.slice(hello.challenge.offset,hello.challenge.limit), authenticate, onContainer, onError);
230+
let authHandler = new AuthHandler(socket, hello, notificationListener, onContainer, onError);
218231
authHandler.sendAuthRequest();
219232
return authHandler;
220233
} else {
@@ -226,12 +239,12 @@ studio.protocol = (function(ProtoBuf) {
226239
};
227240
}
228241

229-
obj.Handler = function(socket, authenticate) {
242+
obj.Handler = function(socket, notificationListener) {
230243
this.onContainer = undefined;
231244
this.onError = undefined;
232245
var onContainer = function(container) {(this.onContainer && this.onContainer(container));}.bind(this);
233246
var onError = function(){(this.onError && this.onError());}.bind(this);
234-
var handler = new HelloHandler(socket, authenticate, onContainer, onError);
247+
var handler = new HelloHandler(socket, notificationListener, onContainer, onError);
235248
this.handle = function(message){
236249
handler = handler.handle(message);
237250
};
@@ -447,13 +460,13 @@ studio.internal = (function(proto) {
447460
};
448461
}
449462

450-
obj.AppConnection = function(url, authenticate) {
463+
obj.AppConnection = function(url, notificationListener) {
451464
var appConnection = this;
452465
var appName = "";
453466
var appId = undefined;
454467
var appUrl = (location.protocol=="https:" ? proto.WSS_PREFIX : proto.WS_PREFIX) + url;
455468
var socket = new WebSocket(appUrl);
456-
var handler = new proto.Handler(socket, authenticate);
469+
var handler = new proto.Handler(socket, notificationListener);
457470
var requests = [];
458471
var nodeMap = new Map();
459472
var systemNode = new AppNode(appConnection, proto.SYSTEM_NODE_ID);
@@ -521,7 +534,7 @@ studio.internal = (function(proto) {
521534
setTimeout(function () {
522535
console.log("REconnect timer");
523536
socket = new WebSocket(appUrl);
524-
handler = new proto.Handler(socket, authenticate);
537+
handler = new proto.Handler(socket, notificationListener);
525538
handler.onContainer = handleIncomingContainer;
526539
socket.binaryType = proto.BINARY_TYPE;
527540
socket.onopen = onOpen;
@@ -946,33 +959,32 @@ studio.api = (function(internal) {
946959

947960
obj.structure = internal.structure;
948961

949-
/**
950-
* Creates an instance of AuthRequest
951-
*
952-
* @param userID String user id to authenticate
953-
* @param password String password to use
954-
*
955-
* @this AuthRequest
956-
* @constructor
957-
*/
958-
obj.AuthRequest = function(userID, password) {
959-
var obj = {}
960-
obj.userID = userID;
961-
obj.password = password;
962-
return obj;
962+
obj.Request = function(systemName, applicationName, cdpVersion, systemUseNotification) {
963+
this.systemName = function() {
964+
return systemName;
965+
}
966+
this.applicationName = function() {
967+
return applicationName;
968+
}
969+
this.cdpVersion = function() {
970+
return cdpVersion;
971+
}
972+
this.systemUseNotification = function() {
973+
return systemUseNotification;
974+
}
963975
}
964976

965977
/**
966978
* Creates an instance of Client
967979
*
968980
* @param studioURL String containing the address and port of StudioAPI server separated by colon character
969-
* @param authenticate Function authenticate(lastAttemptMessage) returning a {Promise.<AuthRequest>} containing userID and password for authentication
981+
* @param notificationListener Object returning two functions: applicationAcceptanceRequested(AuthRequest) and credentialsRequested(AuthRequest). Function credentialsRequested must return a Promise of dictionary containing 'Username' and 'Password' as keys for authentication.
970982
*
971983
* @this Client
972984
* @constructor
973985
*/
974-
obj.Client = function(studioURL, authenticate) {
975-
var appConnection = new internal.AppConnection(studioURL, authenticate);
986+
obj.Client = function(studioURL, notificationListener) {
987+
var appConnection = new internal.AppConnection(studioURL, notificationListener);
976988

977989
/**
978990
* Request root node.
@@ -1038,3 +1050,4 @@ studio.api = (function(internal) {
10381050
})(studio.internal);
10391051

10401052
export default studio
1053+

studioapi.proto

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// This file describes the StudioAPI wire protocol. It can be compiled with
22
// the Google Protobuf protoc compiler into native C++, Java, Python etc.
33

4-
// package StudioAPI.Proto;
4+
syntax = "proto2";
55

6-
// option optimize_for = LITE_RUNTIME;
7-
// option java_package = "no.icd.studioapi.proto";
6+
package StudioAPI.Proto;
7+
8+
option optimize_for = LITE_RUNTIME;
9+
option java_package = "no.icd.studioapi.proto";
810

911
/** Initial server connection response. */
1012
message Hello {
@@ -17,11 +19,13 @@ message Hello {
1719
optional uint32 cdp_version_major = 7;
1820
optional uint32 cdp_version_minor = 8;
1921
optional uint32 cdp_version_patch = 9;
22+
optional uint32 idle_lockout_period = 10;
23+
optional string system_use_notification = 11;
2024
}
2125

2226
/** Server expects this response if it sent a auth_required true. */
2327
message AuthRequest {
24-
optional string user_id = 1;
28+
optional string user_id = 1; // case-insensitive (can be sent in any casing)
2529
message ChallengeResponse {
2630
optional string type = 1;
2731
optional bytes response = 2; // data corresponding to the type, eg. hash(challenge + password)
@@ -68,6 +72,9 @@ message Container {
6872
eCurrentTimeResponse = 8;
6973
eChildAddRequest = 9;
7074
eChildRemoveRequest = 10;
75+
eReAuthRequest = 11;
76+
eReAuthResponse = 12;
77+
eActivityNotification = 13;
7178
}
7279
optional Type message_type = 1;
7380
optional Error error = 2;
@@ -80,6 +87,8 @@ message Container {
8087
optional uint64 current_time_response = 9;
8188
repeated ChildAdd child_add_request = 10;
8289
repeated ChildRemove child_remove_request = 11;
90+
optional AuthRequest re_auth_request = 12;
91+
optional AuthResponse re_auth_response = 13;
8392
extensions 100 to max;
8493
}
8594

@@ -89,10 +98,14 @@ message Error {
8998
optional string text = 2;
9099
optional uint32 node_id = 3;
91100
optional string parameter = 4;
101+
optional bytes challenge = 5; // new challenge for re-authentication, used with code = eAUTH_RESPONSE_EXPIRED
102+
optional uint32 idle_lockout_period = 6; // updated value for idle lockout period, used with code = eAUTH_RESPONSE_EXPIRED
92103
extensions 100 to max;
93104
}
94105

95106
enum RemoteErrorCode {
107+
eAUTH_RESPONSE_EXPIRED = 1; // connection is in non-authenticated state (e.g. because of session inactivity timeout) -
108+
// full reconnect or new AuthRequest with ChallengeResponse is needed to continue
96109
eINVALID_REQUEST = 10;
97110
eUNSUPPORTED_CONTAINER_TYPE = 20;
98111
eVALUE_THROTTLING_OCCURRING = 30;
@@ -211,5 +224,8 @@ message ValueRequest {
211224
optional double fs = 2; // If present indicates that values expected no more often than provided FS rate
212225
// (server will accumulate and time-stamp values if they occur more often)
213226
optional bool stop = 3; // If true target must stop updates on the provided values else this is start
227+
optional double sample_rate = 4; // If non zero indicates that values should be
228+
// sampled with given sampling rate frequency (samples/second)
229+
// missing or zero means all samples must be provided
214230
extensions 100 to max;
215231
}

0 commit comments

Comments
 (0)