Registering call logging service into RingCentral Embeddable results in undefined error - ring

It appears all my current attempts to register a third party service (My Single Page Application) using RingCentral Embeddable are proving difficult. I'm using the postMessage API with type rc-adapter-register-third-party-service and the result remains "undefined"
document.querySelector("#rc-widget-adapter-frame").contentWindow.postMessage({
type: 'rc-adapter-register-third-party-service',
service: {
name: 'TestService'
}
}, '*');
Is there a resolution to this as I'm successful receiving inbound calls. When I use type "rc-adapter-new-call" for outbound calls it also works but third party integration is proving difficult and the link neither pops up on the softPhone.
For more details see...
https://github.com/ringcentral/ringcentral-embeddable/blob/master/docs/third-party-service-in-widget.md#register-your-service
Thanks!

I think the issue is that we need to register service after widget loaded.
Here is a demo for it. Let me update the embeddable docs.
var registered = false;
window.addEventListener('message', function (e) {
const data = e.data;
if (data && data.type === 'rc-login-status-notify' && registered === false) {
registered = true;
registerService();
}
});

Related

Identity Platform / Firebase Error (auth/invalid-refresh-token)

I am in the process of upgrading an existing working Firebase Auth project to Identity Platform to benefit from the goodness of tenants.
I am currently testing this against the local emulator and am facing the following issues:
My users no longer show up in the emulator. I reckon, however, that
the behaviour is expected since I am creating users against a tenant
and no longer the default project users "pool"
The users do not show
up in the GCP console either. Yet, the getUserByEmail() method
in a Cloud Function returns the registered users. I therefore have no clue where these users are currently created...
Authenticating users via generateSignInWithEmailLink() works fine.
However, a few steps in the funnel after this, when using the await user?.getIdToken(true) method, I am getting the following error: Uncaught (in promise) FirebaseError: Firebase: Error (auth/invalid-refresh-token) and can't figure out why.
Interestingly, the user.getIdTokenResult() method works fine and does not yield any error.
My entire snippet:
const getCurrentUser = async (): Promise<Auth["currentUser"]> => {
return new Promise((resolve, reject) => {
const unsubscribe = onAuthStateChanged(
auth,
async (user) => {
if (user) {
if (document.referrer.includes("stripe")) {
// console.log({ user });
await user?.getIdToken(true);
console.log({ after: user });
}
state.isAuthenticated.value = true;
state.user.value = user;
try {
const { claims } = await user.getIdTokenResult();
state.claims.value = claims;
if (typeof claims.roles === "string") {
if (claims.active && claims.roles.includes("account_owner")) {
state.isActive.value = true;
}
}
} catch (e) {
console.log(e);
if (e instanceof Error) {
throw new Error();
}
}
}
unsubscribe();
resolve(user);
},
(e) => {
if (e instanceof Error) {
state.error.value = e.message;
logClientError(e as Error);
}
reject(e);
}
);
});
};
For reference, I am working with a Vue 3 / Vite repo.
Any suggestion would be welcome,
Thanks,
S.
Just a quick follow-up here for anyone looking for an answer to this.
I raised a bug report on the firebase-tools Github and:
Users not appearing in the Emulator UI: behaviour confirmed by the firebase team. The emulator does not not support multi-tenancy at the moment. In my experience, however, working with the emulator with multi-tenants, the basic functionalities seem to work: creating users, retrieving them. Impossible however to visualise them or export them.
Refresh token error: bug confirmed by the firebase team and in the process of being triaged. Will likely take some time before being fixed (if ever?). So for now, even if far from being ideal, I have added conditional checks to my code to skip the force refresh of the token on localhost. Instead, I log out and log back in with the users I am expecting to see some changes in the claims for, as this process does not error. Another solution would be to use an actual Firebase Auth instance and not the local emulator, but it feels a bit messy to combine localhost/emulator resources and actual ones, even with a dev account.
The GH issue: here.

external service result mutates state of aggregate

My problem is that I don't know how to handle external calls that mutates the state but also needs validation before executing them
Here is my command handler
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
return sandbox;
}
The problem here is that the sandbox aggregate needs to be in correct state before calling external service and I cannot satisfy both. My only idea here is to create separate method CanCreateRedisInstance that checks if aggregate state is valid and only then calls external service. What I don't like is that I introduce validation methods
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
if(!sandbox.CanCreateRedisInstance())
{
throw new ValidationExcetpion("something");
}
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
return sandbox;
}
The other approach I thought of is to make whole process more cqrs-ish.
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
sandbox.ScheduleRedisInstanceCreation();
}
public void ScheduleRedisInstanceCreation()
{
if(RedisInstanceDetails != null)
{
throw new ValidationException("something")
}
RedisInstanceDetails = RedisInstanceDetails.Scheduled(some arguments);
AddEvent(new RedisInstanceCreationScheduled(some arguments));
}
The RedisInstanceCreationScheduled event is sent to queue and picked by event handler
which will call external service and based on result will create other events
public async Task ExecuteAsync(RedisInstanceCreationScheduled event)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
_aggregateStore.Save(sandbox);
}
However this approach add some extra complexity and I am not quite sure if event handler should modify aggregate.
Both approaches are possible.
Why no validation should stay in the Handler? When you change something in the domain, the domain object makes also a validation about the action, and deny it if it's not possible. Here you just need to interact with an external service to verify it.
The external service is just an interface in the domain layer, that you're going to implement with a concrete class into the infrastructure layer. Hence you will not have a directly binding with azure, but a service, let's say CloudService, that in it's implementation uses Azure. This allows you to build domain related exceptions that are thrown by classes that stay in the infrastructure layer.
Also the CQRS approach is valid. But you have to take care when you use it.
You can, for example, start a saga where you ask to the external service to create the instance (CreateRedisInstance), then, according to the event that you get (success or failure) you proceed with the next handler. But you really have to take care about middle situations: what should be done to handle failures between the 2 actions? You need also a rollback of the first action if the second one ends with a failure.
Said this, I would go with the first one if there're no really need to handle a complex process. Moreover, it looks that is all related to the same domain (no infra-domain actions are required), hence there're no real need to augment the complexity with a saga where every success/fail status should be correctly handled.

Messages not coming thru to Azure SignalR Service

I'm implementing Azure SignalR service in my ASP.NET Core 2.2 app with React front-end. When I send a message, I'm NOT getting any errors but my messages are not reaching the Azure SignalR service.
To be specific, this is a private chat application so when a message reaches the hub, I only need to send it to participants in that particular chat and NOT to all connections.
When I send a message, it hits my hub but I see no indication that the message is making it to the Azure Service.
For security, I use Auth0 JWT Token authentication. In my hub, I correctly see the authorized user claims so I don't think there's any issues with security. As I mentioned, the fact that I'm able to hit the hub tells me that the frontend and security are working fine.
In the Azure portal however, I see no indication of any messages but if I'm reading the data correctly, I do see 2 client connections which is correct in my tests i.e. two open browsers I'm using for testing. Here's a screen shot:
Here's my Startup.cs code:
public void ConfigureServices(IServiceCollection services)
{
// Omitted for brevity
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = authority;
jwtOptions.Audience = audience;
jwtOptions.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// Check to see if the message is coming into chat
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/im")))
{
context.Token = accessToken;
}
return System.Threading.Tasks.Task.CompletedTask;
}
};
});
// Add SignalR
services.AddSignalR(hubOptions => {
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(10);
}).AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]);
}
And here's the Configure() method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Omitted for brevity
app.UseSignalRQueryStringAuth();
app.UseAzureSignalR(routes =>
{
routes.MapHub<Hubs.IngridMessaging>("/im");
});
}
Here's the method I use to map a user's connectionId to the userName:
public override async Task OnConnectedAsync()
{
// Get connectionId
var connectionId = Context.ConnectionId;
// Get current userId
var userId = Utils.GetUserId(Context.User);
// Add connection
var connections = await _myServices.AddHubConnection(userId, connectionId);
await Groups.AddToGroupAsync(connectionId, "Online Users");
await base.OnConnectedAsync();
}
Here's one of my hub methods. Please note that I'm aware a user may have multiple connections simultaneously. I just simplified the code here to make it easier to digest. My actual code accounts for users having multiple connections:
[Authorize]
public async Task CreateConversation(Conversation conversation)
{
// Get sender
var user = Context.User;
var connectionId = Context.ConnectionId;
// Send message to all participants of this chat
foreach(var person in conversation.Participants)
{
var userConnectionId = Utils.GetUserConnectionId(user.Id);
await Clients.User(userConnectionId.ToString()).SendAsync("new_conversation", conversation.Message);
}
}
Any idea what I'm doing wrong that prevents messages from reaching the Azure SignalR service?
It might be caused by misspelled method, incorrect method signature, incorrect hub name, duplicate method name on the client, or missing JSON parser on the client, as it might fail silently on the server.
Taken from Calling methods between the client and server silently fails
:
Misspelled method, incorrect method signature, or incorrect hub name
If the name or signature of a called method does not exactly match an appropriate method on the client, the call will fail. Verify that the method name called by the server matches the name of the method on the client. Also, SignalR creates the hub proxy using camel-cased methods, as is appropriate in JavaScript, so a method called SendMessage on the server would be called sendMessage in the client proxy. If you use the HubName attribute in your server-side code, verify that the name used matches the name used to create the hub on the client. If you do not use the HubName attribute, verify that the name of the hub in a JavaScript client is camel-cased, such as chatHub instead of ChatHub.
Duplicate method name on client
Verify that you do not have a duplicate method on the client that differs only by case. If your client application has a method called sendMessage, verify that there isn't also a method called SendMessage as well.
Missing JSON parser on the client
SignalR requires a JSON parser to be present to serialize calls between the server and the client. If your client doesn't have a built-in JSON parser (such as Internet Explorer 7), you'll need to include one in your application.
Update
In response to your comments, I would suggest you try one of the Azure SignalR samples, such as
Get Started with SignalR: a Chat Room Example to see if you get the same behavior.
Hope it helps!

Azure + SignalR - Secure hubs to different connection types

I have two hubs in a web role,
1) external facing hub meant to be consumed over https external endpoint for website users.
2) intended to be connected to over http on an internal endpoint by worker roles.
I would like the ability to secure access to the hubs somehow.
Is there anyway I can check to see what connection type the connecting user/worker role is using and accept/deny based on this?
Another method I thought of was perhaps using certificate authentication on the internal hubs but i'd rather not have to for speed etc.
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "web");
// Web external connection
app.MapSignalR("/signalr", new HubConfiguration()
{ EnableJavaScriptProxies = true, EnableDetailedErrors = false });
// Worker internal connection
app.MapSignalR("/signalr-internal", new HubConfiguration()
{ EnableJavaScriptProxies = false, EnableDetailedErrors = true});
EDIT: I've included my own answer
A simple solution you can use roles of client to distinguish between to connections
object GetAuthInfo()
{
var user = Context.User;
return new
{
IsAuthenticated = user.Identity.IsAuthenticated,
IsAdmin = user.IsInRole("Admin"),
UserName = user.Identity.Name
};
}
also other options are fully described here
I ended up probing the request environment variables and checking the servers localPort and request scheme in a custom AuthorizeAttribute. The only downside to this at the moment is that the javascript proxies will still generate the restricted hub info. But i'm working on that :).
I'll leave the question open for a bit to see if anyone can extend on this.
public class SignalrAuthorizeAttribute : Microsoft.AspNet.SignalR.AuthorizeAttribute, Microsoft.AspNet.SignalR.IDependencyResolver
{
public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, Microsoft.AspNet.SignalR.IRequest request)
{
bool isHttps = request.Environment["owin.RequestScheme"].ToString().Equals("https", StringComparison.OrdinalIgnoreCase) ? true : false;
bool internalPort = request.Environment["server.LocalPort"].ToString().Equals("2000") ? true : false;
switch(hubDescriptor.Name)
{
// External Hubs
case "masterHub":
case "childHub":
if (isHttps && !internalPort) return base.AuthorizeHubConnection(hubDescriptor, request);
break;
// Internal hubs
case "workerInHub":
case "workerOutHub":
if (!isHttps && internalPort) return base.AuthorizeHubConnection(hubDescriptor, request);
break;
default:
break;
}
return false;
}
}

Public Database between chrome extensions

I want to created a public database so other extensions can access it, create tables, add entities, remove entities what they want.
I saw that the only way to do this is to use message passing between multiple extensions, but this solutions is problematic for me, because I need permission to "management" in order to know the other extensions IDs.
There is an option for sending messages to all extensions without knowing their ID? or there is another way of implementing public db without pub-sub synchronization?
btw - I can use localStorage or WebSQL.
Could you create an extension, hub, that is used to register other extensions and has a messaging hub.
All of the extensions that wanted to communicate with the public DB could then do it via the hub. Upon initialization from the background page, each extension could register with the hub their ID and which events they want to subscribe to.
Register action from each extension
chrome.tabs.sendRequest("hub", {
action: "register",
key: "somePrivKey",
id: "extId",
subscribeTo: ["createFoo", "deleteFoo"]
});
Then, each action performed would be communicated to the hub:
chrome.tabs.sendRequest("hub", {
action: "createFoo",
key: "somePrivKey",
context: 1
});
The hub extension would then listen to events. For "register" actions the hub would register the extension as an endpoint for the "subscribeTo" actions. For other actions ("createFoo" or "deleteFoo") the hub would iterate over the list of registered extensions for the event and perform a sendRequest that sends the "action" name and an optional "context".
A shared "key" could be known between the hub and all the extensions that want to communicate to prevent the hub from listening to events not from a known source.
Hub extension background.js:
var actionToExtMap = {};
chrome.extension.onRequestExternal.addListener(function(request, sender, sendResponse) {
if (request.key === "somePrivKey") {
if (request.action === "register") {
for (i = 0; i < request.subscribeTo.length; i++) {
var action = request.subscribeTo[i];
var extsionsForAction = actionToExtMap[action] || [];
extsionsForAction.push(request.id)
}
} else if (request.action) {
var extensionsToSendAction = actionToExtMap[request.action];
for (i = 0; i < extensionsToSendAction.length; i++) {
chrome.extension.sendRequest(extensionsToSendAction[i], {
action: request.action,
context: request.context //pass an option context object
}
}
}
}
});

Resources