Background
In an application I'm working on, I've found that I can define values in sessionStorage in Chrome 62 on Windows 10, and that apparently changing that value in one tab affects other tabs that point to the same key.
I was operating under the assumption that localStorage is supposed to persist information across all browser windows, while sessionStorage is only supposed to persist information for a specific window or tab.
More specifically, I have an AngularJS service I'm using as a layer for sessionStorage interactions:
export class PersistenceSvc {
public static $inject: string[] = ['$window'];
public constructor(public $window: ng.IWindowService) {}
public save<T>(name: string, data: T): void {
const saveData: string = JSON.stringify(data);
this.$window.sessionStorage.setItem(name, saveData);
}
public load<T>(name: string): T {
const loadData: string = this.$window.sessionStorage.getItem(name);
const result: T = JSON.parse(loadData) as T;
return result;
}
}
...That I use from a run block in order to implement some data persistence in my application.
export function persistSomeData(
someSvc: Services.SomeService,
userAgentSvc: Services.UserAgentSvc,
persistenceSvc: Services.PersistenceSvc,
$window: ng.IWindowService) {
if(userAgentSvc.isMobileSafari()) {
// Special instructions for iOS devices.
return;
}
const dataToPersist: Models.DataModel = persistenceSvc.load<Models.DataModel>('SomeData');
if(dataToPersist) {
// Set up the state of someSvc with the data loaded.
} else {
// Phone home to the server to get the data needed.
}
$window.onbeforeunload = () => {
persistenceSvc.save<Models.DataModel>('SomeData', someSvc.dataState);
};
}
persistSomeData.$inject = [
// All requisite module names, omitted from example because lazy.
];
angular
.module('app')
.run(persistSomeData);
When only operating using a single tab, this works fine (unless running from an iOS device, but that's tangential to what I'm encountering.) When you do the following though, you start seeing some more interesting behavior...
Steps:
1. Open a Chrome instance. Create a new tab, and drag that out such that it becomes its own window.
2. Navigate to your site, that's using the above code.
3. Do things on your site that cause someSvc's data state to have different data in the first browser.
4. Do things on your site that cause someSvc's data state to have different data in the second browser.
5. Do something on your site that draws upon someSvc's data state in the first browser.
6. Observe that the data utilized on the first browser instance, was sourced by the second browser instance. (This is the problem, right here.)
Question:
In the past I haven't done a lot of cookie/localStorage/sessionStorage programming, so it's very possible that I've terribly misunderstood something. Bearing that in mind, why is it that window.sessionStorage is behaving in a way that the MDN documentation as well as the winning answer to this SO question says it shouldn't be behaving in?
EDIT: It turns out there is a problem, but it's not clientside. Closing this question, as I was operating under the assumption that the client was the problem.
There is something wrong with your code as a quick and easy test on the browser console shows that sessionStorage only impacts the browser tab that is open. A change in the right tab is not reflecting to the left tab:
Related
I'm completely new to SalesForce and have inherited a report that's not working. Please excuse any incorrect terminology, since I'm learning about all this as I go. The report has three prompts: states, years, and members. All dropdowns are supposed to populate with data returned from functions in an APEX class. State, which populates from a picklist, and years, which is populated with a loop, work fine. Members, which populates from a SQL query, returns nothing. If I run the report without any prompts selected (which should return an unfiltered list of results from a SQL query), it also returns nothing. Both of the SQL queries return data when I execute them directly in the query editor in the developer console, but they return nothing when called from the APEX functions.
Here's the initialization code from the Lightning controller:
doInit: function (component, event, helper) {
var action = component.get('c.getTrcAccounts');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS' && component.isValid()) {
component.set('v.trcAccList', response.getReturnValue());
}
helper.getLocationState(component, event);
helper.getYear(component, event);
});
$A.enqueueAction(action);
},
Here are the two helper functions referenced in that code:
getLocationState: function (component, event) {
var action = component.get('c.getLocationState');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS') {
component.set('v.LocationStateList', response.getReturnValue());
}
});
$A.enqueueAction(action);
},
getYear: function (component, event) {
var action = component.get('c.yearsOptions');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS') {
component.set('v.LocationYearList', response.getReturnValue());
}
});
$A.enqueueAction(action);
}
Here is the code from the APEX class that returns the data for those three prompts:
Global class DataTableLocations {
#AuraEnabled
Global static List<TRC_Account__c> getTrcAccounts(){
set<string> trcAccountSet = new set<string>();
List<TRC_Account__c> traccList = new List<TRC_Account__c>();
for(TRC_Account__c trcacc : [SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000]){
if(!trcAccountSet.contains(trcacc.Name)){
trcAccountSet.add(trcacc.Name);
traccList.add(trcacc);
}
}
if(traccList.size()>0){
return traccList;
}
else{
return null;
}
}
#AuraEnabled
Global static List<string> getLocationState(){
List<string> options = new List<string>();
//options.add(new SelectOption('SelectAll', 'Select All'));
for( Schema.PicklistEntry f : Location__c.Physical_Address_State__c.getDescribe().getPicklistValues()) {
options.add(f.getValue());
}
return options;
}
#AuraEnabled
Global static List<string> yearsOptions() {
List<string> options = new List<string>();
date OldDate= date.today().addYears(-18);
integer oldyear=OldDate.year();
for( integer i=0; i<19 ;i++) {
options.add(string.valueOf(oldyear));
oldyear++;
}
return options;
}
}
If I run SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000 directly in the query editor window in the developer console, I get 7 results. However, if I output the response.getReturnValue() for getTrcAccounts in the doInit function, it's null.
Any help is greatly appreciated, as we're in a bit of a time crunch in conjunction with a site redesign. I'm told these reports were working at one point, but no one knows when they stopped working, and we inherited this code from a different company that did the original development. Thank you!
UPDATE:
In case it helps, this is the code in the lightning app that I think is used on the public page:
<aura:application extends="ltng:outApp" access="GLOBAL" implements="ltng:allowGuestAccess">
<aura:dependency resource="c:SearchBinReceiptsByYear"/>
</aura:application>
Edit
Right, it's a public page, it's called "Salesforce Sites". It's exposed to whole world without having to log in. These have special security in place because most of the time you don't want to expose data like that. At best you'd display contact us form, maybe some documents to download, product catalog... It's all very locked down, default is to ban everything and then admin decides what's allowed. It's bit unusual to have a Visualforce page + Aura component but ok, it happens.
You (and any other internal user) can see the results if you'd access this page from within salesforce. Something like https://mydomain.my.salesforce.com/apex/SearchBinReceiptsByYear and for you the page will work fine, "just" not outside of salesforce.
When exposed like that on the web - there's no logged in user. There's special "[Site Name] Guest User", you can see them if you search "Sites" in Setup. It has a special profile, also with [Site Name] in it. And nasty thing is - it doesn't show on the list of Users or Profiles.
Your code broke when Salesforce (auto)activated a critical update. Probably this one: https://releasenotes.docs.salesforce.com/en-us/spring20/release-notes/rn_networks_secure_perms_guests.htm There are some good resources on the net if you Google "Secure Object Permissions for Guest Users", for example https://katiekodes.com/salesforce-spring-20-guest-user/
Ask your system administrator colleague or read up a bit about sharing rules.
You'll have to go to Setup -> Sharing Rules. There's a checkbox that caused your stuff to break and you can't untick it.
Scroll down to your TRC Account object and hit "New". You'll need to create something like this, but with your criteria (TRC Member equals true)
Save, wait a bit (it might take a while to recalculate the sharing, you'll get an email) and try the page.
If it still doesn't work you'll have to check the Guest user's profile, it might need permissions to Read TRC Accounts and their Name field.
If it's Salesforce Sites - try this to find it: https://help.salesforce.com/articleView?id=000334554&type=1&mode=1
If it's a Customer Portal, Community, Digital Experience (they renamed the product few times) - try with https://help.salesforce.com/articleView?id=sf.rss_config_guest_user_profile.htm&type=5
Original answer
It looks like it's running OK because accounts (members?) are fetched first and in that fetch's callback (what to do when data comes back from server) you have helper.getLocationState, helper.getYear. And you wrote that these populate OK. It's not the best performance code but it should get the job done.
In no specific order...
Does the whole thing work OK for sysadmins? Or is it broken for everybody? If it works for sysadmins it might be something to do with sharing, your sysadmin should know (Setup -> Sharing settings is where you control who can see what. Maybe "mortals" are not allowed to see any data? Typically sysadmins bypass it. As a quick & dirty test you can modify the class definition to global without sharing class DataTableLocations but it's a really ugly hack.
What happens if you open DeveloperConsole (upper right corner) while running this component, do you see any errors in the logs? What happens if in the console you go Debug -> Open ExecuteAnonymous and run this piece of code:
System.debug(DataTableLocations.getTrcAccounts());
Does it return something? Throw error?
You can go to Setup -> Debug Mode, tick the checkbox next to your user and save. This slows the system down a bit but lets you debug the javascript better. You can then sprinkle some debugger; or console.log statements in the source code and view what happens in your browser's console (Ctrl+Shift+J in Chrome, Ctrl+Shift+I in firefox). For example
action.setCallback(this, function (response) {
var state = response.getState();
debugger;
console.log(state);
console.log(component.isValid());
console.table(response.getReturnValue());
if (state === 'SUCCESS' && component.isValid()) {
component.set('v.trcAccList', response.getReturnValue());
}
console.log(component.get('v.trcAccList'));
debugger;
helper.getLocationState(component, event);
helper.getYear(component, event);
});
How's the trcAccList variable actually used in the "cmp" file, in the HTML-like file? Maybe it's being set all right and contains 7 records but it's not displayed right?
I have been following along with the following Symfony tutorials, but I believe they are using version 4 and I am using version 5. They reach a point in the tutorial which shows that the web debug toolbar shows the user's email logged and they even pointed out that if you see logged as anonymous, then just refresh. I did refresh, but it still shows as anon.
As you can see by the following screen shot, login was successful and it shows the correct username as well:
I started to watch the first part of the tutorial - listed below - when I reached a point in the second part that pointed out that I should watch the first part, which made sense, that I might have missed something, but that was an even older version of Symfony and things have changed in version 5.
First part of the tutorial
Second part of the tutorial
After going through the tutorials, I still have the web debug tool showing anon. Now, I am using React as a form to POST the email and password - see next screen shot - would that effect how the web debug toolbar, but I do not see how, because the console shows that the system knows the user.
Does anyone know a config that needs to be changed?
I have tried changing the following within src\Security\TokenAuthenticator - getUser from:
return $this->em->getRepository(User::class)
->findOneBy(['apiToken' => $credentials])
;
To:
return $this->em->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
But no change, still shows anon
Also, as the subject states, I cannot redirect via onAuthenticationSuccess
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
I do not see why this does not work. Again, is it because I am posting via a React app?
Turns that it is because I am running an older version of the browser Firefox and the log in is working. You can see by the screen shot of both Firefox and Chrome, that it is working Chrome
As far as the redirect goes, PHPStorm was saying that I did not have urlGenerator available in the TokenAuthenticator class. As a result, I should have noticed before and this is what I did to correct it:
In my src\Security\TokenAuthenticator I have the following:
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
In my constructor:
private $em;
private $urlGenerator;
public function __construct(EntityManagerInterface $em, UrlGeneratorInterface $urlGenerator)
{
$this->em = $em;
$this->urlGenerator = $urlGenerator;
}
My onAuthenticationSuccess:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
// redirect to some "app_homepage" route - of wherever you want
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
But it is still not working
I have tried
use Symfony\Component\HttpFoundation\RedirectResponse;
private $redirectResponse;
public function __construct(EntityManagerInterface $em, RedirectResponse $redirectResponse)
{
$this->em = $em;
$this->redirectResponse = $redirectResponse;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
// redirect to some "app_homepage" route - of wherever you want
return $this->redirectResponse->redirectToRoute('app_homepage');
}
But PHPStorm tells me that it cannot find method redirectToRoute within class RedirectResponse
The only thing that I have found to work with redirecting users to the home page after successful is login, is within my React login app. I have an async to my handleClick method, after the fetch POST, I have a setTimeout of 3000 that uses a plain javascript:
window.location.href = '/';
I would love to know the answer to why I cannot redirect via the Authenticator class that I have created, but at least someone who is using Firefox will not have to wonder why their web debug tool is not showing that the user has successfully logged in while still showing anon
Using RecordRTC library, I'm hooking my React web application with webcam video recording, replaying and saving functionalities. Coming from native application development, I'm always concerned about potential memory leak which often can be easily diagnosed by checking system memory or lagging UI experience. In web applications, what diagnoses can you perform to see if a JS object is being created and deleted properly without leaks.
My concern appeared when I began integrating replay functionality as shown below. The requestusermedia method instantiates the webcam stream when React component mounts. In fact, the src state gets assigned with the url to the video stream. Afterwards, anytime a stop button is clicked, a new url, representing a webm file of recorded video, is created and assigned to the same src state. The functionality of streaming and replaying works as planned. But, I'm concerned that continuation of creating and replaying video, essentially creating a new url wrapping webm file would only result in memory leak unless the browser is refreshed.
Are there any checks in the browser level I could conduct to diagnose this? Or is this something I shouldn't be concerned of at all in the web application world?
requestUserMedia() {
captureUserMedia((stream) => {
this.setState({ src: window.URL.createObjectURL(stream)});
});
}
handleRecord(){
if (!this.state.record) {
captureUserMedia((stream) => {
var recorder = RecordRTC(stream, {
type: 'video'
});
recorder.startRecording();
this.state.recordVideo = recorder;
});
} else {
var recorder = this.state.recordVideo
recorder.stopRecording(() => {
var blob = recorder.getBlob();
var url = window.URL.createObjectURL(blob);
this.setState({ src: url })
});
}
let newRecordState = !this.state.record
this.setState({
record: newRecordState
})
}
Setting the videos src to a string created with URL.createObjectURL has been deprecated for that reason. Set video.srcObject = stream instead.
For the second createObjectURL use URL.revokeObjectURL to revoke the previous one.
I'm trying to implement short-term caching in my Angular service -- a bunch of sub-components get created in rapid succession, and each one has an HTTP call. I want to cache them while the page is loading, but not forever.
I've tried the following two methods, neither of which have worked. In both cases, the HTTP URL is hit once for each instance of the component that is created; I want to avoid that -- ideally, the URL would be hit once when the grid is created, then the cache expires and the next time I need to create the component it hits the URL all over again. I pulled both techniques from other threads on StackOverflow.
share() (in service)
getData(id: number): Observable<MyClass[]> {
return this._http.get(this.URL)
.map((response: Response) => <MyClass[]>response.json())
.share();
}
ReplaySubject (in service)
private replaySubject = new ReplaySubject(1, 10000);
getData(id: number): Observable<MyClass[]> {
if (this.replaySubject.observers.length) {
return this.replaySubject;
} else {
return this._http.get(this.URL)
.map((response: Response) => {
let data = <MyClass[]>response.json();
this.replaySubject.next(data);
return data;
});
}
}
Caller (in component)
ngOnInit() {
this.myService.getData(this.id)
.subscribe((resultData: MyClass[]) => {
this.data = resultData;
},
(error: any) => {
alert(error);
});
}
There's really no need to hit the URL each time the component is created -- they return the same data, and in a grid of rows that contain the component, the data will be the same. I could call it once when the grid itself is created, and pass that data into the component. But I want to avoid that, for two reasons: first, the component should be relatively self-sufficient. If I use the component elsewhere, I don't want to the parent component to have to cache data there, too. Second, I want to find a short-term caching pattern that can be applied elsewhere in the application. I'm not the only person working on this, and I want to keep the code clean.
Most importantly, if you want to make something persistent even when creating/destroying Angular components it can't be created in that component but in a service that is shared among your components.
Regarding RxJS, you usually don't have to use ReplaySubject directly and use just publishReplay(1, 10000)->refCount() instead.
The share() operator is just a shorthand for publish()->refCount() that uses Subject internally which means it doesn't replay cached values.
Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.