-
Client-side JavaScript
- Overview
- Add and execute JavaScript
- ScriptPermissions
- Execution time limit
- Javascript parameters
- Event handlers
- Returning values
- Http masks
- JSApi
- JSApiContract
- JSApiRoleBuilder
- JSApiPermissionBuilder
- JSApiReferenceBuilder
- JSApiSharedFolders
- JSApiSharedStorage
- JSApiOriginStorage
- JSApiRevisionStorage
- JSApiHttpClient
- Http server
- Examples
2018-10-25 11:10
Client-side JavaScript
Overview
This API provides to client to add some JavaScript code to contracts definition. Execution of this script may perform some useful actions, e.g. print specific contract info or create new revision.
Add and execute JavaScript
You can add JS code to contract with Java methods Definition#setJS(byte[], String, JSApiScriptParameters)
or State#setJS(byte[], String, JSApiScriptParameters)
. One contract may contains many JS scripts. setJS
method add js-file to scripts list as attach. Also, it can be .zip compressed file. In this case .zip file should contains single .js file with script text, and scriptParameters.isCompressed
should be set to true
. Execute any attached script with Contract#execJS(JSApiExecOptions, byte[], String...)
- it will try to run jsApiEvents.main()
function. Use new JSApiExecOptions()
for default options. The second parameter byte[]
should receive content of attached script file (whether compressed or not).
ScriptPermissions
Each attached JS has its own permissions list. You can change this permissions with JSApiScriptParameters#setPermission(ScriptPermissions permission, Boolean state)
while call setJS. ScriptPermissions is enum of string values:
- shared_folders
- shared_storage
- origin_storage
- revision_storage
- http_client
Execution time limit
You can limit time of JavaScript code execution with scriptParameters.timeLimitMillis
, in call of method setJS
.
Javascript parameters
All string parameters of Contract#execJS(JSApiExecOptions, byte[], String...)
method will be passed into javascript as global var jsApiParams
. On JS, you can get count of parameters with jsApiParams.length
(here will be 0
if we have no parameters), and iterate over it with jsApiParams[i]
.
Event handlers
JS code can handle some types of external events. Now there are main
event and handlers for http server. Any event handler should be declared as method of jsApiEvents
object.
var jsApiEvents = new Object();
jsApiEvents.main = function() {
return 'hello world';
};
jsApiEvents.httpHandler_index = function() {
return 'index page';
};
Returning values
JS code can return any values to client, as result value from main
event. (If js code don't have main
event, you can put result values into js global var result
, but this variant is deprecated.) Also, you can return from js array of values and read them on java with ScriptObjectMirror
, e.g.:
var jsApiEvents = new Object();
jsApiEvents.main = function() {
return ['22', '33'];
};
and then on java:
ScriptObjectMirror res = (ScriptObjectMirror) contract.execJS();
System.out.println("res[0]: " + res.get("0"));
System.out.println("res[1]: " + res.get("1"));
Http masks
JSApiScriptParameters
has two fields domainMasks
and ipMasks
. This masks allows to access remote urls/IPs with JSApiHttpClient
class (it can be accessed in JS by call jsApi.getHttpClient()
).
domainMasks examples:
- universa.io - allows domain universa.io, without subdomains
- sub.universa.io - allows domain sub.universa.io, without universa.io
- *.universa.io - allows any subdomains, and universa.io too
- universa.io:80 - allows only http port for universa.io
- universa.io:443 - allows only https port for universa.io
- universa.io:8080 - allows only custom port 8080 for universa.io
- universa.io:* - allows any port for universa.io
- universa.io - allows http(80) and https(443) ports for universa.io by default
ipMasks examples:
- 192.168.33.44 - allows one ip address
- 192.168.33.* - allows ip addresses from 192.168.33.0 to 192.168.33.255
- ports are the same as in domainMasks
JSApi
While executing, javascript receives instance of JSApi into global var jsApi
. This var provides to js set of useful methods:
JSApiContract getCurrentContract()
return
instance ofJSApiContract
that represents current contract
JSApiRoleBuilder getRoleBuilder()
return
role builder object, used to create simple, list or link roles
JSApiPermissionBuilder getPermissionBuilder()
return
permission builder object, used to create any type of permissions
JSApiSharedFolders getSharedFolders()
return
accessor to shared folders. List of shared folders can be passed to script throughJSApiExecOptions
parameter ofexecJS
method
JSApiSharedStorage getSharedStorage()
return
accessor to clients local shared storage, available for all contracts
JSApiOriginStorage getOriginStorage()
return
accessor to clients local shared storage, available for contracts with sameorigin
ascurrentContract.origin
JSApiRevisionStorage getRevisionStorage()
return
accessor to clients local shared storage, available for current revision ofcurrentContract
JSApiHttpClient getHttpClient
return
http client, need permissionhttp_client
PublicKey bin2publicKey(byte[] binary)
return
unpackedPublicKey
from given binary
PublicKey base64toPublicKey(String s)
return
unpackedPublicKey
from given base64 string, that contains packed binary
byte[] string2bin(String s)
return
binary representation for given string
String bin2base64(byte[] bin)
return
base64-string representation for given binary
JSApiContract
Instance of this class may represent some contract. It provides access to contract data with set of methods:
String getId()
return
hashId of contract, as base64 string
int getRevision()
return
revision number, as int
String getOrigin()
return
hashId of origin contract, as base64 string
String getParent()
return
hashId of parent contract, as base64 string
long getCreatedAt()
return
contract creation time, as unix timestamp
String getStateDataField(String fieldPath)
fieldPath
path.to.fieldreturn
corresponding value from data section of contract state, as String
void setStateDataField(String fieldPath, String value)
fieldPath
path.to.fieldvalue
String value to set
void setStateDataField(String fieldPath, int value)
fieldPath
path.to.fieldvalue
int value to set
String getDefinitionDataField(String fieldPath)
fieldPath
path.to.fieldreturn
corresponding value from data section of contract definition, as String
String getTransactionalDataField(String fieldPath)
fieldPath
path.to.fieldreturn
corresponding value from data section of contract state, as String
void setTransactionalDataField(String fieldPath, String value)
fieldPath
path.to.fieldvalue
String value to set
List<String> getIssuer()
return
list of addresses fromissuer
role, as array of strings. Public keys converts to addresses, AnonymousIds ignored.
List<String> getOwner()
return
list of addresses fromowner
role, as array of strings. Public keys converts to addresses, AnonymousIds ignored.
List<String> getCreator()
return
list of addresses fromcreator
role, as array of strings. Public keys converts to addresses, AnonymousIds ignored.
void setOwner(List<String> addresses)
addresses
array of addresses to set forowner
role
void registerRole(JSApiRole role)
role
instance of JSApiRole that will be registered. Name must be unique otherwise existing role will be overwritten
boolean isPermitted(String permissionName, PublicKey... keys)
permissionName
type of permission to check forkeys
set of keys to check withreturn
permission allowed for keys is found
JSApiContract createRevision()
return
new revision of current contract, as instance ofJSApi_contract
void addPermission(JSApiPermission permission)
permission
instance of JSApiPermission that will be added
void addReference(JSApiReference reference)
reference
instance of JSApiReference that will be added, it can be created throughJSApiReferenceBuilder
JSApiRoleBuilder
This object is used to create any type of roles. Methods:
JSApiSimpleRole createSimpleRole(String name, String... addresses)
name
is role nameaddresses
set of addresses for which role will be createdreturn
new role, represented by classJSApiSimpleRole
JSApiListRole createListRole(String name, String mode, JSApiRole... roles)
name
is role namemode
is mode of sub-roles combining: "all", "any" or "quorum"addresses
set of addresses for which role will be createdreturn
new role, represented by classJSApiListRole
JSApiRoleLink createRoleLink(String newRoleName, String existingRoleName)
return
new role link, represented by classJSApiRoleLink
JSApiPermissionBuilder
This object is used to create any type of permissions. Methods:
JSApiSplitJoinPermission createSplitJoinPermission(JSApiRole role, ScriptObjectMirror params)
role
allows to permissionparams
is parameters of permission: fieldname, minvalue, minunit, joinmatch_fieldsreturn
new permission, represented byJSApiSplitJoinPermission
class
JSApiChangeNumberPermission createChangeNumberPermission(JSApiRole role, ScriptObjectMirror params)
role
allows to permissionparams
is parameters of permission: fieldname, range (minvalue, maxvalue) and delta (minstep, max_step)return
new permission, represented byJSApiChangeNumberPermission
class
JSApiChangeOwnerPermission createChangeOwnerPermission(JSApiRole role)
role
allows to permissionreturn
new permission, represented byJSApiChangeOwnerPermission
class
JSApiModifyDataPermission createModifyDataPermission(JSApiRole role, ScriptObjectMirror params)
role
allows to permissionparams
is parameters of permission: fields is map of field names and lists of allowed valuesreturn
new permission, represented byJSApiModifyDataPermission
class
JSApiRevokePermission createRevokePermission(JSApiRole role)
role
allows to permissionreturn
new permission, represented byJSApiRevokePermission
class
JSApiReferenceBuilder
This object is used to create references. Methods:
JSApiReference createReference(String type)
type
"TRANSACTIONAL"
,"EXISTING_DEFINITION"
or"EXISTING_STATE"
return
new reference asJSApiReference
instance
JSApiReference
This class is used for manage reference conditions.
void setConditions(ScriptObjectMirror conditions)
conditions
e.g. {'all_of':['ref.issuer==ZT4tGcT821Lzv4bx2zBZ6AdmFAod5Feu44UCM9J2ZiiGRj1qyo']}
, see References conditions for details
JSApiSharedFolders
This object is used for access to user's shared folders. List of shared folders can be passed to script through JSApiExecOptions
parameter of execJS
method.
byte[] readAllBytes(String fileName)
This method searches file in all shared folders, returns full file contents.
fileName
is string containing target file name with relative path or without pathreturn
byte array with contents of target file
public void writeNewFile(String fileName, byte[] data)
This method creates new file in first shared folder from execOptions.sharedFolders
list. If file already exists - throws an exception.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into new created file
public void rewriteExistingFile(String fileName, byte[] data)
This method searches file in all shared folders. If file was found, it's contents will be replaced.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into target file
JSApiSharedStorage
This object is used for access to clients local shared storage, available for all contracts. Local path is ~/.universaStorage/shared/
byte[] readAllBytes(String fileName)
This method searches file in common shared folder, returns full file contents.
fileName
is string containing target file name with relative path or without pathreturn
byte array with contents of target file
void writeNewFile(String fileName, byte[] data)
This method creates new file in common shared folder. If file already exists - throws an exception.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into new created file
void rewriteExistingFile(String fileName, byte[] data)
This method searches file in common shared folder. If file was found, it's contents will be replaced.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into target file
JSApiOriginStorage
This object is used for access to clients local shared storage, available for contracts with same origin
as currentContract.origin
. Local path is ~/.universaStorage/origin/<origin>/
.
byte[] readAllBytes(String fileName)
This method searches file in origin shared folder, returns full file contents.
fileName
is string containing target file name with relative path or without pathreturn
byte array with contents of target file
void writeNewFile(String fileName, byte[] data)
This method creates new file in origin shared folder. If file already exists - throws an exception.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into new created file
void rewriteExistingFile(String fileName, byte[] data)
This method searches file in origin shared folder. If file was found, it's contents will be replaced.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into target file
JSApiRevisionStorage
This object is used for access to clients local shared storage, available for current revision of currentContract
. Also, provides read-only access to parent's revision shared folder. Local path is ~/.universaStorage/revision/<id>/
.
byte[] readAllBytes(String fileName)
This method searches file in revision shared folder, returns full file contents.
fileName
is string containing target file name with relative path or without pathreturn
byte array with contents of target file
byte[] readAllBytesFromParent(String fileName)
This method searches file in parent's revision shared folder, returns full file contents.
fileName
is string containing target file name with relative path or without pathreturn
byte array with contents of target file
void writeNewFile(String fileName, byte[] data)
This method creates new file in revision shared folder. If file already exists - throws an exception.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into new created file
void rewriteExistingFile(String fileName, byte[] data)
This method searches file in revision shared folder. If file was found, it's contents will be replaced.
fileName
is string containing target file name with relative path or without pathdata
array of bytes that will be placed into target file
JSApiHttpClient
This object provides methods for making http requests. Target urls (or ip-addresses) should be allowed by domainMasks
and/or ipMasks
parameters in JSApiScriptParameters
.
List sendGetRequest(String strUrl, String respType)
Sends GET request to strUrl.
strUrl
is target url of ip address, may contains :portrespType
one of following:text
for plain text,json
for DOM object orbin
for binary filereturn
array of two fields, first is resp_code from server (int), second is response of type corresponding to respType
sendPostRequest(String strUrl, String respType, Map
params, String contentType)
Sends POST request to strUrl.
strUrl
is target url of ip address, may contains :portrespType
one of following:text
for plain text,json
for DOM object orbin
for binary fileparams
is dict with key-value parameterscontentType
is format, in which parameters will be encoded.form
for application/x-www-form-urlencoded orjson
for application/jsonreturn
array of two fields, first is resp_code from server (int), second is response of type corresponding to respType
sendPostRequestMultipart(String strUrl, String respType, Map
formParams, Map files)
strUrl
is target url of ip address, may contains :portrespType
one of following:text
for plain text,json
for DOM object orbin
for binary fileformParams
is dict with key-value parameters, that will be sent to remote as textfiles
is dict of key-value parameters, that will be sent to remote as binary datareturn
array of two fields, first is resp_code from server (int), second is response of type corresponding to respType
Http server
Contract with attached javascript can implement local http server. In other words, you can implement some http handlers on javascript, attach this javascript to Contract
, and run it with uniclient
command --start-http-server
. All http contracts should be approved, so you need to register it first.
Http endpoint handlers
For handling endpoint, you need to add event handler into jsApiEvents
with any name and two parameters request
(JSApiHttpRequest
) and response
(JSApiHttpResponse
). Then, bind endpoint's path and your handler in routes-file
.
JSApiHttpRequest
JSApi can handle application/x-www-form-urlencoded
, application/json
and multipart/form-data
types of requests. In any case, all request parameters will put in Map
collection corresponding to request's content type, that can be accessed with getParams
method.
Map getParams()
return
parsed key-values request parameters. For uploaded file, here will be key=filename and velue=binary with contents of file.
JSApiHttpResponse
Is used for setting response to client in http handlers.
void setResponseCode(int code)
code
http response codes, like 200 (by default), or 404 or something else
void setBodyAsPlainText(String answer)
answer
String with plain text, than will be return to client
void setBodyAsJson(ScriptObjectMirror bodyAsJson)
bodyAsJson
js-object with answer structure, e.g. inline object like {value: 42}
Start server, routes-file
You can start http server with uniclient --start-http-server /path/to/routes-file.json
. In routes file you should specify listen port and all endpoints, like:
{
"listenPort": "8880",
"routes": [
{"endpoint": "/endpoint1", "handlerName": "httpHandler_endpoint1", "contractPath": "/tmp/contract1.tp", "scriptName": "script1.js"},
{"endpoint": "/endpoint2", "handlerName": "httpHandler_endpoint2", "contractPath": "/tmp/contract1.tp", "scriptName": "script2.js", "jsApiParams": ["param1", "param2", "param3"]},
{"endpoint": "/endpoint3", "handlerName": "httpHandler_endpoint3", "scriptName": "script3.js", "slotId": "IEdqdKCzieGoMweJM8zsmr6fnnPSzD7u8poQkgeUXOQLzPz5scSwYlDEqlZWIU0bTsR1FRLG9+tVprjB28feOeg5GNZGFEbUV4F/5+kQXtqYGtMfQn0QWKvABd9GsUfO", "originId": "xSwLq3g5X2/Cqq5cVECONYAy6TlzxZqVGigKqlhC2hg4zaDqcuT/DRAKRXYy6RTp6itpASWYqbeXw5/6DPTB7wTmt3rCgvJzf7Tb9A23JGdFebu45CGdUODDFhgWA2bh"}
]
}
Each endpoint has parameters:
endpoint
path that will be handledhandlerName
name of handler method, that will be searched injsApiEvents
objectscriptName
name of script, attached to contract. Each contract can have many java scripts attached, so we need to select js by it's namecontractPath
path to contract file in local filesystemslotId
if you want to automatically download latest contract from slot1, specifyslotId
+originId
originId
if you want to automatically download latest contract from slot1, specifyslotId
+originId
jsApiParams
put here array of strings, that will be passed to script'sjsApiParams
object
Http server and SLOT1
If you have save you contract with javascript endpoints in slot1, uniclient will automatically check and download latest revision of it (period of checking is 600 seconds). For this, you should specify your endpoint contract (in routes.json file) with slotId
+originId
. Also, in this case you can ommit contractPath
parameter, your contract will be downloaded from slot1 immidiately after start http server.
Important: for correct work slot1 with http server, it's need to save script file body in contract binary. For this, use setJS
method with flag putContentIntoContract=true
Http script example
Javascript, attached to contract:
var jsApiEvents = new Object();
jsApiEvents.httpHandler_getVersion = function(request, response){
response.setBodyAsJson({
version: 1
});
};
Attach it to contract with setJS
(putContentIntoContract=true
), set jsFileName
=script1.js
. Don't forget to add ModifyDataPermission
to field scripts
if you want to change your javacript implementation later. Register in Universa network and save in SLOT1. Now you have to get originId
of your contract and slotId
of slot1 storage contract.
Next, create ~/routes.json file with content:
{
"listenPort": "8880",
"routes": [
{"endpoint": "/contract1/getVersion", "handlerName": "httpHandler_getVersion", "scriptName": "script1.js", "slotId": "<your_slotId>", "originId": "<your_originId>"}
]
}
Now, start http server with command java -jar uniclient.jar --start-http-server ~/routes.json
. Your endpoint will be available on http://localhost/contract1/getVersion
And last, you can create and register new revision of your contract with new javascript implementation. It will automatically updated in 5-20 minutes. Restart http server manually for immediately update.
Examples
Hello world
JS code:
print('Hello World');
Java code:
Contract contract = new Contract();
contract.setJS(js); // js with code above
contract.execJS();
Print current owner and revision number
JS code:
print('owner: ' + jsApi.getCurrentContract().getOwner());
print('revision: ' + jsApi.getCurrentContract().getRevision());
Java code:
Contract contract = new Contract(myPrivateKey);
contract.setJS(js); // js with code above
contract.execJS();
Create new revision with incremented value
JS code:
rev = jsApi.getCurrentContract().createRevision();
var oldValue = parseInt(rev.getStateDataField('test_value'));
var newValue = (oldValue + 1) >> 0; // '>> 0' converts js-number to int
rev.setStateDataField('test_value', newValue);
result = rev
Java code:
Contract contract = new Contract(TestKeys.privateKey(1));
Binder permParams = new Binder();
permParams.set("min_value", 1);
permParams.set("min_step", 1);
permParams.set("max_step", 1);
permParams.set("field_name", "test_value");
ChangeNumberPermission perm = new ChangeNumberPermission(contract.getOwner(), permParams);
contract.addPermission(perm);
contract.getStateData().set("test_value", 11);
contract.setJS(js); // js with code above
contract.seal();
contract = Contract.extractContractFromJs((Contract.JSApi_contract) contract.execJS());
System.out.println("revision: " + contract.getRevision());
System.out.println("test_value: " + contract.getStateData().getIntOrThrow("test_value"));