Socket
Socket
Sign inDemoInstall

elarian

Package Overview
Dependencies
36
Maintainers
3
Versions
81
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.3.1 to 0.5.0

lib/utils/service/AppStateService.proto

145

docs/README.md

@@ -93,14 +93,4 @@ ## Classes

* [new Elarian(config)](#new_Elarian_new)
* [.generateAuthToken()](#Elarian+generateAuthToken) ⇒ <code>AuthToken</code>
* [.addReminder(id, reminder)](#Elarian+addReminder) ⇒ <code>Reply</code>
* [.addGroupReminder(group, reminder)](#Elarian+addGroupReminder) ⇒ <code>Reply</code>
* [.cancelReminder(id, reminder)](#Elarian+cancelReminder) ⇒ <code>Reply</code>
* [.cancelGroupReminder(group, reminder)](#Elarian+cancelGroupReminder) ⇒ <code>Reply</code>
* [.updateGroups(id, groups)](#Elarian+updateGroups) ⇒ <code>Reply</code>
* [.deleteGroups(id, groups)](#Elarian+deleteGroups) ⇒ <code>Reply</code>
* [.updateMetadata(id, updates)](#Elarian+updateMetadata) ⇒ <code>Reply</code>
* [.deleteMetadata(id, keys)](#Elarian+deleteMetadata) ⇒ <code>Reply</code>
* [.leaseAppState(id)](#Elarian+leaseAppState) ⇒ <code>StateReply</code>
* [.updateAppState(id, state)](#Elarian+updateAppState) ⇒ <code>StateReply</code>
* [.deleteAppState(id)](#Elarian+deleteAppState) ⇒ <code>StateReply</code>
* [.fetchAppState()](#Elarian+fetchAppState) ⇒ <code>AppState</code>
* [.updateAppState(data)](#Elarian+updateAppState) ⇒ <code>AppState</code>
* [.connect()](#Client+connect) ⇒ [<code>Elarian</code>](#Elarian)

@@ -122,118 +112,11 @@ * [.isConnected()](#Client+isConnected) ⇒ <code>boolean</code>

<a name="Elarian+generateAuthToken"></a>
<a name="Elarian+fetchAppState"></a>
### elarian.generateAuthToken() ⇒ <code>AuthToken</code>
<p>Generate a short-lived auth token to use instead of apiKey. Used for browser and mobile clients.</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
<a name="Elarian+addReminder"></a>
### elarian.addReminder(id, reminder) ⇒ <code>Reply</code>
<p>Add a reminder</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>human id</p> |
| reminder | <code>Reminder</code> | <p>a reminder</p> |
<a name="Elarian+addGroupReminder"></a>
### elarian.addGroupReminder(group, reminder) ⇒ <code>Reply</code>
<p>Add a group reminder</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| group | <code>IndexMapping</code> | |
| reminder | <code>Reminder</code> | <p>a reminder</p> |
<a name="Elarian+cancelReminder"></a>
### elarian.cancelReminder(id, reminder) ⇒ <code>Reply</code>
<p>Cancel a reminder</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>human id</p> |
| reminder | <code>string</code> | <p>a reminder key</p> |
<a name="Elarian+cancelGroupReminder"></a>
### elarian.cancelGroupReminder(group, reminder) ⇒ <code>Reply</code>
<p>Cancel a group reminder</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| group | <code>IndexMapping</code> | |
| reminder | <code>string</code> | <p>a reminder key</p> |
<a name="Elarian+updateGroups"></a>
### elarian.updateGroups(id, groups) ⇒ <code>Reply</code>
<p>Update a human's groups</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
| groups | <code>Array.&lt;GroupIndex&gt;</code> | |
<a name="Elarian+deleteGroups"></a>
### elarian.deleteGroups(id, groups) ⇒ <code>Reply</code>
<p>Delete a human's groups</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
| groups | <code>Array.&lt;string&gt;</code> | |
<a name="Elarian+updateMetadata"></a>
### elarian.updateMetadata(id, updates) ⇒ <code>Reply</code>
<p>Update a human's metadata</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
| updates | <code>map.&lt;string, DataMapValue&gt;</code> | |
<a name="Elarian+deleteMetadata"></a>
### elarian.deleteMetadata(id, keys) ⇒ <code>Reply</code>
<p>Delete a human's metadata</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
| keys | <code>Array.&lt;string&gt;</code> | |
<a name="Elarian+leaseAppState"></a>
### elarian.leaseAppState(id) ⇒ <code>StateReply</code>
### elarian.fetchAppState() ⇒ <code>AppState</code>
<p>Lease app state</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
<a name="Elarian+updateAppState"></a>
### elarian.updateAppState(id, state) ⇒ <code>StateReply</code>
### elarian.updateAppState(data) ⇒ <code>AppState</code>
<p>Update app state</p>

@@ -243,18 +126,6 @@

| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
| state | <code>DataMapValue</code> | |
| Param | Type |
| --- | --- |
| data | <code>Buffer</code> |
<a name="Elarian+deleteAppState"></a>
### elarian.deleteAppState(id) ⇒ <code>StateReply</code>
<p>Delete app state</p>
**Kind**: instance method of [<code>Elarian</code>](#Elarian)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | <p>a human id</p> |
<a name="Client+connect"></a>

@@ -261,0 +132,0 @@

@@ -1,1 +0,1 @@

module.exports = require('./lib/index.node');
module.exports = require('./lib/index');
/* eslint-disable max-len */
/* global window */
/* eslint-disable no-underscore-dangle */
const grpc = require('@grpc/grpc-js');
const validate = require('validate.js');
const { Single } = require('rsocket-flowable');
const protoLoader = require('@grpc/proto-loader');
const {
ElarianMessages,
} = require('./utils');
const utils = require('./utils');
const {
DataMapValue,
ServerToAppNotification,
ServerToAppNotificationReply,
} = ElarianMessages;
const { log } = utils;
const { connectRSocket } = require('./utils');
const defaultConfigOptions = {
resumable: false,
lifetime: 60000,
keepAlive: 1000,
notificationTimeout: 5000,
};
/**

@@ -34,3 +19,2 @@ * Instantiate an elarian client. You have to call connect() on then client to start using it

...config,
isSimulator: config.isSimulator === true,
};

@@ -42,19 +26,6 @@ const constraints = {

},
orgId: {
sessionId: {
type: 'string',
presence: true,
},
apiKey: {
type: 'string',
},
authToken: {
type: 'string',
presence: this.platform.isBrowser,
},
allowNotifications: {
type: 'boolean',
},
isSimulator: {
type: 'boolean',
},
options: {

@@ -65,6 +36,2 @@ type: 'object',

if (!opts.apiKey && !opts.authToken) {
throw new Error('Either one of apiKey or authToken is required');
}
const error = validate(opts, constraints);

@@ -75,28 +42,13 @@ if (error) {

this._client = null;
this._socket = null;
this.options = opts;
const configOpts = opts.options || {};
this.configOptions = {
lifetime: configOpts.lifetime || defaultConfigOptions.lifetime,
resumable: configOpts.resumable || defaultConfigOptions.resumable,
keepAlive: configOpts.keepAlive || defaultConfigOptions.keepAlive,
serializer: configOpts.serializer || {
type: 'text',
serialize: (data) => JSON.stringify(data),
deserialize: (data) => {
try {
return JSON.parse(data);
} catch (err) { this.platform.log.warn(`Failed to deserialize ${data}: ${err.message}`); }
return data;
},
this.options = {
uris: {
social: 'id.elarian.com:443',
state: 'api.elarian.com:443',
},
// eslint-disable-next-line max-len
notificationTimeout: configOpts.notificationTimeout || defaultConfigOptions.notificationTimeout,
...opts,
};
this._socialService = null;
this._appStateService = null;
this.eventListeners = {
// debug
data: null,
// Connection

@@ -114,50 +66,9 @@ error: null,

*/
this.connect = function connect({ host, port } = {}) {
connectRSocket({
...this.options,
authToken: this.options.authToken,
apiKey: this.options.authToken ? null : this.options.apiKey,
}, {
host,
port,
...this.configOptions,
platform: this.platform,
setSocket: (socket) => {
this._socket = socket;
},
getConnectionHandlers: () => ({
error: (err) => {
if (this.eventListeners.error) {
this.eventListeners.error(err);
}
},
closed: () => {
this.disconnect();
if (this.eventListeners.closed) {
this.eventListeners.closed();
}
},
pending: this.eventListeners.pending,
connecting: this.eventListeners.connecting,
}),
notificationHandler: this._notificationHandler(this),
}).then(({ client }) => {
this._client = client;
if (this.eventListeners.connected) {
this.eventListeners.connected();
}
this._lifetimeId = setInterval(() => {}, 10000);
}).catch((ex) => {
if (this.eventListeners.error) {
this.eventListeners.error(ex);
}
});
return this;
};
this.connect = function connect() {
// TODO: Connect to notification service
this.getSocket = function getSocket() {
if (!this._socket) {
throw new Error('Client is not connected');
if (this.eventListeners.connected) {
this.eventListeners.connected();
}
return this._socket;
return this;
};

@@ -170,3 +81,3 @@

this.isConnected = function isConnected() {
return this._client !== null && this._socket !== null;
return this._isConnected;
};

@@ -178,144 +89,67 @@

this.disconnect = function disconnect() {
if (this._client) {
this._client.close();
clearInterval(this._lifetimeId);
// TODO: Disconnect from notification service
if (this.eventListeners.closed) {
this.eventListeners.closed();
}
this._client = null;
this._socket = null;
};
const cleanup = (code) => {
this.platform.log.warn(`Disconnecting from API server(${code})`);
log.warn(`Disconnecting from API server(${code})`);
this.disconnect();
if (!this.platform.isBrowser) {
process.exit(code);
}
process.exit(code);
};
if (this.platform.isBrowser) {
window.onbeforeunload = cleanup;
} else {
process.on('SIGINT', cleanup.bind(null));
process.on('SIGQUIT', cleanup.bind(null));
process.on('SIGTERM', cleanup.bind(null));
}
process.on('SIGINT', cleanup.bind(null));
process.on('SIGQUIT', cleanup.bind(null));
process.on('SIGTERM', cleanup.bind(null));
}
const cleanUpNotificationPayload = (event, data) => {
/* eslint-disable no-param-reassign */
switch (event) {
// App
case 'reminder':
data.workId = data.workId.value;
data.group = {
mapping: data.group.mapping,
expiresAt: data.group.expiresAt.seconds,
};
data.reminder = {
key: null,
payload: data.reminder.payload.value,
remindAt: data.reminder.remindAt.seconds,
interval: data.reminder?.interval.seconds,
};
break;
default:
break;
// eslint-disable-next-line no-underscore-dangle
Client.prototype._getAppStateService = function _getAppStateService() {
if (this._appStateService) {
return this._appStateService;
}
/* eslint-enable no-param-reassign */
return data;
const uri = this.options.uris.state;
const def = protoLoader.loadSync(
utils.SERVICE_PROTO.APP_STATE,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
},
);
const pd = grpc.loadPackageDefinition(def);
this._appStateService = new pd.com.elarian.hera.proto.AppStateService(uri, grpc.credentials.createSsl());
return this._appStateService;
};
// eslint-disable-next-line no-underscore-dangle
Client.prototype._notificationHandler = (client) => (incomingPayload) => {
let event;
const { log } = client.platform;
const { notificationTimeout = 5000 } = client.options;
Client.prototype._getSocialService = function _getSocialService() {
if (this._socialService) {
return this._socialService;
}
let response = new ServerToAppNotificationReply();
const handlePayload = async () => {
const notif = ServerToAppNotification.deserializeBinary(incomingPayload.data).toObject();
const uri = this.options.uris.social;
const def = protoLoader.loadSync(
utils.SERVICE_PROTO.SOCIAL,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
},
);
const pd = grpc.loadPackageDefinition(def);
this._socialService = new pd.com.elarian.hera.proto.SocialService(uri, grpc.credentials.createSsl());
return this._socialService;
};
const events = Object.keys(notif);
event = events.find((i) => !['orgId', 'appId', 'createdAt', 'humanId', 'state'].includes(i) && notif[i]);
const data = cleanUpNotificationPayload(event, { ...notif[event] });
data.orgId = notif.orgId;
data.appId = notif.appId;
data.humanId = notif.humanId;
data.createdAt = notif?.createdAt.seconds;
data.state = notif?.state;
const globalListener = client.eventListeners.data;
if (globalListener) {
await globalListener(event, data, notif);
}
const listener = client.eventListeners[event];
let incomingAppData = notif.state || {};
const { serializer } = client.configOptions;
incomingAppData = serializer.type === 'text' ? incomingAppData.stringVal : incomingAppData.bytesVal;
incomingAppData = incomingAppData ? serializer.deserialize(incomingAppData) : undefined;
let outgoingAppData = incomingAppData;
if (listener) {
// eslint-disable-next-line no-async-promise-executor
const listenerExec = new Promise(async (resolve, reject) => {
try {
const cb = (nextState) => resolve(nextState);
listener(data, cb);
} catch (ex) {
reject(ex);
}
});
const nextState = await Promise.race([
listenerExec,
new Promise((resolve) => {
setTimeout(resolve, notificationTimeout, {});
}),
]);
if (nextState) { outgoingAppData = nextState; }
}
if (outgoingAppData) {
const appDataUpdate = new DataMapValue();
const serializedValue = serializer.serialize(outgoingAppData);
switch (serializer.type) {
case 'text':
appDataUpdate.setStringVal(serializedValue);
break;
case 'binary':
appDataUpdate.setBytesValue(serializedValue);
break;
default:
throw new Error('Invalid serializer type');
}
response = response.setNextState(appDataUpdate);
}
return response;
};
return new Single((subscriber) => {
subscriber.onSubscribe();
handlePayload()
.then((data) => {
try {
subscriber.onComplete({ data: Buffer.from(data.serializeBinary()) });
} catch (error) {
log.error(`NotificationReplyError::${event}: `, error.message || error);
}
})
.catch((error) => {
// FIXME: This returns a valid response to avoid retries...
// ideally subscriber.onError(error) should be the response
try {
log.error(`NotificationDefaultError::${event}: `, error.message || error);
subscriber.onComplete({ data: Buffer.from(response.serializeBinary()) });
} catch (ex) {
log.error(`NotificationError::${event}: `, ex.message || ex);
}
});
});
// eslint-disable-next-line no-underscore-dangle
Client.prototype._notificationHandler = () => (payload) => {
log.debug(`TODO: Handle incoming ${payload}`);
};

@@ -322,0 +156,0 @@

@@ -0,36 +1,4 @@

/* eslint-disable no-underscore-dangle */
const Client = require('./client');
const {
ElarianMessages,
} = require('./utils');
const {
Duration,
Reminder,
Timestamp,
GroupIndex,
StringValue,
DataMapValue,
IndexMapping,
AppToServerCommand,
AppToServerCommandReply,
AddReminderCommand,
AddGroupReminderCommand,
CancelReminderCommand,
CancelGroupReminderCommand,
UpdateGroupCommand,
DeleteGroupCommand,
UpdateMetadataCommand,
DeleteMetadataCommand,
LeaseAppStateCommand,
UpdateAppStateCommand,
DeleteAppStateCommand,
} = ElarianMessages;
/**

@@ -49,3 +17,4 @@ * Instantiate an elarian client. You have to call connect() on the client to start using it

// Core
reminder: null,
consentDenied: null,
consentRevoked: null,
};

@@ -56,465 +25,29 @@ }

/**
* Add a reminder
* @param {string} id human id
* @param {Reminder} reminder a reminder
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.addReminder = function addReminder(id, reminder) {
const socket = this.getSocket();
if (!id || !reminder) {
throw new Error('An id and reminder are required');
}
let cmd = new AddReminderCommand();
cmd = cmd.setHumanId(id);
let rem = new Reminder()
.setKey(reminder.key)
.setPayload(new StringValue().setValue(reminder.payload))
.setRemindAt(new Timestamp().setSeconds(Math.floor(reminder.remindAt)));
if (reminder.interval) {
rem = rem.setInterval(new Duration().setSeconds(Math.floor(reminder.interval)));
}
cmd.setReminder(rem);
const req = new AppToServerCommand()
.setAddReminder(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
Elarian.prototype.fetchUserData = function fetchUserData() {
throw new Error('Not implemented');
};
/**
* Add a group reminder
* @param {IndexMapping} group
* @param {Reminder} reminder a reminder
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.addGroupReminder = function addGroupReminder(group, reminder) {
const socket = this.getSocket();
if (!group || !reminder) {
throw new Error('A group and reminder are required');
}
const cmd = new AddGroupReminderCommand();
let rem = new Reminder()
.setKey(reminder.key)
.setPayload(new StringValue().setValue(reminder.payload))
.setRemindAt(new Timestamp().setSeconds(Math.floor(reminder.remindAt)));
if (reminder.interval) {
rem = rem.setInterval(new Duration().setSeconds(Math.floor(reminder.interval)));
}
cmd.setReminder(rem);
const grp = new IndexMapping()
.setKey(group.key)
.setValue(new StringValue().setValue(group.value));
cmd.setGroup(grp);
const req = new AppToServerCommand()
.setAddGroupReminder(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getGroupCommand();
const result = {
status: res.getStatus(),
description: res.getDescription(),
workId: (res.getWorkId() || {
getValue: () => undefined,
}).getValue(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
Elarian.prototype.updateUserData = function updateUserData() {
throw new Error('Not implemented');
};
/**
* Cancel a reminder
* @param {string} id human id
* @param {string} reminder a reminder key
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.cancelReminder = function cancelReminder(id, reminder) {
const socket = this.getSocket();
if (!id || !reminder) {
throw new Error('A human id and reminder key are required');
}
const cmd = new CancelReminderCommand().setHumanId(id).setKey(reminder);
const req = new AppToServerCommand()
.setCancelReminder(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Cancel a group reminder
* @param {IndexMapping} group
* @param {string} reminder a reminder key
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.cancelGroupReminder = function cancelGroupReminder(group, reminder) {
const socket = this.getSocket();
if (!group || !reminder) {
throw new Error('A group and reminder key are required');
}
const cmd = new CancelGroupReminderCommand()
.setGroup(new IndexMapping()
.setKey(group.key)
.setValue(new StringValue().setValue(group.value)))
.setKey(reminder);
const req = new AppToServerCommand()
.setCancelGroupReminder(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getGroupCommand();
const result = {
status: res.getStatus(),
description: res.getDescription(),
workId: (res.getWorkId() || {
getValue: () => undefined,
}).getValue(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Update a human's groups
* @param {string} id a human id
* @param {GroupIndex[]} groups
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.updateGroups = function updateGroups(id, groups) {
const socket = this.getSocket();
if (!groups || !groups.length || !id) {
throw new Error('A human id and group(s) are required');
}
const cmd = new UpdateGroupCommand()
.setHumanId(id);
groups.forEach((grp) => {
cmd.addUpdates(new GroupIndex()
.setExpiresAt(new Timestamp().setSeconds(Math.floor(grp.expiresAt)))
.setMapping(new IndexMapping()
.setKey(grp.mapping.key)
.setValue(new StringValue().setValue(grp.mapping.value))));
});
const req = new AppToServerCommand()
.setUpdateGroup(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Delete a human's groups
* @param {string} id a human id
* @param {string[]} groups
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.deleteGroups = function deleteGroups(id, groups) {
const socket = this.getSocket();
if (!groups || !groups.length || !id) {
throw new Error('A human id and group(s) are required');
}
const cmd = new DeleteGroupCommand()
.setHumanId(id);
groups.forEach((grp) => {
cmd.addDeletions(grp);
});
const req = new AppToServerCommand()
.setDeleteGroup(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Update a human's metadata
* @param {string} id a human id
* @param {map<string, DataMapValue>} updates
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.updateMetadata = function updateMetadata(id, updates) {
const socket = this.getSocket();
if (!updates || !id) {
throw new Error('A human id and group(s) are required');
}
const cmd = new UpdateMetadataCommand()
.setHumanId(id);
Object.keys(updates).forEach((key) => {
const val = new DataMapValue();
const target = updates[key];
if (target.stringVal) {
val.setStringVal(target.stringVal);
}
if (target.bytesVal) {
val.setBytesVal(target.bytesVal);
}
cmd.getUpdatesMap().set(key, val);
});
const req = new AppToServerCommand()
.setUpdateMetadata(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Delete a human's metadata
* @param {string} id a human id
* @param {string[]} keys
* @returns {Reply}
* @memberof Elarian
*/
Elarian.prototype.deleteMetadata = function deleteMetadata(id, keys) {
const socket = this.getSocket();
if (!keys || !keys.length || !id) {
throw new Error('A human id and keys(s) are required');
}
const cmd = new DeleteMetadataCommand()
.setHumanId(id);
keys.forEach((key) => {
cmd.addDeletions(key);
});
const req = new AppToServerCommand()
.setDeleteMetadata(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
/**
* Lease app state
* @param {string} id a human id
* @returns {StateReply}
* @returns {AppState}
* @memberof Elarian
*/
Elarian.prototype.leaseAppState = function leaseAppState(id) {
const socket = this.getSocket();
if (!id) {
throw new Error('A human id is required');
}
const cmd = new LeaseAppStateCommand()
.setHumanId(id);
const req = new AppToServerCommand()
.setLeaseAppState(cmd);
Elarian.prototype.fetchAppState = function fetchAppState() {
const service = this._getAppStateService();
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getLeaseAppState();
let state = res.getState();
if (state) {
state = {
stringVal: state.getStringVal(),
bytesVal: state.getBytesVal(),
};
}
const result = {
state,
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
const params = {
appId: this.options.appId,
sessionId: this.options.sessionId,
};
service.GetAppState(params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});

@@ -525,97 +58,31 @@ };

* Update app state
* @param {string} id a human id
* @param {DataMapValue} state
* @returns {StateReply}
* @param {Buffer} data
* @returns {AppState}
* @memberof Elarian
*/
Elarian.prototype.updateAppState = function updateAppState(id, state) {
const socket = this.getSocket();
if (!id || !state) {
throw new Error('A human id and state are required');
Elarian.prototype.updateAppState = function updateAppState(data) {
if (!data) {
throw new Error('id and data are required');
}
const val = new DataMapValue();
if (state.stringVal) {
val.setStringVal(state.stringVal);
}
if (state.bytesVal) {
val.setBytesVal(state.bytesVal);
}
const cmd = new UpdateAppStateCommand()
.setHumanId(id)
.setUpdate(val);
const service = this._getAppStateService();
const req = new AppToServerCommand()
.setUpdateAppState(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
const params = {
state: {
appId: this.options.appId,
sessionId: this.options.sessionId,
state: data,
},
};
service.UpdateAppState(params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
/**
* Delete app state
* @param {string} id a human id
* @returns {StateReply}
* @memberof Elarian
*/
Elarian.prototype.deleteAppState = function deleteAppState(id) {
const socket = this.getSocket();
if (!id) {
throw new Error('A human id is required');
}
const cmd = new DeleteAppStateCommand()
.setHumanId(id);
const req = new AppToServerCommand()
.setDeleteAppState(cmd);
return new Promise((resolve, reject) => {
socket
.requestResponse({
data: Buffer.from(req.serializeBinary()),
})
.subscribe({
onComplete: (value) => {
try {
const res = AppToServerCommandReply
.deserializeBinary(value.data)
.getUpdateState();
const result = {
status: res.getStatus(),
description: res.getDescription(),
};
resolve(result);
} catch (ex) {
reject(ex);
}
},
onError: (error) => reject(error),
});
});
};
module.exports = Elarian;
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
require('./authentication');
const Client = require('./client');
const Elarian = require('./elarian');

@@ -13,3 +10,3 @@

*/
const initializeClient = (config, connectOpts = {}) => new Promise((resolve, reject) => {
const initializeClient = (config) => new Promise((resolve, reject) => {
try {

@@ -19,3 +16,3 @@ const client = new Elarian(config);

client.on('connected', () => resolve(client));
client.connect(connectOpts);
client.connect();
} catch (error) {

@@ -26,8 +23,5 @@ reject(error);

module.exports = (conf) => {
Client.prototype.platform = conf;
return {
Elarian,
initializeClient,
};
module.exports = {
Elarian,
initializeClient,
};

@@ -1,221 +0,9 @@

const { RSocketClient } = require('rsocket-core');
const { Duration } = require('google-protobuf/google/protobuf/duration_pb');
const { Timestamp } = require('google-protobuf/google/protobuf/timestamp_pb');
const { StringValue, Int32Value } = require('google-protobuf/google/protobuf/wrappers_pb');
const pkg = require('../../package.json');
const appSocket = require('./service/app_socket_pb');
const ElarianMessages = { ...appSocket };
ElarianMessages.Duration = Duration;
ElarianMessages.Timestamp = Timestamp;
ElarianMessages.Int32Value = Int32Value;
ElarianMessages.StringValue = StringValue;
const {
AppConnectionMetadata,
} = ElarianMessages;
const makeConnectionHandlerName = (kind) => {
const name = kind.toLowerCase().replace(/_/, '');
return name === 'notconnected' ? 'pending' : name;
const SERVICE_PROTO = {
SOCIAL: `${__dirname}/service/SocialService.proto`,
APP_STATE: `${__dirname}/service/AppStateService.proto`,
};
const extractConnectionState = (transport, isBrowser) => {
if (isBrowser) {
// eslint-disable-next-line no-underscore-dangle
return transport._status.kind;
}
return transport.getConnectionState().kind;
};
const connectRSocket = (options, connectionOptions) => new Promise((resolve, reject) => {
const {
orgId,
appId,
apiKey,
authToken,
isSimulator = false,
allowNotifications = true,
} = options;
const {
host,
port,
platform,
setSocket,
lifetime = 60000,
keepAlive = 1000,
reconnectTimeout = 60000,
resumable = true,
notificationHandler,
getConnectionHandlers,
} = connectionOptions;
const { log, getTransport, isBrowser } = platform;
if (!notificationHandler && allowNotifications) {
reject(new Error('notificationHandler is required'));
return;
}
let data = new AppConnectionMetadata()
.setOrgId(orgId)
.setAppId(appId)
.setSimulatorMode(isSimulator)
.setSimplexMode(!allowNotifications);
if (authToken) {
data = data.setAuthToken(new StringValue().setValue(authToken));
} else if (apiKey) {
data = data.setApiKey(new StringValue().setValue(apiKey));
}
data = Buffer.from(data.serializeBinary());
const metadata = Buffer.from(JSON.stringify({
agent: `javascript/${pkg.version}`,
}));
const transport = getTransport({
host,
port,
resumeToken: resumable ? Buffer.from(`${appId}-${Date.now()}`) : undefined,
sessionDurationSeconds: lifetime / 1000,
});
log.debug(`Transport Initialized: ${host}:${port}`);
const client = new RSocketClient({
transport,
setup: {
lifetime,
keepAlive,
metadataMimeType: 'application/json',
dataMimeType: 'application/octet-stream',
payload: { data, metadata },
},
responder: {
metadataPush: (payload) => {
log.debug('Got a metadataPush', payload);
},
fireAndForget: (payload) => {
log.debug('Got a fireAndForget', payload);
},
requestStream: (payload) => {
log.debug('Got a requestStream', payload);
},
requestChannel: (payload) => {
log.debug('Got a requestChannel', payload);
},
requestResponse: (payload) => notificationHandler(payload),
},
errorHandler: (error) => {
log.debug('Connection Error', error);
if (getConnectionHandlers().error) {
getConnectionHandlers().error(error);
}
},
});
let transportStarted = false;
const handleConnected = (socket) => {
log.debug('Client connected');
setSocket(socket);
};
const reconnect = (timeout = reconnectTimeout) => {
setTimeout(() => {
if (client === null) {
// give up
return;
}
log.info('Attempting to reconnect...');
// eslint-disable-next-line no-underscore-dangle
client._connection = null;
client.connect().subscribe({
onComplete: (socket) => {
setTimeout(() => {
const connectionState = extractConnectionState(transport, isBrowser);
if (connectionState === 'CONNECTED') {
handleConnected(socket);
} else {
log.error(new Error(`Failed to connect: ${connectionState}`));
}
}, 1000);
},
onError: (error) => {
log.debug('Client connection refused ', error);
},
});
}, timeout);
};
transport.connectionStatus().subscribe({
onNext: (status) => {
log.debug(`Transport: ${status.kind}`);
const handler = getConnectionHandlers()[makeConnectionHandlerName(status.kind)];
if (handler) {
try {
handler(status.error);
} catch (ex) {
log.debug('Failed to call connection status handler');
}
}
if (status.kind === 'CONNECTED') {
transportStarted = true;
return;
}
if (status.kind === 'ERROR') {
transport.setConnectionStatus({
kind: 'NOT_CONNECTED',
});
return;
}
if (['NOT_CONNECTED', 'CLOSED'].includes(status.kind)) {
if (transportStarted) {
log.info(`Disconnected from Elarian server, will attempt to reconnect in ${reconnectTimeout}ms...`);
reconnect();
}
}
},
onError: (error) => {
log.debug(`Transport error: ${error}`);
if (getConnectionHandlers().error) {
getConnectionHandlers().error(error);
}
},
onSubscribe: (subscription) => {
subscription.request(Number.MAX_SAFE_INTEGER);
},
onComplete: (signal) => {
log.debug(`Transport closed: ${signal}`);
},
});
client.connect().subscribe({
onComplete: (socket) => {
setTimeout(() => {
const connectionState = extractConnectionState(transport, isBrowser);
if (connectionState === 'CONNECTED') {
handleConnected(socket);
resolve({ client });
} else {
reject(new Error(`Failed to connect: ${connectionState}`));
}
}, 1000);
},
onError: (error) => {
log.debug('Client connection refused ', error);
reject(error);
},
onSubscribe: () => {},
onNext: (entry) => {
log.debug('Client data received ', entry);
},
});
});
module.exports = {
connectRSocket,
ElarianMessages,
SERVICE_PROTO,
log: console,
};

@@ -6,104 +6,15 @@ /* eslint-disable max-len */

* @typedef {Object} ClientConfig
* @property {string} apiKey
* @property {string} sessionId
* @property {string} appId
* @property {string} orgId
* @property {string} [authToken] received from an client that authenticated with the API key. @see {@link Elarian.generateAuthToken}
* @property {boolean} [isSimulator] Run this client as a simulator
* @property {boolean} [allowNotifications] allow this non-simulator client to receive notifications or not
* @property {ConnectionOptions} [options] setup connection options
*/
/**
* An object representing serializer
* @typedef {Object} Serializer
* @property {text|binary} type
* @property {function} serialize
* @property {function} deserialize
*/
/**
* An object representing config options
* @typedef {Object} ConnectionOptions
* @property {number} [lifetime = 6000]
* @property {number} [keepAlive = 1000]
* @property {boolean} [resumable = false]
* @property {number} [notificationTimeout = 5000]
* @property {number} [reconnectTimeout = 60000]
* @property {Serializer} [serializer] used to (de)serialize your metadata
*/
/**
* An object representing a reminder
* @typedef {Object} Reminder
* @property {string} key
* @property {number} remindAt timestamp in seconds e.g 1615361861
* @property {string} payload
* @property {number} [interval] duration in seconds e.g. 60
*/
/**
* @typedef {Object} Reply
* @property {boolean} status
* @property {string} description
* @property {string} [workId]
*/
/**
* @typedef {Object} StateReply
* @property {boolean} status
* @property {string} description
* @property {DataMapValue} state
*/
/**
* @typedef {Object} IndexMapping
* @property {string} key
* @property {string} value
*/
/**
* @typedef {Object} DataMapValue
* @property {string} [stringVal]
* @property {binary} [bytesVal]
*/
/**
* @typedef {Object} GroupIndex
* @property {IndexMapping} mapping
* @property {number} expiresAt
*/
/**
* An object representing auth token
* @typedef {Object} AuthToken
* @property {string} token
* @property {number} lifetime in seconds
*/
/**
* An object representing the notification data
* @typedef {Object} Notification
* @property {string} humanId
* @property {string} orgId
* An object representing app state
* @typedef {Object} AppState
* @property {string} appId
* @property {long} createdAt
* @property {DataMapValue} state
* @property {string} sessionId
* @property {Buffer} state
*/
/**
* Reminder notification
* @typedef {Notification} ReminderNotification
* @property {Reminder} reminder
* @property {GroupIndex} group
* @property {string} workId
*/
/**
* Notification callback
* @typedef {function} NotificationCallback
* @param {DataMapValue} [nextState = null]
* @returns {void}
*/
/**
* A function that reacts to events

@@ -130,3 +41,4 @@ * @typedef {function} NotificationHandler

* <ul>
* <li><b>reminder</b>: @see {@link ReminderNotification}</li>
* <li><b>consentDenied</b>: ...</li>
* <li><b>consentRevoked</b>: ...</li>
* </ul>

@@ -133,0 +45,0 @@ * </li>

{
"name": "elarian",
"version": "0.3.1",
"version": "0.5.0",
"description": "Elarian JavaScript SDK",

@@ -11,6 +11,5 @@ "main": "index.js",

"lint": "eslint .",
"build": "rm -rf docs/ && npm run build:docs && npm run build:web",
"build": "rm -rf docs/ && npm run build:docs",
"build:types": "jsdoc -t node_modules/tsd-jsdoc/dist -c .jsdoc -r lib/ && mv docs/types.d.ts ./ ",
"build:docs": "jsdoc -c .jsdoc -R README.md -r lib/ && jsdoc2md -c .jsdoc --files lib/** > docs/README.md",
"build:web": "webpack",
"prepublish": "npm run build"

@@ -26,34 +25,19 @@ },

"keywords": [
"africastalking",
"elarian",
"sms",
"ussd",
"voice",
"customer",
"payments",
"rcs",
"google",
"facebook",
"whatsapp",
"instagram",
"telegram"
"elarian"
],
"engines": {
"node": ">=18 <20"
"node": ">=12"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.12",
"@grpc/proto-loader": "^0.7.10",
"google-protobuf": "^3.21.2",
"rsocket-core": "0.0.27",
"rsocket-flowable": "0.0.27",
"rsocket-tcp-client": "0.0.27",
"rsocket-websocket-client": "0.0.27",
"validate.js": "^0.13.1",
"ws": "^8.13.0"
"validate.js": "^0.13.1"
},
"devDependencies": {
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint": "^8.55.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-mocha": "^10.2.0",
"eslint-plugin-security": "^1.7.1",

@@ -65,3 +49,3 @@ "ink-docstrap": "^1.3.2",

"mocha": "^10.2.0",
"node-fetch": ">=3.3.1",
"node-fetch": ">=3.3.2",
"nyc": "^15.1.0",

@@ -71,5 +55,5 @@ "promise-parallel-throttle": "^3.3.0",

"signale": "^1.4.0",
"webpack": "^5.88.1",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}

@@ -19,19 +19,13 @@ # Elarian

// on node
const { initializeClient } = require('elarian');
/*
or in the browser
<script src="web.js"></script>
// or import { initializeClient } from 'elarian/web'
*/
// ...
const elarian = await initializeClient({
apiKey: 'YOUR_API_KEY', // or authToken: 'YOUR_AUTH_TOKEN'
orgId: 'YOUR_ORG_ID',
sessionId: 'YOUR_SESSION_ID',
appId: 'YOUR_APP_ID',
});
elarian.on('reminder', (data, customer) => {
elarian.on('consentDenied', (userId, data) => {
// ...

@@ -41,6 +35,5 @@ });

const userId = 'abc...';
const { state } = await elarian.leaseAppState(userId);
const data = JSON.parse(state.stringVal);
await elarian.updateAppState(userId, { stringVal: JSON.stringify({ ...data, status: 'good boy' }) });
await elarian.updateMetadata(userId, { bio: { bytesVal: Buffer.from('age=29;gender=female') }});
const { state } = await elarian.fetchAppState();
const data = JSON.parse(state.toString());
await elarian.updateAppState({ state: Buffer.from(JSON.stringify({ ...data, status: 'good boy' })) });

@@ -47,0 +40,0 @@ ```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc