I use signalR in my project. It works fine. In a part on my project, I need to use instant notification when a delete button is clicked. The button is in jQuery datatable. I used the following code:
var connection = new signalR.HubConnectionBuilder().withUrl("/signalRServer").withAutomaticReconnect().build();
connection.start();
function DeleteData(pmId, mainFileName, fileName) {
if (confirm("Are you sure")) {
connection.invoke("PmPageUpdate", pmId).catch(function(err){
return console.error(err);
});
Delete(pmId, mainFileName, fileName);
} else {
return false;
}
}
When I debug, the hub method is not called. Console shows the following error:
PmManagement:692 Error: Failed to invoke 'PmPageUpdate' due to an
error on the server.
at H. (signalr.js:1:13973)
at L.I (signalr.js:1:14804)
at X.L.connection.onreceive (signalr.js:1:10649)
at WebSocket.r.onmessage (signalr.js:1:27565)
How can I fix this?
Update:
The followings are codes I've written for the hub. Some methods that are not related to this question has been ignored.
Hub:
private readonly IUserRepository _userRepository;
private readonly ICostCenterRepository _costcenterRepository;
private readonly IHttpContextAccessor _httpContext;
private readonly INotificationRepository _notificationRepository;
private readonly IMessageRepository _messageRepository;
private readonly IPmRepository _pmRepository;
public MessageHub(IUserRepository userRepository, ICostCenterRepository costcenterRepository, IHttpContextAccessor httpContext,
INotificationRepository notificationRepository, IMessageRepository messageRepository, IPmRepository pmRepository)
{
_userRepository = userRepository;
_costcenterRepository = costcenterRepository;
_httpContext = httpContext;
_notificationRepository = notificationRepository;
_messageRepository = messageRepository;
_pmRepository = pmRepository;
}
//define a dictionary to store the userid.
private static Dictionary<string, List<string>> NtoIdMappingTable = new Dictionary<string, List<string>>();
public Task JoinNotificationGroup(string groupName)
{
return Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public Task LeaveNotificationGroup(string groupName)
{
return Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public override async Task OnConnectedAsync()
{
var username = Context.User.Identity.Name;
var userId = Context.UserIdentifier;
List<string> userIds;
//store the userid to the list.
if (!NtoIdMappingTable.TryGetValue(username, out userIds))
{
userIds = new List<string>();
userIds.Add(userId);
NtoIdMappingTable.Add(username, userIds);
}
else
{
userIds.Add(userId);
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var username = Context.User.Identity.Name;
//remove userid from the List
if (NtoIdMappingTable.ContainsKey(username))
{
NtoIdMappingTable.Remove(username);
}
await base.OnDisconnectedAsync(exception);
}
string username = _userRepository.GetUsernameByCostCenter(pmId).ToString();
var userId = NtoIdMappingTable.GetValueOrDefault(username);
await Clients.User(userId.ToString()).SendAsync("SignalReceiver");
}
I'm trying to make a web socket connection between js and java, but I get this answer:
Uncaught DOMException: An attempt was made to use an object that is not, or is no longer, usable
I did it based in some samples on internet. Someone have some idea what can it be?
Project Name is Teste-1
JS Code
var socket = null;
function init(){
socket = new WebSocket("wss://localhost:8080/Teste-1/task");
socket.onmessage = onMessage;
}
function onMessage(event){
var task = JSON.parse(event.data);
if(task.action === "add"){
document.getElementById("point").innerHTML += event.data;
}
}
function teste() {
var action = {
action: "add",
name: "Test",
description: "This is just a test"
};
socket.send(JSON.stringify(action));
}
window.onload = init;
HTML Code
<html>
<head>
<title>Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<button onclick="teste()">Teste</button>
<div id="point"></div>
<script src="websocket.js"></script>
</body>
</html>
JAVA Codes
public class Task implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private String description;
public Task(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
#ServerEndpoint(value="/task")
public class TaskSocket {
#Inject
private TaskSessionHandler handler;
#OnOpen
public void open(Session session){
handler.addSession(session);
}
#OnClose
public void close(Session session){
handler.removeSession(session);
}
#OnError
public void onError(Throwable error){
Logger.getLogger(TaskSocket.class.getName()).log(Level.SEVERE, null, error);
}
#OnMessage
public void handleMessage(String message, Session session) {
try (JsonReader reader = Json.createReader(new StringReader(message))) {
JsonObject jsonMessage = reader.readObject();
if ("add".equals(jsonMessage.getString("action"))) {
Task task = new Task();
task.setName(jsonMessage.getString("name"));
task.setDescription(jsonMessage.getString("description"));
handler.addTask(task);
}
}
}
}
#ApplicationScoped
public class TaskSessionHandler {
//Each client connected to the application has its own session.
private final Set<Session> sessions = new HashSet<>();
private final Set<Task> tasks = new HashSet<>();
public void addSession(Session session) {
sessions.add(session);
for(Task task : tasks){
JsonObject addMessage = createJSON(task);
sendToSession(session, addMessage);
}
}
public void removeSession(Session session) {
sessions.remove(session);
}
public List<Task> getTasks(){
List<Task> list = new ArrayList<Task>(tasks);
return list;
}
public void addTask(Task e){
tasks.add(e);
JsonObject message = this.createJSON(e);
sendToAllConnectedSessions(message);
}
private JsonObject createJSON(Task task){
JsonProvider provider = JsonProvider.provider();
JsonObject message = provider.createObjectBuilder()
.add("action", "add")
.add("name",task.getName())
.add("description",task.getDescription()).build();
return message;
}
private void sendToAllConnectedSessions(JsonObject message) {
for (Session session : sessions) {
sendToSession(session, message);
}
}
private void sendToSession(Session session, JsonObject message) {
try {
session.getBasicRemote().sendText(message.toString());
} catch (IOException ex) {
sessions.remove(session);
Logger.getLogger(TaskSessionHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
I got the same error message due to the websocket.send() being executed before the connection to the webSocket server was established and open.
My contribution would be the advice to make sure messages aren't sent until the connection is open, so that the server actually can receive them.
I cannot tell whether that was the problem in the question asked though. However I faced a very similar problem and ended up here, and wanted to share what worked for me.
In my case the code that didn't work looked like this:
const exampleSocket = new WebSocket('wws://example')
exampleSocket.onopen = function (event) {
console.log('connection is open!')
}
exampleSocket.send(JSON.stringify(msg))
exampleSocket.onmessage = function (event) {
console.log('message event data looks like this: ', event.data)
}
What worked was moving the code that sent a message into the onopen callback function.
const exampleSocket = new WebSocket('wws://example')
exampleSocket.onopen = function (event) {
console.log('connection is open!')
exampleSocket.send(JSON.stringify(msg)) // <- this is the change
}
exampleSocket.onmessage = function (event) {
console.log('message event data looks like this: ', event.data)
}
Sidenote: the readyState property of websocket could be useful when dealing with making sure the server is ready to receive messages.
If it is 1, it means connection is open, so it could be used like this:
if (exampleSocket.readyState === 1) {
console.log("It is safe to send messages now")
}
I am using PrimeFaces 6.0 components:
<p:commandButton type="submit" value="Create Customer"
icon="ui-icon-check"
actionListener="#{newCustomerBean.saveNewCustomer}"
update = "#form"
oncomplete="ajaxUploadFile();"/>
<p:inputText id="saveCustomerId" value ="#{newCustomerBean.savedKundeId}"/>
and I want to execute the following sequence of actions with them:
1.) Execute the actionListener method on the backing bean to save a customer;
2.) Update the form field saveCustomerId with the id of the customer that is saved on step (1). The actionListener method generates a customer Id after the successful save and stores is as a bean property;
3.) Execute the Java Script method ajaxUploadFile()
According to the link
Execution order of events when pressing PrimeFaces p:commandButton
this sequence shall be as I have imagined.
However, in reality, the method
ajaxUploadFile()
is called BEFORE the input field with id saveCustomerId is updated.
Could you help me get the right sequence?
Here is the backing bean:
#ManagedBean
#ViewScoped
public class NewCustomerBean implements Serializable {
public enum KundeTyp {
TYP_NATPERS("Nat. Person"), TYP_FIRMA("Firma");
private String value;
private KundeTyp(String value) {
this.value = value;
}
#Override
public String toString() {
return value;
}
}
private KundeTyp custmerType;
private Map<String, KundeTyp> custmerTypes;
private long savedKundeId;
#Inject
private KundeDBService kundeService;
private String vorname;
private String addresse;
private String steuerNummer;
private String kundeTyp = Integer.MIN_VALUE + "";
#PostConstruct
public void init() {
custmerTypes = new HashMap<String, KundeTyp>();
custmerTypes.put(KundeTyp.TYP_NATPERS.value, KundeTyp.TYP_NATPERS);
custmerTypes.put(KundeTyp.TYP_FIRMA.value, KundeTyp.TYP_FIRMA);
}
public KundeTyp getCustmerType() {
return custmerType;
}
public void setCustmerType(KundeTyp custmerType) {
this.custmerType = custmerType;
}
public Map<String, KundeTyp> getCustmerTypes() {
return custmerTypes;
}
public void setCustmerTypes(Map<String, KundeTyp> custmerTypes) {
this.custmerTypes = custmerTypes;
}
public String getVorname() {
return vorname;
}
public void setVorname(String vorname) {
this.vorname = vorname;
}
public String getAddresse() {
return addresse;
}
public void setAddresse(String addresse) {
this.addresse = addresse;
}
public String getSteuerNummer() {
return steuerNummer;
}
public void setSteuerNummer(String steuerNummer) {
this.steuerNummer = steuerNummer;
}
public String getKundeTyp() {
return kundeTyp;
}
public void setKundeTyp(String kundenTyp) {
this.kundeTyp = kundenTyp;
}
public String saveNewCustomer(ActionEvent e) {
Kunde neuerKunde = null;
switch (this.custmerType) {
case TYP_NATPERS: {
neuerKunde = new NatuerlichePerson();
break;
}
case TYP_FIRMA: {
neuerKunde = new Firma();
((Firma) neuerKunde).setSteuerNummer("123456");
break;
}
}
neuerKunde.setVorname(vorname);
neuerKunde.setAdresse(this.addresse);
try {
savedKundeId = kundeService.saveKunde(neuerKunde);
} catch (ServiceException se) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error",
"Unable to save the new customer: " + se.getMessage()));
}
return null;
}
public long getSavedKundeId() {
return savedKundeId;
}
public void setSavedKundeId(long savedKundeId) {
this.savedKundeId = savedKundeId;
}
}
I would propose a work-around here, since I was not able to find a solution.
Instead of updating the customerId on the front-end, we put it as a session attribute in the HttpSession.
Then, in the UploadServlet, which handles the file upload, we read this attribute and save the image under this customerId.
I need to read the Data off NfcV (ISO 15693) Tags, I already tried the Phonegap-Nfc Plugin from Chariotsolution, but it seems this plugin can only read the TagId and Type off NfcV tags, so i decided i could create my own custom plugin, I first foud the echo Hello World Plugin tutorial which worked fine and without problems, so I looked for Java NfcV reader and found this Java NfcV Reader.
From what I understood so far is, that I need to call the cordova.exec function and extend my Java class from CordovaPlugin, which is working.
I don't know Java since I am a Webdeveloper, which makes it a little hard for me.
This actually looks pretty good but i tried to implement it in the way of the Hello World echo example which didnt work out as planned.
I have now a plugin folder with
Hello.java
package org.apache.cordova.plugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
/**
* This class echoes a string called from JavaScript.
*/
public class Hello extends CordovaPlugin
{
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("hello")) {
String message = args.getString(0);
this.hello(message, callbackContext);
return true;
}
return false;
}
private void hello(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
}
ReadNfcV.java from the link above
package org.apache.cordova.plugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import org.apache.http.util.ByteArrayBuffer;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.nfc.tech.NfcV;
import android.nfc.tech.TagTechnology;
//import android.os.Parcelable;
import android.util.Log;
/**
* #author uhahn
*
*/
public class ReadNfcV extends CordovaPlugin implements TagTechnology {
protected NfcV mynfcv;
// protected Tag mytag; // can be retrieved through mynfcv
private final String TAG=this.getClass().getName();
protected final int maxtries=3;
protected boolean isTainted=true; // Tag info already read?
protected byte[] mysysinfo=null; // NfcV SystemInformation - or generated
protected byte[] myuserdata=null; // buffer user content
protected boolean[] blocktainted; // true when block is to be uploaded to tag
protected byte[] blocklocked; // 0 means writable
protected byte afi=0;
public byte nBlocks=0;
public byte blocksize=0;
public byte[] Id;
public byte[] UID; // becomes valid when a real tag is contacted
public byte DSFID = -1;
public int maxtrans=0; // tag dependent max transceive length
public byte lastErrorFlags=-1; // re-set by each transceive
public byte lastErrorCode=-1; // re-set by each transceive
public byte manuByte=0;
public static final byte BYTE_IDSTART=(byte)0xe0;
public static final byte MANU_TAGSYS=0x04;
public static final HashMap<Byte,String> manuMap = new HashMap<Byte, String>();
static{
manuMap.put(MANU_TAGSYS, "TagSys");
}
/**
* read new NfcV Tag from NFC device
*/
public ReadNfcV(Tag t) {
UID = t.getId(); // sysinfo holds the UID in lsb order - Id will be filled lateron from sysinfo!
// Log.d(TAG,"getId: "+toHex(t.getId()));
mynfcv=NfcV.get(t);
try {
mynfcv.connect();
mysysinfo=getSystemInformation();
// explore Nfcv properties..
//initfields(); // done by getSys..
maxtrans=mynfcv.getMaxTransceiveLength();
DSFID=mynfcv.getDsfId();
Log.d(TAG,nBlocks + " x " + blocksize + " bytes");
blocklocked=new byte[nBlocks]; // init the lock shadow
getMultiSecStatus(0, nBlocks); // and fill from tag
blocktainted=new boolean[nBlocks];
taintblock(0,nBlocks);
// Log.d(TAG,"maxtrans "+maxtrans);
// init space for userdata ?
myuserdata= new byte[nBlocks*blocksize];
} catch (IOException e) {
// TODO Auto-generated catch block
lastErrorFlags=-1;
Log.d(TAG, "MyNfcV failed: "+e.getMessage());
e.printStackTrace();
}
}
/**
* recreate NfcV Tag from log
* #param sysinfo: the logged system info only
*/
public ReadNfcV(String sysinfo){
int startat=0;
sysinfo.toLowerCase(); // ignore case
if(sysinfo.startsWith("0x")){ // lets believe in HEX
startat=2;
}
mysysinfo=hexStringToByteArray(sysinfo.substring(startat));
initfields();
// init space for userdata TODO limit size?
//myuserdata= new byte[nBlocks*blocksize];
isTainted=false;
// TODO fake Tag? mytag = Tag.CREATOR.createFromParcel(???);
}
/**
* recreate NfcV Tag from log
* #param sysinfo: the logged system info
* #param userdata: the logged userdata
*/
public ReadNfcV(String sysinfo, String userdata){
this(sysinfo);
// TODO fake userdata
int startat=0;
userdata.toLowerCase(); // ignore case
if(userdata.startsWith("0x")){ // lets believe in HEX
startat=2;
}
myuserdata=hexStringToByteArray(userdata.substring(startat));
}
/**
* parse system information byte array into attributes
* with respect to the flags found
* DSFID
* AFI
* memsize values (block count and length)
*/
private void initfields(){
byte[] read=mysysinfo;
if((null!=read)&&(12<read.length)&&(0==read[0])){// no error
char flags=(char)read[1]; //s.charAt(1);
// String s=new String(read);
//s.substring(2, 9).compareTo(Id.toString()) // the same?
//set the Id from mysysinfo
int pos=2;
boolean forwardId=false; // the Id field is in lsb order
if(BYTE_IDSTART==read[pos]){
forwardId=true;
manuByte=read[pos+1];
}else if(BYTE_IDSTART==read[pos+7]){
manuByte=read[pos+6];
forwardId=false;
}else
Log.e(TAG,"Id start byte not found where expected");
if(null==Id){ // dont overwrite, if given
Id=new byte[8];
for(int i=0;i<8;i++)
// TODO decide if Id to be reversed (Zebra needs msb order, that is Id[7] changes between tags)
Id[i]=(forwardId? read[pos+i] : read[pos + 7 - i]); //reverse?!
Log.d(TAG,"Id from sysinfo (reversed): "+toHex(Id));
}
pos=10; // start after flags, Infoflags and Id TODO: change if transceive should eat up the error byte
if(0<(flags&0x1)){ // DSFID valid
pos++; // already implemented
}
if(0<(flags&0x2)){ // AFI valid
afi=(byte)read[pos++];//s.charAt(pos++);
}
if(0<(flags&0x4)){ // memsize valid
nBlocks=(byte)(read[pos++]+1);//(s.charAt(pos++)+1);
blocksize=(byte)(read[pos++]+1); //((s.charAt(pos++)&0x1f)+1);
}
}
}
/**
* #return the stored afi byte
*/
public byte getAFI(){
if(isTainted){ // system info not read yet
getSystemInformation(); // fill in the fields
}
return afi;
}
public byte getDsfId(){
// return mynfcv.getDsfId(); // avoid re-reading
return DSFID;
}
public int getblocksize(){
return (int)blocksize;
}
public int getnBlocks(){
return (int)nBlocks;
}
public byte[] getSystemInformation(){
if(isTainted){ // dont reread
mysysinfo=transceive((byte)0x2b);
isTainted=false; // remember: we have read it and found it valid
if(0==lastErrorFlags){// no error
isTainted=false; // remember: we have read it and found it valid
initfields(); // analyze
}}
return mysysinfo;
}
/**
* overload method transceive
* #return resulting array (or error?)
*/
protected byte[] transceive(byte cmd){
return transceive(cmd, -1, -1, null);
}
protected byte[] transceive(byte cmd, int m){
return transceive(cmd, m, -1, null);
}
protected byte[] transceive(byte cmd, int m ,int n){
return transceive(cmd, m, n, null);
}
/**
* prepare and run the command according to NfcV specification
* #param cmd command byte
* #param m command length
* #param n
* #param in input data
* #return
*/
protected byte[] transceive(byte cmd,int m, int n, byte[] in){
byte[] command;
byte[] res="transceive failed message".getBytes();
ByteArrayBuffer bab = new ByteArrayBuffer(128);
// flags: bit x=adressed,
bab.append(0x00);
bab.append(cmd); // cmd byte
// 8 byte UID - or unaddressed
// bab.append(mytag.getId(), 0, 8);
// block Nr
if(-1!=m)bab.append(m);
if(-1!=n)bab.append(n);
if(null!=in)bab.append(in, 0, in.length);
command=bab.toByteArray();
Log.d(TAG,"transceive cmd: "+toHex(command));
// Log.d(TAG,"transceive cmd length: "+command.length);
// TODO background!
try {
if(!mynfcv.isConnected()) return res;
for(int t=maxtries;t>0;t++){ // retry reading
res=mynfcv.transceive(command);
if(0==res[0]) break;
}
}
catch (TagLostException e){ //TODO roll back user action
Log.e(TAG, "Tag lost "+e.getMessage());
try {
mynfcv.close();
} catch (IOException e1) {
e1.printStackTrace();
}
return e.getMessage().getBytes();
}
catch (IOException e) {
Log.d(TAG, "transceive IOEx: "+e.getMessage()+toHex(res));
// e.printStackTrace();
return e.getMessage().getBytes();
}
finally{
Log.d(TAG,"getResponseFlags: "+mynfcv.getResponseFlags());
lastErrorFlags=res[0];
Log.d(TAG,"Flagbyte: "+String.format("%2x", lastErrorFlags));
if(0!=lastErrorFlags){
lastErrorCode=res[1];
Log.d(TAG,"ErrorCodebyte: "+String.format("%2x", lastErrorCode));
}
}
if(0==mynfcv.getResponseFlags())
return (res);
else
// return new String("response Flags not 0").getBytes();
return res;
}
public void taintblock(int i, int n){
for(int j=0;j<n;j++)
setblocktaint(j,true);
}
public void taintblock(int i){
setblocktaint(i,true);
}
protected void setblocktaint(int i, boolean b){
blocktainted[i]=b;
}
/* (non-Javadoc)
* #see android.nfc.tech.TagTechnology#getTag()
*
*/
#Override
public Tag getTag() {
// TODO Auto-generated method stub
//return mytag;
return mynfcv.getTag();
}
/* (non-Javadoc)
* #see android.nfc.tech.TagTechnology#close()
*/
#Override
public void close() throws IOException {
try {
mynfcv.close();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "close failed: "+e.getMessage());
e.printStackTrace();
}
}
/* (non-Javadoc)
* #see android.nfc.tech.TagTechnology#connect()
*/
#Override
public void connect() throws IOException {
try {
mynfcv.connect();
} catch (IOException e) {
lastErrorFlags=-1; // TODO discriminate error states
Log.d(TAG,"connect failed: "+e.getMessage());
e.printStackTrace();
}
}
/* (non-Javadoc)
* #see android.nfc.tech.TagTechnology#isConnected()
*/
#Override
public boolean isConnected() {
// TODO Auto-generated method stub
// mynfcv.getDsfId();
return mynfcv.isConnected(); // better?
}
public byte[] readSingleBlock(int i){
byte[] read=transceive((byte)0x20,i);
setblocktaint(i,false); // remember we read this block
if(0!=lastErrorFlags)return read; // TODO not so ignorant..
byte[] res=new byte[read.length-1]; // drop the (0) flag byte TODO: in transceive?
for (int l = 0; l < read.length-1; l++) {
res[l]=read[l+1];
myuserdata[i*blocksize+l]=res[l]; // sort block into our buffer
}
return res;
}
/**
*
* #param i starting block number
* #param j block count
* #return block content concatenated
*/
public byte[] readMultipleBlocks(int i,int j){
if(0==blocksize){
Log.e(TAG,"readMult w/o initfields?");
getSystemInformation(); // system info was not read yet
}
byte[] read = transceive((byte)0x23,i,j);
if(0!=read[0])return read; // error flag set: TODO left as exercise..
byte[] res=new byte[read.length-1]; // drop the (0) flag byte
for (int l = 0; l < read.length-1; l++) {
res[l]=read[l+1];
myuserdata[i*blocksize+l]=res[l]; // sort block into our buffer
}
if(res.length<j*blocksize) return read; // da fehlt was
for (int k = i; k < j; k++) { // all blocks we read
setblocktaint(k, false); // untaint blocks we read
// #TODO reverting block order should be done on demand - or under user control (done again in DDMData)
// reverse(res,k*blocksize,blocksize); // swap string positions
}
return res;
}
public byte[] getMultiSecStatus(int i,int n){
byte[] read = transceive((byte)0x2c,i,n-1);
Log.d(TAG,"secstatus "+toHex(read));
if(0!=read[0])return read;
int startat=1; // TODO transceive will skip the error field soon
for(int j=0;j<nBlocks;j++)
blocklocked[j]=read[startat+i+j];
return read;
}
/**
* move anywhere to utils
* #param s
* #return
*/
public static String toHex(byte[] in){
String text=String.format("0x");
for (byte element : in) {
text=text.concat(String.format("%02x", element));
}
return text;
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}
my hello.js file
var hello = {
world: function(str, callback) {
cordova.exec(callback, function(err) {
callback('Nothing to hello.');
}, "Hello", "hello", [str]);
}
}
var ReadNfcV = {
read: function (str, callback) {
cordova.exec(callback, function (err) {
callback('Nothing to hello.');
}, "Hello", "hello", [str]);
}
}
module.exports = hello;
module.exports = ReadNfcV;
and my plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
id="org.apache.cordova.plugin"
version="0.1.0">
<js-module src="hello.js" name="hello">
<clobbers target="hello" />
</js-module>
<!-- Android -->
<platform name="android">
<source-file src="Hello.java" target-dir="src/org/apache/cordova/plugin" />
<source-file src="ReadNfcV.java" target-dir="src/org/apache/cordova/plugin" />
<config-file target="res/xml/config.xml" parent="/*">
<feature name="Hello" >
<param name="android-package" value="org.apache.cordova.plugin.Hello"/>
</feature>
</config-file>
</platform>
</plugin>
I was able to deploy the app so I can test a bit, My problem is that I dont really understand how i can call the ReadNfc class from the ReadNfcV.java file from within my app via javascript. I just did the same as in the Tutorial but now the hello.World function is not a function anymore so i guess i did smth wrong in my hello.js file. I would really appreciate it if someone could help and explain me how i can call my java class via javascript and then return the result from the java class back to my javascript. I looked 2 Days for an already existing plugin but didnt find anything on that subject but the phonegap-nfc plugin.
Kind regards Christopher
Update Day1
I added tech.NfcV to the Import List
import android.nfc.tech.NfcV;
Changed the execute function as suggested
#Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute " + action);
if (!getNfcStatus().equals(STATUS_NFC_OK)) {
callbackContext.error(getNfcStatus());
return true; // short circuit
}
createPendingIntent();
if (action.equals(REGISTER_DEFAULT_TAG)) {
addTechList(new String[]{NfcV.class.getName()}); //changed this form Mifare to NfcV
registerDefaultTag(callbackContext);
} else if (action.equalsIgnoreCase(INIT)) {
init(callbackContext);
} else {
// invalid action
return false;
}
return true;
}
Problem seems to be that I get Invalid action returned at the moment so something is wrong here
I changed the registerDefault function to
private void registerDefaultTag(CallbackContext callbackContext) {
addTechFilter();
callbackContext.success();
}
And i changed the Parse Message function to
void parseMessage() {
cordova.getThreadPool().execute(new Runnable() {
#Override
public void run() {
Log.d(TAG, "parseMessage " + getIntent());
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "action " + action);
if (action == null) {
return;
}
if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)){
Tag tagFromIntent = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
NfcV mfc = NfcV.get(tagFromIntent);
fireTagEvent(tag);
}
/*if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
Ndef ndef = Ndef.get(tag);
fireNdefEvent(NDEF_MIME, ndef, messages);
} else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
for (String tagTech : tag.getTechList()) {
Log.d(TAG, tagTech);
if (tagTech.equals(NdefFormatable.class.getName())) {
fireNdefFormatableEvent(tag);
} else if (tagTech.equals(Ndef.class.getName())) { //
Ndef ndef = Ndef.get(tag);
fireNdefEvent(NDEF, ndef, messages);
}
}
}
if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
fireTagEvent(tag);
}*/
setIntent(new Intent());
}
});
}
So currently i get the error invalid action as soon as i click on start Listening and calling the Taglisteners in Javascript atm I use all 4 different Listeners to see if any work. It seems that I need to write a new fireEvent function specific for NfcV since the existing ones arent working
Update 2
I have managed to compile the plugin and deploy the app but nothing is happening i am not getting a Tag object back
The parse Message Function
void parseMessage() {
cordova.getThreadPool().execute(new Runnable() {
#Override
public void run() {
Log.d(TAG, "parseMessage " + getIntent());
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "action " + action);
if (action == null) {
return;
}
if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)){
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if(tag != null){
byte[] id = tag.getId();
// set up read command buffer
byte blockNo = 0; // block address
byte[] readCmd = new byte[3 + id.length];
readCmd[0] = 0x20; // set "address" flag (only send command to this tag)
readCmd[1] = 0x20; // ISO 15693 Single Block Read command byte
System.arraycopy(id, 0, readCmd, 2, id.length); // copy ID
readCmd[2 + id.length] = blockNo; // 1 byte payload: block address
NfcV tech = NfcV.get(tag);
if (tech != null) {
// send read command
try {
tech.connect();
byte[] data = tech.transceive(readCmd);
fireTagEvent(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
tech.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
setIntent(new Intent());
}
}
});
}
My fireTagEvent
private void fireTagEvent(byte[] data) {
String s2 = new String(data);
String command = MessageFormat.format(javaScriptEventTemplate, TAG_DEFAULT, s2);
Log.v(TAG, s2);
this.webView.sendJavascript(s2);
}
The execute function
#Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute " + action);
if (!getNfcStatus().equals(STATUS_NFC_OK)) {
callbackContext.error(getNfcStatus());
return true; // short circuit
}
createPendingIntent();
if (action.equals(REGISTER_DEFAULT_TAG)) {
addTechList(new String[]{NfcV.class.getName()});
registerDefaultTag(callbackContext);
} else if (action.equalsIgnoreCase(INIT)) {
init(callbackContext);
} else {
// invalid action
return false;
}
return true;
}
Thats pretty much it the app starts the "addTagDiscoveredListener" is registered but im not getting any Object back so either the nfcv tag is not read or i just dont get anything back not really sure...
I used the Chariotsolution plugin as a start to build my own nfc reading plugin. I think it would save you much time if you don't start from scratch.
There are many things you can remove because the original plugin only deals with NDEF tags, but removing lines is faster than re-inventing the wheel.
It's a bit old in my head, so I'm not sure I can explain everything right...
My nead was to read info in Mifare classic tags, but maybe you can adapt for your needs...
So, if you look at NfcPlugin.java, in the execute function, all I kept was the code for the actions REGISTER_DEFAULT_TAG and INIT.
Updated the code in REGISTER_DEFAULT_TAG to register the listening for mifare classic tag. And modified registerDefaultTag function to call addTechFilter instead of addTagFilter.
So this leaves us with
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute " + action);
if (!getNfcStatus().equals(STATUS_NFC_OK)) {
callbackContext.error(getNfcStatus());
return true; // short circuit
}
createPendingIntent();
if (action.equals(REGISTER_DEFAULT_TAG)) {
addTechList(new String[]{MifareClassic.class.getName()});
registerDefaultTag(callbackContext);
} else if (action.equalsIgnoreCase(INIT)) {
init(callbackContext);
} else {
// invalid action
return false;
}
return true;
}
private void registerDefaultTag(CallbackContext callbackContext) {
addTechFilter();
callbackContext.success();
}
Now what you need to understand is that once you called from the js the init function, the parseMessage function of the plugin will be called each time the device sees a nfc tag.
So in the parseMessage function I have a test
if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED))
in wich I have all the code to deal with my tag.
In that code can get info from the tag in the intent using something like this :
Tag tagFromIntent = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mfc = MifareClassic.get(tagFromIntent);
and then depending on your treatment you can call either fireErrorEvent or a fire...Event of your own that would return the data to the javascript using the webView.sendJavascript function.
I'm running out of time to detail the js part.
Not sure sure if it will help you or if it's the way you want to go (don't know how the tag you're using is working). Let me know if it helps you and if you need more details.
Okay so I finally managed to get it working
In the Phonegap-nfc.js I added an eventlistener for the NFCV tag
addNfcVListener: function (callback, win, fail) {
document.addEventListener("nfcv", callback, false);
cordova.exec(win, fail, "NfcPlugin", "registerNfcV", []);
},
The matching Execute function looks like this
#Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute " + action);
if (!getNfcStatus().equals(STATUS_NFC_OK)) {
callbackContext.error(getNfcStatus());
return true; // short circuit
}
createPendingIntent();
if (action.equalsIgnoreCase(REGISTER_NFCV)) {
registerNfcV(callbackContext);
}else {
// invalid action
return false;
}
return true;
}
Here is where the Tag gets added to the techlist
private void registerNfcV(CallbackContext callbackContext) {
addTechList(new String[]{NfcV.class.getName()});
callbackContext.success();
}
Here the Tag gets parsed and fires an event
void parseMessage() {
cordova.getThreadPool().execute(new Runnable() {
#Override
public void run() {
Log.d(TAG, "parseMessage " + getIntent());
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "action " + action);
if (action == null) {
return;
}
if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)){
NfcvData ma;
Tag tagFromIntent = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
NfcV mfc = NfcV.get(tagFromIntent);
Tag tag = mfc.getTag();
fireNfcVReadEvent(NFCV, mfc, messages);
}
setIntent(new Intent());
}
});
}
Then this event is called
private void fireNfcVReadEvent(String type, NfcV nfcv, Parcelable[] messages) {
JSONObject jsonObject = buildNfcVReadJSON(nfcv, messages);
String tag = jsonObject.toString();
String command = MessageFormat.format(javaScriptEventTemplate, type, tag);
Log.v(TAG, command);
this.webView.sendJavascript(command);
}
Which sends the Taginformation back to my Javascript
It appears that you are not sending any data back in you callback, just the cordova SUCCESS. callbackContext.success(returndatahere); See the cordova plugin spec for supported data types.
I'm playing around with the WebView in the Android browser, but is wondering if anyone have used the browser together with the html5 to use the camera and gps of the local device?
Or will I need to do a Javascript connection to the Java source code for this?
Will this also work in IOS?
This is, without using PhoneGap.
best,
Henrik
It might be a bit late, but just in case I will give you an example to do that.
(Camera)
1////add this to your webChromeClient
myWebView.setWebChromeClient(new WebChromeClient()
{
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType )
{
mUploadMessage = uploadMsg;
Intent cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE");
Calendar cal = Calendar.getInstance();
cal.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
File photo = new File(Environment.getExternalStorageDirectory(), sdf.format(cal.getTime()) +".jpg");
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
picUri = Uri.fromFile(photo);
startActivityForResult(cameraIntent, TAKE_PICTURE);
}
});
2///add this function
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch(requestCode)
{
case TAKE_PICTURE:
if(resultCode == Activity.RESULT_OK)
{
Uri mypic = picUri;
mUploadMessage.onReceiveValue(mypic);
mUploadMessage = null;
}
else
{
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
default:
{
return;
}
}
}
3// and the HTML looks like this
<input type="file" id="files" name="files" accept="image/*" capture="camera" >
The above code will open the native camera app.
P.S. This is a mix of code from different sources.
It's worth pointing out where the code shown here should live, as it's not obvious for a newbie - it goes in your Activity class, i.e.
public class MyActivity extends Activity {
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
...
}
#Override
public void onCreate(Bundle savedInstanceState) {
...
myWebView.setWebChromeClient(new WebChromeClient()
{
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
...
}
});
}
}
In my case, I've override the makeWebViewEngine method in order to be able to modify the CordovaWebViewEngine casted to a SystemWebViewEngine as follows (based on this answer):
public class MainActivity extends CordovaActivity {
private static final int FILECHOOSER_RESULTCODE = 12345;
private ValueCallback<Uri> mUploadMessage;
private Uri mPicUri;
private ValueCallback<Uri[]> mFilePathCallback;
private String mCameraPhotoPath;
#Override
protected CordovaWebViewEngine makeWebViewEngine() {
SystemWebViewEngine systemWebViewEngine = (SystemWebViewEngine) super.makeWebViewEngine();
SystemWebView systemWebView = (SystemWebView) systemWebViewEngine.getView();
systemWebView.setWebChromeClient(new SystemWebChromeClient(systemWebViewEngine) {
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE");
Calendar cal = Calendar.getInstance();
cal.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
File photo = new File(Environment.getExternalStorageDirectory(), sdf.format(cal.getTime()) + ".jpg");
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
mPicUri = Uri.fromFile(photo);
startActivityForResult(cameraIntent, FILECHOOSER_RESULTCODE);
}
// For Android 5.0+
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
// Double check that we don't have any existing callbacks
if (mFilePathCallback != null) {
mFilePathCallback.onReceiveValue(null);
}
mFilePathCallback = filePathCallback;
// Set up the take picture intent
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
} catch (IOException ex) {
// Error occurred while creating the File
Log.e(TAG, "Unable to create Image File", ex);
}
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(photoFile));
} else {
takePictureIntent = null;
}
}
// Set up the intent to get an existing image
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");
// Set up the intents for the Intent chooser
Intent[] intentArray;
if (takePictureIntent != null) {
intentArray = new Intent[]{takePictureIntent};
} else {
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, MainActivity.FILECHOOSER_RESULTCODE);
return true;
}
});
return systemWebViewEngine;
}
// …
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
}
}
Furthermore, based on this other answer, I've had to specify the openFileChooser method for Android 4.1 and, for Android 5.0+, I've had to specify the onShowFileChooser method.
By the way, I've modified the onActivityResult method as follows:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (Build.VERSION.SDK_INT >= 21) {
if (requestCode != FILECHOOSER_RESULTCODE || mFilePathCallback == null) {
super.onActivityResult(requestCode, resultCode, intent);
return;
}
Uri[] results = null;
// Check that the response is a good one
if (resultCode == Activity.RESULT_OK) {
String dataString = intent.getDataString();
if (dataString != null) {
results = new Uri[]{Uri.parse(dataString)};
} else {
// If there is not data, then we may have taken a photo
if (mCameraPhotoPath != null) {
results = new Uri[]{Uri.parse(mCameraPhotoPath)};
}
}
}
mFilePathCallback.onReceiveValue(results);
mFilePathCallback = null;
} else {
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == this.mUploadMessage) {
return;
}
Uri result;
if (resultCode != RESULT_OK) {
result = null;
} else {
result = intent == null ? this.mPicUri : intent.getData(); // retrieve from the private variable if the intent is null
}
this.mUploadMessage.onReceiveValue(result);
this.mUploadMessage = null;
}
}
}
Thanks to #Will and #Christian for pointing the good direction in this answer BTW :)