Skip to content

Instantly share code, notes, and snippets.

@SignpostMarv
Last active February 28, 2016 15:15
Show Gist options
  • Save SignpostMarv/ebd17ab0ebf102daff22 to your computer and use it in GitHub Desktop.
Save SignpostMarv/ebd17ab0ebf102daff22 to your computer and use it in GitHub Desktop.
dynamicish crosswalk .net extension api
// Based on https://github.com/crosswalk-project/tizen-extensions-crosswalk/blob/56953350a1ac0196183a51a5682d9fb9a388ea64/src/nfc/nfc_api.js
var g_next_async_call_id = 1;
var g_async_calls = {};
function AsyncCall(resolve, reject) {
this.resolve = resolve || function placeholderRes(res) {
console.log(res);
};
this.reject = reject || function placeholderRej(err) {
console.error(err);
};
}
function createPromise(msg) {
var promise = new Promise(function (resolve, reject) {
g_async_calls[g_next_async_call_id] = new AsyncCall(resolve, reject);
});
msg.asyncCallId = g_next_async_call_id;
extension.postMessage(JSON.stringify(msg));
++g_next_async_call_id;
return promise;
}
extension.setMessageListener(function (json) {
document.body.appendChild(document.createTextNode(json));
});
exports = new Promise(function (resolve, reject) {
try {
extension.setMessageListener(function handleMsg(json) {
try {
var msg = JSON.parse(json);
var methods = [];
var api = {};
console.log(msg);
if (msg instanceof Array) {
if (msg.length < 1) {
reject(new Error('No API Methods available!: ' + json));
} else {
methods = msg.filter(function filterMsg(maybeMethod) {
return (typeof (maybeMethod) === 'string');
});
if (methods.length !== msg.length) {
reject(new Error('API Method length mismatch! Found ' + methods.length + ', expected ' + msg.length + '! :' + json));
} else {
methods.forEach(function feMethod(methodName) {
api[methodName] = function apiMethod() {
return createPromise({
method: methodName,
arguments: arguments,
});
};
});
extension.setMessageListener(function handleMsg(json) {
var msg = JSON.parse(json);
if (
typeof (msg) === 'object' &&
Object.keys(msg).indexOf('asyncCallId') >= 0 &&
typeof (g_async_calls[msg.asyncCallId]) !== 'undefined' &&
g_async_calls[msg.asyncCallId] instanceof AsyncCall
) {
if (Object.keys(msg).indexOf('success') < 0) {
g_async_calls[msg.asyncCallId].reject(msg);
} else {
g_async_calls[msg.asyncCallId].resolve(msg);
}
delete g_async_calls[msg.asyncCallId];
} else {
console.error('Unexpected message receieved: ' + json);
}
});
resolve(api);
}
}
} else {
reject(new Error('JSON was not an array! :' + json));
}
} catch (err) {
reject(err);
}
});
extension.postMessage("GETAPI");
} catch (err) {
reject(err);
}
});
echo.catch(
function(err){
console.error(err);
}
).then(
function(api){
api.echo('foo', 'bar').then(
function(resp){
console.log(resp);
}
).catch(function(err){
console.error(err);
});
}
);
{
"devDependencies": {
"crosswalk-app-tools": "^0.9.1"
},
"scripts": {
"xwalk-create-windows": "mkdir xwalk && cd xwalk && crosswalk-app create org.crosswalkProject.windows --platform=windows && mv org.crosswalkProject.windows windows"
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
</packages>
<?xml version="1.0" ?>
<Prebuild version="1.10" xmlns="http://dnpb.sourceforge.net/schemas/prebuild-1.10.xsd">
<Solution activeConfig="Debug" name="xwalk_dotnet" path="./">
<Configuration name="Debug">
<Options>
<CompilerDefines>TRACE;DEBUG</CompilerDefines>
<OptimizeCode>false</OptimizeCode>
<CheckUnderflowOverflow>false</CheckUnderflowOverflow>
<AllowUnsafe>true</AllowUnsafe>
<WarningLevel>4</WarningLevel>
<WarningsAsErrors>false</WarningsAsErrors>
<SuppressWarnings/>
<OutputPath>bin</OutputPath>
<DebugInformation>true</DebugInformation>
<IncrementalBuild>true</IncrementalBuild>
<NoStdLib>false</NoStdLib>
</Options>
</Configuration>
<Configuration name="Release">
<Options>
<CompilerDefines>TRACE</CompilerDefines>
<OptimizeCode>true</OptimizeCode>
<CheckUnderflowOverflow>false</CheckUnderflowOverflow>
<AllowUnsafe>true</AllowUnsafe>
<WarningLevel>4</WarningLevel>
<WarningsAsErrors>false</WarningsAsErrors>
<SuppressWarnings/>
<OutputPath>bin</OutputPath>
<DebugInformation>false</DebugInformation>
<IncrementalBuild>true</IncrementalBuild>
<NoStdLib>false</NoStdLib>
</Options>
</Configuration>
<Project frameworkVersion="v4_5" name="xwalk_dotnet" path="./" type="Library">
<Configuration name="Debug">
<Options>
<OutputPath>xwalk/windows/prj/windows/</OutputPath>
</Options>
</Configuration>
<Configuration name="Release">
<Options>
<OutputPath>xwalk/windows/prj/windows/</OutputPath>
</Options>
</Configuration>
<ReferencePath>./cs/libs/</ReferencePath>
<Reference name="Microsoft.CSharp"/>
<Reference name="System" />
<Reference name="System.Core"/>
<Reference name="System.Reflection"/>
<Reference name="Newtonsoft.Json" localCopy="true"/>
<Files>
<Match pattern="./cs/*.cs" recurse="true"/>
<Match pattern="Properties/*.resx" recurse="false"/>
</Files>
</Project>
</Solution>
</Prebuild>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="api" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\cs\api.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
</root>
Prebuild.exe /target vs2010
// Copyright (c) 2015 Intel Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
using System;
using System.Collections.Generic;
namespace xwalk
{
public class XWalkExtension
{
public XWalkExtension()
{
XWalkExtensionInstance.InstanceReady += (sender, e) =>
{
((XWalkExtensionInstance)sender).RegisterApiMethods(typeof(XWalkExtension));
};
}
public String ExtensionName()
{
return "echo";
}
public String ExtensionAPI()
{
return xwalk_dotnet.Properties.Resources.api;
}
[XWalkExtensionMethod("echo")]
public static Dictionary<string, object> echoResponse(Dictionary<string, object> message)
{
return message;
}
}
}
// Copyright (c) 2015 Intel Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
namespace xwalk
{
public class XWalkExtensionInstance
{
public static EventHandler InstanceReady;
private int addinsCount = 0;
private Exception addinsException = null;
public XWalkExtensionInstance(dynamic native)
{
native_ = native;
InstanceReady.Invoke(this, null);
}
public void HandleMessage(String message)
{
handleFromJs(message);
}
public void HandleSyncMessage(String message)
{
handleFromJs(message, false);
}
private dynamic native_;
public void sendToJs(String message, bool sendAsync = true)
{
if (sendAsync)
{
native_.PostMessageToJS(message);
}
else
{
native_.SendSyncReply(message);
}
}
public void sendToJs(Dictionary<string, object> message, bool sendAsync)
{
sendToJs(JsonConvert.SerializeObject(message), sendAsync);
}
public void sendToJs(object[] message, bool sendAsync)
{
sendToJs(JsonConvert.SerializeObject(message), sendAsync);
}
public void sendToJs(int message, bool sendAsync)
{
sendToJs(JsonConvert.SerializeObject(message), sendAsync);
}
public void handleFromJs(String json, bool wasAsync = true)
{
Dictionary<string, object> response = new Dictionary<string, object>();
try
{
if (json == "GETAPI")
{
RegisterApiMethods(typeof(XWalkExtension));
sendToJs(GetApiMethodNames, wasAsync);
return;
}
else if (json == "GETADDINSCOUNT")
{
if (addinsException != null)
{
sendToJs(addinsException.Message, wasAsync);
}
else
{
sendToJs(addinsCount, wasAsync);
}
}
Dictionary<string, object> message = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
if (message.ContainsKey("asyncCallId"))
{
response["asyncCallId"] = message["asyncCallId"];
}
if (message.ContainsKey("method"))
{
object requestedApiMethod;
if (!message.TryGetValue("method", out requestedApiMethod))
{
throw new Exception("Failed to identify API method! :" + json);
}
else if (!(requestedApiMethod is string))
{
throw new Exception("API method was not a string! :" + json);
}
Dictionary<string, object> methodResponse = CallApiMethod((string)requestedApiMethod, message);
foreach (string key in methodResponse.Keys)
{
response[key] = methodResponse[key];
}
}
else
{
throw new Exception("Unsupported API call! :" + json);
}
}
catch (Exception err)
{
response["error"] = err.Message;
}
if (!response.ContainsKey("error"))
{
response["success"] = true;
}
sendToJs(response, wasAsync);
}
private static Type expectedReturnType
{
get
{
return typeof(Dictionary<string, object>);
}
}
private static Dictionary<string, Delegate> apiMethods = new Dictionary<string, Delegate>();
public string[] GetApiMethodNames
{
get
{
return apiMethods.Keys.ToArray();
}
}
public Dictionary<string, object> CallApiMethod(string methodName, Dictionary<string, object> methodArgs)
{
if (!apiMethods.ContainsKey(methodName))
{
throw new ArgumentException("The specified method does not exist!");
}
return (Dictionary<string, object>)apiMethods[methodName].DynamicInvoke(
new object[] {
(object)methodArgs
}
);
}
public void RegisterApiMethods(Type t)
{
XWalkExtensionMethod methodAttr;
bool anyAdded = false;
Type expectedReturnType = typeof(Dictionary<string, object>); ;
MethodInfo[] methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static);
if (methods.Length < 1)
{
throw new ArgumentException("Type has no methods!");
}
foreach (MethodInfo method in methods)
{
if (method.GetCustomAttributes(typeof(XWalkExtensionMethod), true).Any())
{
if (method.ReturnType != expectedReturnType)
{
throw new ArgumentException("Methods are expected to return Dictionary<string, object>");
}
methodAttr = method.GetCustomAttribute<XWalkExtensionMethod>();
try
{
apiMethods[methodAttr.GetMethodName()] = CreateDelegate(method);
}
catch (Exception ex)
{
throw new Exception("Could not create delegate!", ex);
}
anyAdded = true;
}
}
if (!anyAdded)
{
throw new ArgumentException("Specified type does not specify any API methods!");
}
}
private static Delegate CreateDelegate(MethodInfo method)
{
if (method == null)
{
throw new ArgumentNullException("Cannot create delegate from null method");
}
ParameterExpression[] parameters = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
MethodCallExpression call = Expression.Call(null, method, parameters);
return Expression.Lambda(call, parameters).Compile();
}
}
}
using System;
namespace xwalk
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class XWalkExtensionMethod : Attribute
{
private string _name;
public XWalkExtensionMethod(string name)
{
_name = name;
}
public string GetMethodName()
{
return _name;
}
}
}
@SignpostMarv
Copy link
Author

r2 invoked with:

echo.api.catch(
    function(err){
        console.error(err);
    }
).then(
    function(api){
        api.echo('foo', 'bar').then(
            function(resp){
                console.log(resp);
            }
        ).catch(function(err){
            console.error(err);
        });
    }
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment