Thus there are many ad hoc libraries supporting upload/download progress in angular2, I do not know how to use native angular2 http api to show progress while doing upload/download.
The reason why I want to use native http api is because I want to utilize:
http interceptors (http API wrappers) around native http api that validate, cache & enrich the actual http request being sent such as this & this
Angular's http api is much more robust than any ad hoc APIs
There is a nice article about how to do upload/download using angular's http api
But the article mentions that there is no native way to support progress.
Did anyone try using http api for showing progress?
If not, do you know an issue in the angular repo for this?
As of Angular 4.3.x and beyond versions, it can be achieved using the new HttpClient from #angular/common/http.
Read the Listening to progress events section.
Simple upload example (copied from the section mentioned above):
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
});
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for upload progress events.
if (event.type === HttpEventType.UploadProgress) {
// This is an upload progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});
And for downloading, it might be something like pretty much the same:
const req = new HttpRequest('GET', '/download/file', {
reportProgress: true,
});
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for download progress events.
if (event.type === HttpEventType.DownloadProgress) {
// This is an download progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% downloaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely downloaded!');
}
});
Remember in case that you're monitoring a download, the Content-Length has to be set, otherwise, there's no way to the request to be measured.
I would suggest using the native JavaScript XHR wrapped as an Observable, it's fairly easy to create on your own:
upload(file: File): Observable<string | number> {
let fd: FormData = new FormData();
fd.append("file", file);
let xhr = new XMLHttpRequest;
return Observable.create(observer => {
xhr.addEventListener("progress", (progress) => {
let percentCompleted;
// Checks if we can really track the progress
if (progress.lengthComputable) {
// progress.loaded is a number between 0 and 1, so we'll multiple it by 100
percentCompleted = Math.round(progress.loaded / progress.total * 100);
if (percentCompleted < 1) {
observer.next(0);
} else {
// Emit the progress percentage
observer.next(percentCompleted);
}
}
});
xhr.addEventListener("load", (e) => {
if (e.target['status'] !== 200) observer.error(e.target['responseText']);
else observer.complete(e.target['responseText']);
});
xhr.addEventListener("error", (err) => {
console.log('upload error', err);
observer.error('Upload error');
});
xhr.addEventListener("abort", (abort) => {
console.log('upload abort', abort);
observer.error('Transfer aborted by the user');
});
xhr.open('POST', 'http://some-dummy-url.com/v1/media/files');
// Add any headers if necessary
xhr.setRequestHeader("Authorization", `Bearer rqrwrewrqe`);
// Send off the file
xhr.send(fd);
// This function will get executed once the subscription
// has been unsubscribed
return () => xhr.abort()
});
}
And this is how one would use it:
// file is an instance of File that you need to retrieve from input[type="file"] element
const uploadSubscription = this.upload(file).subscribe(progress => {
if (typeof progress === Number) {
console.log("upload progress:", progress);
}
});
// To abort the upload
// we should check whether the subscription is still active
if (uploadSubscription) uploadSubscription.unsubscribe();
This is now possible, check out https://angular.io/guide/http#tracking-and-showing-request-progress.
I could give an example here but I think that the official example from the Angular docs would be better
Related
Background:
My project consists of a Vue 2 Front end encased in an electron shell with an asp.net core web API backend.
What I am trying to do is get a google authorization token and refresh token so that I can create a folder and files within that folder on a users google drive. I also want to display a list of the folders content in my front end.
I have tried using the new Google Identity Services code in my front end but when launching my app and clicking on the google button I the following error
Error 400: invalid_request
If you’re the app developer, make sure that these request details comply with Google policies:
redirect_uri: storagerelay://file/?id=auth12850
I think this is because electron is seen more of a desktop app which according to the google documentation needs to use a loopback address to open the system browser and authenticate from there.
So I tried the google authentication api on my backend which is C# I have the google web authorization broker setup and when I run my backend and call my endpoint I get the google sign in page and can get the authorization token and refresh token.
Question:
Is there a way to capture the URL of the page that comes up for authentication so that I can put it in a child window in electron.
or a way to pass the data to the front end so that I can show the list of files to my users.
Would I still need to use a loopback address even though I am getting the authentication page from the web broker?
If I do need the loopback functionality would I be better off using the data from the Google Desktop Application example rather than the google Web broker?
To learn more about electron you can go Electron website.
For a web app I think your going to have an issue
Users will be redirected to this path after they have authenticated with Google. The path will be appended with the authorization code for access, and must have a protocol. It can’t contain URL fragments, relative paths, or wildcards, and can’t be a public IP address.
Not just because of the format but because its going to need to be a domain you can register.
If you go with an installed app then the redirect uri is https://127.0.0.1
Im not sure how you are going to get this to route back properly.
I ended up using the nodejs google api to get this working this is my code that now returns an auth token and refresh token.
This code opens a child window when the authorize button is clicked and loads the google login/account select. Once authorized it shows the app permission window. When a user clicks allow the url is invoked in the loopback of the created server and the auth file is created in the specified directory.
part one successful.. now on to getting everything else working.
/* Google authentication */
function createGoogleWindow() {
const http = require('http');
const path = require('path');
const service = google.drive('v3');
const TOKEN_DIR = path.join(process.env.APPDATA, 'home-inventory', 'bin');
const TOKEN_PATH = path.join(TOKEN_DIR, 'home-inventory.json');
const querystring = require('querystring');
let googleWindow = new BrowserWindow({
parent: win,
height: 600,
width: 400,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
});
if (isDevelopment) googleWindow.webContents.openDevTools();
googleWindow.menuBarVisible = false;
googleWindow.on('closed', () => {
googleWindow = null;
});
const oauth2Client = new google.auth.OAuth2(
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URI
);
// check if we previously stored a token
fs.readFile(TOKEN_PATH, function (err, token){
if (err) {
getNewToken(oauth2Client);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
function callback (auth) {
service.files.list({
auth: auth,
q: `name contains '.bak'`,
pageSize: 50,
fields: "nextPageToken, files(id,name,size,parents,createdTime)",
}, function(err, response) {
if (err) {
console.error('The API returned an error: ', err);
return;
}
const files = response.files;
if (files.length === 0) {
console.warn('no files found');
} else {
console.warn('files', files, auth.credentials.access_token);
}
});
}
function getNewToken(oauth2Client, callback) {
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code !== 'EEXIST') {
throw err
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) {
throw err;
}
// console.debug('file was saved successfully');
googleWindow.close();
});
// console.warn('Token stored to: ', TOKEN_PATH);
}
function handler(request, response, server, callback) {
let qs = querystring.parse(require('url').parse(request.url).query);
oauth2Client.getToken(qs.code, function (err, tokens) {
if (err) {
console.error('Error getting oAuth tokens: ', err);
}
oauth2Client.credentials = tokens;
storeToken(tokens);
callback(oauth2Client)
server.close();
});
}
const server = http.createServer(function (request, response) {
handler(request, response, server, callback);
}).listen(8181, function() {
googleWindow.loadURL(url);
})
}
}
In my project I use CKEditor WYSWYG package to make HTML content for my website.
There is possible to insert image and send it directly from the package to the server.
Since 2 days I try to figure out how is it possible to catch the sent image from the Angular front-end to the Web API, but still no success.
I use .Net6 and Angular 12 with CKEditor 5.
public async Task<ActionResult<string>> AddPostPhoto(IFormFile photo)
{
try
{
System.Console.WriteLine(Request.ContentType);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos/PostPhotos", "fileName.jpg");
var memoryStream = new MemoryStream();
await Request.Body.CopyToAsync(memoryStream);
System.Console.WriteLine(Request.HttpContext.Request.ContentLength);
System.Console.WriteLine(Request.Form.Keys);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
Finally I could find a working solution.
my-upload-adapter.ts
//ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
export class MyUploadAdapter {
xhr: any;
loader: any;
serverUrl: string;
baseApiUrl: string;
constructor(loader: any, serverUrl: string, baseApiUrl: string) {
// The file loader instance to use during the upload.
this.loader = loader;
this.serverUrl = serverUrl;
this.baseApiUrl = baseApiUrl;
}
// Starts the upload process.
upload() {
return this.loader.file
.then((file: any) => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
//Replace below url with your API url
xhr.open('POST', this.baseApiUrl + 'Tutorial/add-post-photo', true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve: any, reject: any, file: any) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: this.serverUrl + response.url
});
});
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener('progress', (evt: any) => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file: any) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
// Send the request.
this.xhr.send(data);
}
}
In the Angular component
onReady($event: any) {
$event.plugins.get('FileRepository').createUploadAdapter = (loader: any) => {
return new MyUploadAdapter(loader, this.serverUrl, this.apiUrl);
};
}
The C# Web API controller
[HttpPost("add-post-photo")]
public async Task<ActionResult<string>> AddPostPhoto(IFormFile upload)
{
try
{
FileInfo fileInfo = new FileInfo(upload.FileName);
System.Console.WriteLine(upload.FileName);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos\\PostPhotos", upload.FileName);
var memoryStream = new MemoryStream();
await upload.OpenReadStream().CopyToAsync(memoryStream);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
It is important to have the parameter upload, otherwise the find the back-end endpoint
Hi so I've implemented a method based on this answer:
How to get notification in xamarin forms on firebase data change?
But I'm having trouble getting it working.
Here's my code:
void DoFirebaseObserve()
{
var firebase = new FirebaseClient(Constants.FirebaseProjectUrl);
firebase.Child("RegisterUserTable").AsObservable<RegisterUser>().Subscribe(obs =>
{
switch (obs.EventType)
{
case Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate:
Console.WriteLine("InsertOrUpdate");
break;
case Firebase.Database.Streaming.FirebaseEventType.Delete:
Console.WriteLine("Delete");
break;
default:
break;
}
});
}
I call this code in the OnAppearing method of my app's home page. I've tried with and without the while (true) {} condition.
When I add, update or delete a record in firebase, nothing happens in my application. The writelines are never called.
Any help would be appreciated. Thanks.
So I worked out how to do it through using Google Cloud Functions in the Firebase console.
Step 1. Set up FCM in your .Net code
Follow this tutorial:
https://learn.microsoft.com/en-us/xamarin/android/data-cloud/google-messaging/remote-notifications-with-fcm?tabs=macos
Once you have got your FCM token from that device, you can add it to your realtime database (I did this myself in the firebase console manually for testing).
Step 2. Set up Cloud Functions
Follow this tutorial:
https://www.youtube.com/watch?v=bpI3Bbhlcas&t=1104s&ab_channel=uNicoDev
Step 3. Create a function to send the message
You can use this node.js code in your cloud functions to send up to 500 devices:
// Create a list containing up to 500 registration tokens.
// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// …
'YOUR_REGISTRATION_TOKEN_N',
];
const message = {
data: {score: '850', time: '2:45'},
tokens: registrationTokens,
};
admin.messaging().sendMulticast(message)
.then((response) => {
console.log(response.successCount + ' messages were sent successfully');
});
Source: https://firebase.google.com/docs/cloud-messaging/send-message#node.js_1
Code Example
Some of my code to help you get an idea of how to go about doing something like this. Can't guarantee this is the optimal method but it works.
const reference = "JourneyTbl/{journeyId}";
// newJourneyAdded | listener method | Send push notif when new journey created:
exports.newJourneyAdded = functions.database.ref(reference)
.onCreate((event, context) => {
// where fcm tokens are stored:
const refNotifications = "NotificationTbl";
// get ref to RegNotTable:
const refFcm = admin.database().ref(refNotifications);
// array to hold all fcms from table:
const allFcms = [];
// get value of "NotificationTbl":
refFcm.on("value", (snapshot) => {
// check if there's any children:
if (snapshot.hasChildren()) {
// loop through children:
snapshot.forEach((element) => {
// stringify the data:
const asString = JSON.stringify(element);
// parse that as a JSON object:
const asJson = JSON.parse(asString);
// add fcm token to array:
allFcms.push(asJson.fcm);
});
}
// if array contains something:
if (allFcms.length > 0) {
// construct message to send:
const msg = {
notification: {
title: "Notification Title goes here",
body: "Notification Body goes here ",
},
tokens: allFcms, // pass the tokens
};
// send that message:
admin.messaging().sendMulticast(msg)
.then((response) => {
console.log(response.successCount + " mgs were sent sfly");
});
} else {
console.log("No devices in FCM tokens list");
}
});
return "ok";
});
This question already has answers here:
How to delete a file after it was streamed in ASP.NET Core
(5 answers)
Closed 2 years ago.
I am working on an ASP.NET Core 2.1 API project, which will be consumed by an Angular App, and later on a mobile app. one of the required functionality is to zip and download a collection of files, the result zipped file is going to be large (1GB or more).
the code I have now is working as follows:
the Angular app requests the API and expects a blob response.
the API on the server creates a Zip file and reads it using memory stream.
the API returns the memory stream using File response.
the method that subscribes to the download service in Angular saves
the file.
what is happening now is when I click in the browser on the download button I have to wait for the download to be finished then the browser shows the default popup that allows the user to save and select where to save.
I was wondering if what I'm doing is correct and won't cause any memory problems in the future?
is there a better methodology where the file could be streamed smoothly, so when the download starts the browser directly shows the save message and shows the default browser progress bar?
angular code:
component click function:
download(event,id){
event.stopPropagation();
event.preventDefault();
this.myservice.Downloadservice(avatar_id).subscribe((res: any) => {
saveAs(res.data, res.filename);
});
}
service code:
DownloadAllservice(id): Observable<any> {
let authToken = localStorage.getItem('auth_token');
let _options = { headers: new Headers({ 'Authorization': `Bearer ${authToken}`, } ),responseType: ResponseContentType.Blob };
let formData = new FormData();
let options ={
type: "zip",
id: id
};
formData.append('options',JSON.stringify(options));
return this.http.post(this.baseUrl + "/api/Download/", formData, _options)
.map(response =>{
return {
'filename': this.getFileNameFromHttpResponse(response),
'data': response.blob()
} })
.catch(this.handleError);
}
.net core code:
[Authorize(Policy = "Admin")]
[DisableRequestSizeLimit]
[HttpPost("Download", Name = "Download")]
public async Task<IActionResult> Download()
{
// get files list then start creating the temp folder and zipped folder
var archive = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp\" + "filename");
var temp = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp");
if (!System.IO.Directory.Exists(temp))
{
Directory.CreateDirectory(temp);
}
Directory.CreateDirectory(archive);
try
{
foreach (var file_id in filelist)
{
var path = file.path;
if (!System.IO.File.Exists(Path.Combine(archive, Path.GetFileName(path)))) {
System.IO.File.Copy(path, Path.Combine(archive, Path.GetFileName(path)));
}
}
var archivezip = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp\" + "filename" + ".zip");
// create a new archive
ZipFile.CreateFromDirectory(archive, archivezip);
var memory = new MemoryStream();
using (var stream = new FileStream(archivezip, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
Directory.EnumerateFiles(archive).ToList().ForEach(f => System.IO.File.Delete(f));
Directory.EnumerateDirectories(archive).ToList().ForEach(f => System.IO.Directory.Delete(f, true));
Directory.Delete(archive);
System.IO.File.Delete(archivezip);
return File(memory, "application/octet-stream","filename.zip");
}
catch (Exception ex)
{
return new BadRequestObjectResult(ex.Message);
}
}
please note that in the future not only angular app will use the API, but mobile apps will also be added
I do a similar thing (I guess everyone does because that's apparently how it's done with Angular). I just do a basic unlimited loading spinner because, so far, I haven't needed to worry about tracking the progress.
However, there are various guides to handling this out there for you to follow.
It seems to boil down to changing your request from a simple, standard get/post request to one that listens to the events of the response.
From the linked article:
this.http.get(url, {
reportProgress: true,
observe: 'events',
responseType: 'blob'
})
Important parts there are to ensure it wants progress reports and to observe events. Once the request is observing events, then you need to handle observing them (obviously).
That, however, is a longer more involved part of the article. Go forth and good luck.
Edit: Ah, the issue is actually API side you're worried about. Fair enough, there are a variety of similar questions that might be of use then.
I want to send pushnotifications to firefox web browser.......
<script>
function notifyMe() {
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
alert("This browser does not support desktop notification");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification("Hi there!");
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
var notification = new Notification("Hi there!");
}
});
}
// At last, if the user has denied notifications, and you
// want to be respectful there is no need to bother them any more.
} Notification.requestPermission().then(function (result) {
console.log(result);
}); function spawnNotification(theBody, theIcon, theTitle) {
var options = {
body: theBody,
icon: theIcon
}
var n = new Notification(theTitle, options);
}
</script>
whenever first time my website run permission popup will come.but when user click allow button how to get browser info like id.I want save that browser id into my database.next time I will send notification using that id. I did not find any code please help me.please anyone help me.
The Web Notifications API does not do what think it does.
It can only be used to display notifications from a web page that a user currently has open in their browser. It is comparable to alert(), except with a larger variety of formatting options.
It is not a push notification service, and cannot be used as one.