Windows 8 looks interesting! The hats (^) will take time to get used, but this by far looks the best upgrade targeted to devices that are mobile.
To understand this better, developed a small windows 8 metro app.
The tasks were
1. Connect to saleforce.com for get a OAuth token
2. Pass the token to a REST chatter API
3. Get the result and parse it.
This tutorial should address the first two points
For OAuth token, windows 8 has a good mechanism using the "WebAuthenticationBroker". The following code uses the authentication broker to call Salesforce.com portal.
void TestRest::MainPage::Button_Click_1(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
//On click of button put the salesforce .com stuff to authenticate
String^ facebookURL = "https://login.salesforce.com/services/oauth2/authorize?client_id=";
//this is the key you get from salesforce...use your account specific key..
String^ clientID;
clientID = "3MVxxxxxxxxxxxxxxxxxxlCKNlWdhyd_JKQdo";
facebookURL += clientID + "&redirect_uri=https%3A%2F%2Flogin.salesforce.com%2Fservices%2Foauth2%2Fsuccess";
facebookURL += "&response_type=token&";
facebookURL += "&display=touch&";
try
{
auto startURI = ref new Uri(facebookURL);
auto endURI = ref new Uri("https://login.salesforce.com/services/oauth2/success");
create_task(WebAuthenticationBroker::AuthenticateAsync(WebAuthenticationOptions::None, startURI, endURI)).then([this](WebAuthenticationResult^ result)
{
String^ statusString;
switch (result->ResponseStatus)
{
case WebAuthenticationStatus::ErrorHttp:
statusString = "ErrorHttp: " + result->ResponseErrorDetail;
break;
case WebAuthenticationStatus::Success:
{
statusString = "Success";
//call the rest api here
m_CSfdcRESTMgr.setSfdcParameters(result->ResponseData);
}
break;
case WebAuthenticationStatus::UserCancel:
statusString = "UserCancel";
break;
}
//resultTextBox->Text += "\nStatus returned by WebAuth broker: " + statusString;
});
}
catch (Exception^ ex)
{
//resultTextBox->Text += "Error launching WebAuth " + ex->Message;
return;
}
}
this is for my developer account on Salesforce, you need to add your own client ID
m_CSfdcRESTMgr is a variable for the class CSfdcRESTMgr. This class manages the API code for Salesforce chatter.
on success SFDC sends you a string that contains
1. OAuth token
2. Refresh token
3. Instance URL.
the code below parses the tokens, important aspect in the code is to remove escape characters. I am sure that part can be optimized further.
void CSfdcRESTMgr::setSfdcParameters(Platform::String ^ webRespone)
{
std ::wstring strReftoken;
strReftoken.assign(L"&refresh_token=");
wstring strInstanceURL = L"&instance_url=";
wstring strId = L"&id=";
wstring strIssuedAT = L"&issued_at=";
wstring strSignature = L"&signature=";
wstring strScope = L"&scope=";
try
{
std::wstring strData ;
strData.assign(webRespone->Data());
//access token
int i = strData.find('=');
++i; //move over =
int j = strData.find(strReftoken);
m_stSFInfo.access_token = strData.substr(i,j-i);
//escape the access token as well
int b = m_stSFInfo.access_token.find(L"%21");
m_stSFInfo.access_token.replace(b,3,L"!");
//refresh token
j += strReftoken.length();
i = strData.find(strInstanceURL);
m_stSFInfo.refresh_token = strData.substr(j,i-j);
i+= strInstanceURL.length();
//instance URL
j = strData.find(strId);
m_stSFInfo.instance_URL = strData.substr(i,j-i);
//m_stSFInfo.instance_URL = L"https://ap1.salesforce.com";
///special function starts to remove %2F with / and %3A with :
int a = m_stSFInfo.instance_URL.find(L"%2F");
m_stSFInfo.instance_URL.replace(a,3,L"/");
a = m_stSFInfo.instance_URL.find(L"%2F");
m_stSFInfo.instance_URL.replace(a,3,L"/");
a = m_stSFInfo.instance_URL.find(L"%3A");
if(a)
m_stSFInfo.instance_URL.replace(a,3,L":");
//
j+= strId.length();
//id
i = strData.find(strIssuedAT);
m_stSFInfo.id = strData.substr(j,i-j);
////
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id.replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%3A");
if(a)
m_stSFInfo.id.replace(a,3,L":");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
////
i += strIssuedAT.length();
//issued at
j = strData.find(strSignature);
m_stSFInfo.issuedAt = strData.substr(i,j-i);
j+= strSignature.length();
//signature
i = strData.find(strScope);
m_stSFInfo.signature = strData.substr(j,i-j);
i+= strScope.length();
//scope
j = strData.length();
m_stSFInfo.scope = strData.substr(i,j-i);
}
catch(Exception^ ex)
{
//GroupTextBox->Text += "error in parse" + ex->Message;
}
}
So now you have the token, pass that to the REST API.
std::wstring CSfdcRESTMgr::fireHTTPRequest(const wstring& methodName,const wstring& uri)
{
m_cancelHttpRequestSource = cancellation_token_source();
auto token = m_cancelHttpRequestSource.get_token();
wstring oauthtoken = L"OAuth ";
oauthtoken+= m_stSFInfo.access_token;
wstring uriToPass = m_stSFInfo.instance_URL + uri;
m_httpRequest = HTTPUtil::StartDownload(
methodName,
uriToPass,
L"Authorization",
oauthtoken,
token);
// Create a continuation that shows the results on the UI.
// Therefore, schedule the continuation to run on the current context.
m_httpRequest.then([this, token](task<tuple<HRESULT, wstring>> previousTask) {
if (token.is_canceled())
{
m_strResponse = L"The operation was canceled";
cancel_current_task();
}
else
{
HRESULT hr = get<0>(previousTask.get());
if (FAILED(hr))
{
m_strResponse = L"The operation failed";
// TODO: Handle the error further.
}
else
{
auto text = ref new String(get<1>(previousTask.get()).c_str());
m_strResponse = text->Data();
}
}
}, task_continuation_context::use_current());
return L" ";
}
void CSfdcRESTMgr::getLatestFeed()
{
wstring strURI = L"/services/data/v24.0/chatter/feeds/company/feed-items";
fireHTTPRequest(L"GET",strURI);
}
the response is got in variable m_strResponse .
I have created a class to call the HTTP request and the associated callback
//implementation of the static method
// @@ToDo - what happen in case of multiple headers ??
task<tuple<HRESULT, wstring>>HTTPUtil::StartDownload(
const wstring& method,
const wstring& uri,
const std::wstring& headerID,
const std::wstring& headerValue,
cancellation_token token)
{
//standard initiation in COM
ComPtr<IXMLHTTPRequest2>request;
HRESULT hr = CoCreateInstance(CLSID_XmlHttpRequest,nullptr,CLSCTX_INPROC,
IID_PPV_ARGS(&request));
if(FAILED(hr)) throw Exception::CreateException(hr);
//create callback
auto callback = Make<HttpRequestCallback>(request.Get(),token);
if(!callback) throw Exception::CreateException(hr);
//set the headers
//hr = request->SetRequestHeader(headerID.c_str(),);
//open and send the request
hr = request->Open(method.c_str(),uri.c_str(),callback.Get(),nullptr,nullptr,nullptr,nullptr);
if(FAILED(hr)) throw Exception::CreateException(hr);
hr = request->SetRequestHeader(headerID.c_str(),headerValue.c_str());
if(FAILED(hr)) throw Exception::CreateException(hr);
hr = request->Send(nullptr,0);
if(FAILED(hr)) throw Exception::CreateException(hr);
// Return a task that complets when the HTTP operation completes.
// We pass the callback to the continuation because the lifetime of the
// callback must exceed the operation to ensure that cancellation
// works correctly.
return create_task(callback->GetCompletionEvent()).then(
[callback](task<tuple<HRESULT, wstring>> previousTask)
{
return previousTask;
});
}
the callback implementation
//callback constructor
HttpRequestCallback::HttpRequestCallback(IXMLHTTPRequest2* request,concurrency::cancellation_token token):
m_request(request),m_token(token),m_aborted(false)
{
//register the call back token
if(m_token != cancellation_token::none())
{
m_registrationToken = m_token.register_callback([this]()
{
m_aborted = true;
if(m_request != nullptr)
{
m_request->Abort();
}
m_completionEvent.set(make_tuple<HRESULT,wstring>(S_OK,wstring()));
});
}
}
task_completion_event<tuple<HRESULT, wstring>> const& HttpRequestCallback:: GetCompletionEvent()
{
return m_completionEvent;
}
// Called when the HTTP request is being redirected to a new URL.
IFACEMETHODIMP HttpRequestCallback::OnRedirect(IXMLHTTPRequest2*, PCWSTR)
{
return S_OK;
}
// Called when HTTP headers have been received and processed.
IFACEMETHODIMP HttpRequestCallback::OnHeadersAvailable(IXMLHTTPRequest2*, DWORD, PCWSTR)
{
return S_OK;
}
// Called when a portion of the entity body has been received.
IFACEMETHODIMP HttpRequestCallback::OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream*)
{
return S_OK;
}
// Called when the entire entity response has been received.
IFACEMETHODIMP HttpRequestCallback::OnResponseReceived(IXMLHTTPRequest2*, ISequentialStream* pResponseStream)
{
// Convert the response to Unicode wstring.
HRESULT hr;
// Holds the response as a Unicode string.
wstringstream ss;
while (true)
{
// Read the response.
ULONG cb;
char buffer[4096];
hr = pResponseStream->Read(buffer, sizeof(buffer), &cb);
if (FAILED(hr) || (cb == 0))
{
break; // Error or no more data to process, exit loop.
}
else
{
// First determine the size required to store the Unicode string.
int const sizeRequired = MultiByteToWideChar(CP_UTF8, 0, buffer, cb, nullptr, 0);
wchar_t* wstr = new(std::nothrow) wchar_t[sizeRequired + 1];
hr = wstr ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
// Convert the string.
MultiByteToWideChar(CP_UTF8, 0, buffer, cb, wstr, sizeRequired);
wstr[sizeRequired] = L'\0'; // Terminate the string.
ss << wstr; // Write the string to the stream.
delete[] wstr;
}
}
}
m_completionEvent.set(make_tuple<HRESULT, wstring>(
SUCCEEDED(hr) ? S_OK : hr, // dont' return S_FALSE
SUCCEEDED(hr) ? ss.str() : wstring()));
return hr;
}
// Called when an error occurs during the HTTP request.
IFACEMETHODIMP HttpRequestCallback::OnError(IXMLHTTPRequest2*, HRESULT hrError)
{
// Set the completion event if the operation was not cancelled by the caller.
if (!m_aborted)
{
m_completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
}
return S_OK;
}
HttpRequestCallback::~HttpRequestCallback()
{
// Unregister the callback.
if (m_token != cancellation_token::none())
{
m_token.deregister_callback(m_registrationToken);
}
}
So here you go , you have a way of accessing REST based apis from chatter using windows 8
next blob - use the data to create a jazzy metro UI :)