How to transition to route in Ember from global context? - ember.js

I am integrating a chrome extension with an ember app. I have some chrome extension code in my app.js file:
window.sendToExtension = (message, callback) => {
chrome.runtime.sendMessage(extensionId, message, (response) => {
console.log('got response from extension!!', response)
if (response.path) {
//Here I need to tell the ember app the transition to the given path
}
})
}
this refers to window so I can't just call this.transitionTo. How can I transition to the route name the extension has told me to?

I figured it out!! All I had to do was add a beforeModel hook to the application route that did window.applicationRouteInstance = this and then I can access this globally, such as in my extension messaging code, to do applicationRouteInstance.transitionTo(response.path)
Also there's the new App.visit api coming in 2.3

Related

Opening an embedded HelloSign Signature in custom iframe

I've built out a system where my users can sign HelloSign signatures created with embedded signature request on the backend.
In the frontend, I'm using the hellosign-embedded library and using it like this to open the included iframe:
const helloSignClient = new HelloSign({ clientId: process.env.REACT_APP_HELLO_SIGN_CLIENT_ID });
// ....
const [generateEmbeddedSignUrl] = useMutation<
IGenerateEmbeddedSignUrlResponse,
IGenerateEmbeddedSignUrlVars
>(GenerateEmbeddedSignUrl, {
onCompleted: ({ generateEmbeddedSignUrl }) => {
if (generateEmbeddedSignUrl.error) {
return message.error(
`Error generating signature URL: ${
generateEmbeddedSignUrl.error.userMessage || generateEmbeddedSignUrl.error.description
}`
);
}
if (generateEmbeddedSignUrl.embeddedSignUrl.signUrl) {
// Embedded URL is generated. Start Signing with the Hello Sign SDK
helloSignClient.open(generateEmbeddedSignUrl.embeddedSignUrl.signUrl, {
// testMode will be true on staging or development, false on production
testMode: process.env.NODE_ENV === 'production' ? false : true,
});
}
},
onError: (error) => message.error(error),
});
Now that I have new requirements, I want to place this hellosign signature into a modal, where the modal will have the list next to the iframe (removed some info because sensitive info):
I'm trying to do something like this but it seems that there's some conflicting logic where the hellosign library will create its own modal/iframe and controlled like that. I'm not sure how to override this behavior.
I tried taking my generated sign URL and placing that into its own iframe but I get an error:
Something went wrong!
Your request seems to have been malformed and returned the following error:
→ Missing parameter: client_id
Which I assume is me missing client_id from the library:
const helloSignClient = new HelloSign({ clientId: process.env.REACT_APP_HELLO_SIGN_CLIENT_ID });
I guess my questions are: how do I achieve the mockup that I was given? Is it possible to open an embedded signature link in my own custom modal/iframe? Do I have to take the CSS route and override some behaviors? How do I open an embedded link without needing a client_id or using this library?

Apollo client useMutation in expo renders twice for every call

I have a basic expo app with React Navigation.
In the top function Navigation I am initiating a useMutation call to an Apollo server like so:
import { callToServer, useMutation } from '../graphQL';
function Navigation() {
console.log("RENDERED");
const [call] = useMutation(callToServer);
call({ variables: { uid: 'xyz', phoneNumber: '123' } });
...
And my GraphQL settings is as follows:
import {
ApolloClient,
createHttpLink,
InMemoryCache,
useMutation,
} from '#apollo/client';
import { onError } from '#apollo/client/link/error';
import { callToServer } from './authAPI';
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: `XXXXXXX/my-app/us-central1/graphql`,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
});
const client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
});
export {
useMutation,
callToServer,
};
export default client;
I want to clarify that I removed the httpLink from the client setting and I still get the two renders per call. I can see in the console that console.log("RENDERED") prints three times. Once when the app loads (normal) and twice after the useMutation call (not normal?)
What's going on here? Why is react re-renders twice per useMutation call? How do I avoid it?
UPDATE
I did further digging and it seems that useMutation does indeed cause the App to render twice - once when the request is sent, and once when it receives a response. I'm not sure I'm loving this default behavior which seems to have no way to disable. Why not let us decide if we want to re-render the App?
If someone has more insight to offer, Id love to hear about it.
Probably it's too late and maybe you've already found the solution, but still...
As I see you do not need data returned from mutation in the code above. In this case you can use useMutation option "ignoreResults" and set it to "true". So mutation will not update "data" property and will not cause any render.

Ember Super Rentals Tutorial 3.15 - Working with data

I was following the ember Super Rental 3.15 tutorial, when I got to the working with data section, I updated the route index file with model hooks, the page stopped working. Also I am finding ember tutorials to be incomplete.
error says property of map is undefined
code in routes index.js file:
import Route from '#ember/routing/route';
const COMMUNITY_CATEGORIES = [
'Condo',
'Townhouse',
'Apartment'
];
export default class IndexRoute extends Route {
async model() {
let response = await fetch('/api/rentals.json');
let { data } = await response.json();
return data.map(model => {
let { attributes } = model;
let type;
if (COMMUNITY_CATEGORIES.includes(attributes.category)) {
type = 'Community';
} else {
type = 'Standalone';
}
return { type, ...attributes };
});
}
}
image if error message:
Your problem is that fetch('/api/rentals.json'); does not return the correct data. And so when you do let { data } = await response.json(); then data will be undefined and you can not do undefined.map.
So the code you posted is correct. The problem is somewhere else. You can check:
did you correctly add the rentals.json file? If you open http://localhost:4200/api/rentals.json do you see the data? So have you done this?
I see some error from mirage. The super-rentals tutorial does not use mirage. I can see this here (sidenote: that git repo is automatically created from the guides, so its always up to date). So this could be your problem. Depending how you configure mirage it will basically mock all your ajax requests. This means that fetch(... will no longer work then expected, mirage assumes you always want to use mocked data and you did not configure mirage correctly. You can try to remove mirage from your package.json, rerun npm install, restart the ember server and try it again.

Authentification with ember-simple-auth in before model hook

I create a app thet need to implement authentification with email/password on all pages except one page (mobile_messages), where need to authenticate with refresh token.
I extend from JWT authenticator and override authenticate method. So it looks like:
authenticate (credentials, headers) {
return new Ember.RSVP.Promise((resolve, reject) => {
this.makeRequest('/auth/mobile_token', credentials, headers)
.then((response) => {
Ember.run(() => {
try {
const sessionData = this.handleAuthResponse(response)
resolve(sessionData)
} catch (error) {
reject(error)
}
})
}, (xhr) => {
Ember.run(() => { reject(xhr.responseJSON || xhr.responseText) })
})
})
}
On mobile_messages route I try to authenticate in before model hook.
beforeModel (transition) {
const authenticator = 'authenticator:api-token'
return this.get('session').authenticate(authenticator, {api_token: transition.queryParams.api_token}).then(() => {
}, (reason) => {
transition.abort()
this.get('notifications').error('Permission denied.', {
autoClear: true,
clearDuration: 6200
})
})
},
I need to stay on mobile_messages route if authenticate rejected. But when I enter to route with wront token I got next backtrase:
Preparing to transition from '' to 'mobileMessages'
index.js:169 generated -> controller:mobileMessages {fullName:
"controller:mobileMessages"}
index.js:169 generated -> controller:aut`enter code here`henticated
{fullName: "controller:authenticated"}
index.js:169 generated -> controller:loading {fullName:
"controller:loading"}
router.js:300 Intermediate-transitioned into 'authenticated.loading'
index.js:169 generated -> route:messages-faxes {fullName:
"route:messages-faxes"}
router.js:190 Transitioned into 'login'
jquery.js:9600 POST http://localhost:3000/auth/mobile_token 500
(Internal Server Error)
It looks like I was redirected before got response from server. An I can't find who is redirect me from route. I try to check ApplicationRouteMixin but i got that sessionInvalidated method calls only if you click logout button. And sessionAuthenticated after success authentification.
If I push to route correct token, then I first redirect to login page and then sessionAuthenticated fires. After that i redirect to baseURL.
Hot to solve issue with redirection to login page?
Ember Simple Auth uses Mixins to determine the route transition behavior that should happen if a user is authenticated/unauthenticated.
For example, this mixin will not allow the user to stay on the route if they are unauthenticated:
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
// route code
});
What you probably want to use is the UnauthenticatedRouteMixin
This mixin is used to make routes accessible only if the session is
not authenticated (e.g., login and registration routes). It defines a
beforeModel method that aborts the current transition and instead
transitions to the routeIfAlreadyAuthenticated if the session is
authenticated.
Include UnauthenticatedRouteMixin in your routes, which needs to accessed if the session is not validated. For example:
// app/routes/login.js
import UnauthenticatedRouteMixin from 'ember-simple-
auth/mixins/unauthenticated-route-mixin';
export default Ember.Route.extend(UnauthenticatedRouteMixin);
It was an error with loading hook. I make an error with naming routes. I created route with name loading to redirect to messages-faxes route. In this case when before model hook return promise ember generate route:application_loading. In application_loading route I run transition to messages-faxes route which has UnauthenticatedRouteMixin. This mixin see that user is not Authenticated and redirect to loading page.

Get two versions of ember-simple-auth to play well together

We're working with two ember applications that each run different version of ember and ember-simple-auth, and want to get ember-simple-auth to work well with both version.
The old app
Ember 1.8.1
Ember-simple-auth 0.7.3
The new app
Ember 2.3.1
Ember-simple-auth 1.0.1
Uses cookie session store
We trying to change the session API for the older version so that it stores the access and refresh tokens correctly so the new app can use it.
So far, we’ve tried overriding the setup and updateStore methods to work with the authenticated nested object but are still running into issues.
Disclaimer - Patrick Berkeley and I work together. We found a solution after posting this question that I figured I would share.
In order for a 0.7.3 version of ember-simple-auth's cookie store to play nicely with a 1.0.0 version, we did have to normalize how the cookie was being formatted on the app with the earlier version in a few key places, mostly centered around the session object (the 0.7.3 session is an ObjectProxy that can be extended in the consuming app to create your own custom session).
The methods that we needed to override, centered around the structure of data being passed to the cookie store to persist and what was being returned when a session was being restored. The key difference is on version 0.7.3, the access_token, etc is stored top-level on the content object property of the session. With 1.0.0. this is nested inside another object inside content with the property name of authenticated. We therefore needed to ensure that everywhere we were making the assumption to set or get the access_token at the top level, we should instead retrieve one level deeper. With that in mind, we came up with these methods being overridden in our custom session object:
// alias access_token to point to new place
access_token: Ember.computed.alias('content.authenticated.access_token'),
// overridden methods to handle v2 cookie structure
restore: function() {
return new Ember.RSVP.Promise((resolve, reject) => {
const restoredContent = this.store.restore();
const authenticator = restoredContent.authenticated.authenticator;
if (!!authenticator) {
delete restoredContent.authenticated.authenticator;
this.container.lookup(authenticator).restore(restoredContent.authenticated).then(function(content) {
this.setup(authenticator, content);
resolve();
}, () => {
this.store.clear();
reject();
});
} else {
this.store.clear();
reject();
}
});
},
updateStore: function() {
let data = this.content;
if (!Ember.isEmpty(this.authenticator)) {
Ember.set(data, 'authenticated', Ember.merge({ authenticator: this.authenticator }, data.authenticated || {}));
}
if (!Ember.isEmpty(data)) {
this.store.persist(data);
}
},
setup(authenticator, authenticatedContent, trigger) {
trigger = !!trigger && !this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: true,
authenticator
});
Ember.set(this, 'content.authenticated', authenticatedContent);
this.bindToAuthenticatorEvents();
this.updateStore();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionAuthenticationSucceeded');
}
},
clear: function(trigger) {
trigger = !!trigger && this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: false,
authenticator: null
});
Ember.set(this.content, 'authenticated', {});
this.store.clear();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionInvalidationSucceeded');
}
},
bindToStoreEvents: function() {
this.store.on('sessionDataUpdated', (content) => {
const authenticator = content.authenticated.authenticator;
this.set('content', content);
if (!!authenticator) {
delete content.authenticated.authenticator;
this.container.lookup(authenticator).restore(content.authenticated).then((content) => {
this.setup(authenticator, content, true);
}, () => {
this.clear(true);
});
} else {
this.clear(true);
}
});
}.observes('store'),
This took us most of the way there. We just needed to ensure that the authenticator name that we use matches the name on 1.0.0. Instead of 'simple-auth-authenticator:oauth2-password-grant', we needed to rename our authenticator via an initializer to 'authenticator:oauth2'. This ensures that the apps with the newer version will be able to handle the correct authenticator events when the cookie session data changes. The initializer logic is simple enough:
import OAuth2 from 'simple-auth-oauth2/authenticators/oauth2';
export default {
name: 'oauth2',
before: 'simple-auth',
initialize: function(container) {
container.register('authenticator:oauth2', OAuth2);
}
};
The above satisfies our needs- we can sign in to an app using ember-simple-auth 0.7.3 and have the cookie session stored and formatted properly to be handled by another app on ember-simple-auth 1.0.0.
Ideally, we would just update the Ember and Ember Simple Auth versions of the app though business needs and the fact that we wanted to focus our energies on the v2 versions (which are completely fresh and new code bases) propelled us to go down this path.