I created a Google Sheet that uses a Google Script to generate short URLs via Firebase API.
This is the code in the Google Script
function URLShortener(longURL) {
var body = {
"dynamicLinkInfo": {
"domainUriPrefix": "https://go.example.com",
"link" : longURL
},
"suffix": {
"option": "SHORT"
}
};
var key = 'xxxxxxx'
var url = "https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=" + key;
var options = {
'method': 'POST',
"contentType": "application/json",
'payload': JSON.stringify(body),
};
var response = UrlFetchApp.fetch(url, options);
var json = response.getContentText();
var data = JSON.parse(json);
var obj = data["shortLink"];
return obj;
Logger.log(obj)
}
The script works and it generates URLs similar to https://go.example.com/Xdka but these link redirect to https://example.com/Xdka instead of the actual URL that is sent, e.g. https://example.com/final_url.
If I try to generate these short links from the Firebase dashboard the same happens.
Did I misunderstand how these short URLs work or am I missing something?
Related
We use a rest api to get customer information. A lot of the GET request were already written by others. I was able to follow their code to create other GET request, but one of the API methods for updating a customer requires using json patch. Below I have pasted in sample code of a current GET method, a Patch method (that I don't know how to implement) and a sample function written in javascript on how to use the json-patch that came from the api creators demo documentation:
public GetCustomerResponse GetCustomerInfo(CustomerRequest request)
{
//All of this works fine the base url and token info is handled elsewhere
var restRequest = CreateRestRequest($"customer/account?id={request.id}", RestSharp.Method.GET);
var response = CreateRestClient().Execute<GetCustomerResponse>(restRequest);
if (response.StatusCode == HttpStatusCode.OK)
{
return response.Data;
}
else
{
return new GetCustomerResponse(response.Content);
}
}
public EditCustomerResponse EditCustomer(EditCustomerRequest request)
{
var restRequest = CreateRestRequest($"customer/account?id={request.id}", RestSharp.Method.PATCH);
var response = CreateRestClient().Execute<EditCustomerResponse>(restRequest);
//how do I pass along json patch data in here???
//sample json might be like:
//[{'op':'replace','path':'/FirstName','value':'John'},{'op':'replace','path':'/LastName','value':'Doe'}]
if (response.StatusCode == HttpStatusCode.OK)
{
return response.Data;
}
else
{
return new EditCustomerResponse(response.Content);
}
}
//javascript demo version that is working
function patchCustomer(acctId, patch, callback) {
var token = GetToken();
$.ajax({
method: 'PATCH',
url: BaseURI + 'customer/account?id=' + acctId,
data: JSON.stringify(patch),
timeout: 50000,
contentType: 'application/json; charset=UTF-8',
beforeSend: function (xhr) { xhr.setRequestHeader('Authorization', 'Bearer ' + token.access_token) },
}).done(function (data) {
if (typeof callback === 'function')
callback.call(data);
}).fail(function (jqXHR, textStatus, errorThrown) {
console.log("Request failed: " + textStatus);
console.error(errorThrown);
failureDisplay(jqXHR);
});
}
This was pretty simple. After viewing similar questions on stackoverflow, I initially was trying something like this:
var body = new
{
op = "replace",
path = "/FirstName",
value = "John"
};
restRequest.AddParameter("application/json-patch+json", body, ParameterType.RequestBody);
It would not work. To get it to work, I added a patchparameters class with op, path and value properties, and then added a list property of type patchparameters to my EditCustomerRequest class and used it like this:
restRequest.AddJsonBody(request.patchParams);
Just want to check, is there any API to add the authorized domain in a programmatical way instead of adding it manually by going to Firebase console?
Also, is there any limit on how many domains can be added as the authorized domains?
JavaScript in Cloud Functions solution
import { google } from "googleapis";
(async () => {
/**
* ! START - Update Firebase allowed domains
*/
// Change this to whatever you want
const URL_TO_ADD = "engineering.acme-corp.net";
// Acquire an auth client, and bind it to all future calls
const auth = new google.auth.GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
const authClient = await auth.getClient();
google.options({ auth: authClient });
// Get the Identity Toolkit API client
const idToolkit = google.identitytoolkit("v3").relyingparty;
/**
* When calling the methods from the Identity Toolkit API, we are
* overriding the default target URLs and payloads (that interact
* with the v3 endpoint) so we can talk to the v2 endpoint, which is
* what Firebase Console uses.
*/
// Generate the request URL
const projectId = await auth.getProjectId();
const idToolkitConfigUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config`;
// Get current config so we can use it when we later update it
const currentConfig = await idToolkit.getProjectConfig(undefined, {
url: idToolkitConfigUrl,
method: "GET",
});
// Update the config based on the values that already exist
await idToolkit.setProjectConfig(undefined, {
url: idToolkitConfigUrl,
method: "PATCH",
params: { updateMask: "authorizedDomains" },
body: JSON.stringify({
authorizedDomains: [
...(currentConfig.data.authorizedDomains || []),
URL_TO_ADD,
],
}),
});
})();
A quick note on other languages
The principles should be the same:
Find a way to interact with Google's identify toolkit API (maybe Google offers an SDK to your language)
Get current config
Set new config
If you can't find an SDK, you can also work with raw http requests: https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/getConfig (it's just a bit trickier to do authentication when doing everything manually)
There is no API for this - you must do it through the console. You can also file a feature request with Firebase support if you want.
There doesn't appear to be any documentation stating limits of number of domains. Again, reach out to Firebase support if the documentation is unclear.
Thanks #Jean Costa
Totally working for me.
Here is C# implementation
using Google.Apis.Auth.OAuth2;
using Newtonsoft.Json;
var serviceAccountJsonFile = "path to service account json";
var projectId = "your project ids";
var authorizedDomains = new
{
authorizedDomains = new string[] {
"localhost",
"******.firebaseapp.com",
"*********.web.app",
"abc.def.com"
}
}; // your desire authorized domain
List<string> scopes = new()
{
"https://www.googleapis.com/auth/identitytoolkit",
"https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/cloud-platform"
};
var url = "https://identitytoolkit.googleapis.com/admin/v2/projects/" + projectId + "/config";
using var stream = new FileStream(serviceAccountJsonFile, FileMode.Open, FileAccess.Read);
var accessToken = GoogleCredential
.FromStream(stream) // Loads key file
.CreateScoped(scopes) // Gathers scopes requested
.UnderlyingCredential // Gets the credentials
.GetAccessTokenForRequestAsync().Result; // Gets the Access Token
var body = JsonConvert.SerializeObject(authorizedDomains);
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Patch, url) {
Content = new StringContent(body,System.Text.Encoding.UTF8)
};
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer " + accessToken);
try
{
var response = client.SendAsync(request).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
catch (HttpRequestException ex)
{
// Failed
}
}
Thanks #Jean Costa and #Yan Naing
here is my php implemetation
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\TransferException;
use Google\Service\IdentityToolkit;
use Google\Service\IAMCredentials;
$KEY_FILE_LOCATION = storage_path('/app/credentials/service-account-1.json') ;
if (!file_exists($KEY_FILE_LOCATION)) {
throw new Exception(sprintf('file "%s" does not exist', $KEY_FILE_LOCATION));
}
$json= file_get_contents($KEY_FILE_LOCATION);
if (!$config = json_decode($json, true)) {
throw new Exception('invalid json for auth config');
}
$client = new \Google\Client();
$client->setAuthConfig($config );
$client->setScopes([ "https://www.googleapis.com/auth/identitytoolkit",
"https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/cloud-platform"]);
$service = new IdentityToolkit($client);
// Get the Identity Toolkit API client
$idToolkit = $service->relyingparty;
//Get current config
$current_config= $idToolkit->getProjectConfig();
//Get service account access token
$access_token_req = new IAMCredentials\GenerateAccessTokenRequest();
$access_token_req->setScope( "https://www.googleapis.com/auth/firebase");
$credentials = new IAMCredentials($client);
$access_token = $credentials->projects_serviceAccounts->generateAccessToken("projects/-/serviceAccounts/{$config["client_email"]}" , $access_token_req )->getAccessToken();
// Generate the request URL (https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/updateConfig)
$idToolkitConfigUrl = "https://identitytoolkit.googleapis.com/admin/v2/projects/{$config["project_id"]}/config";
$authorized_domains = [ 'authorizedDomains' => array_merge( ['twomore.com'],$current_config->authorizedDomains)];
$client = new GuzzleClient( );
$response = null;
try {
$response = $client->request('PATCH', $idToolkitConfigUrl, [
'verify' => Helpers::isProduction() ? true : false ,
'http_errors'=> false, //off 4xx and 5xx exceptioins
'json' => $authorized_domains ,
'headers' => [
"Authorization" => "Bearer " . $access_token ,
"Accept" => "application/json",
]
]);
} catch (TransferException $e) {
throw new Exception( $e->getMessage());
}
$data = json_decode($response->getBody()->getContents(),true);
if($response->getStatusCode()!==200){
throw new Exception($response->getReasonPhrase() . ( isset($data['exception']['message']) ? " - " . $data['exception']['message'] : ""));
}
return response()->json(['data' => [
'authorized_domains' => $data['authorizedDomains']
]]);
I am trying to get the sentiment of a piece of text using th AlchemyAPI in my meteor application. I am using HTTP.call with 'POST' as recommended by the API to make a server to server call, but I am getting an 'invalid-api-key' response.
var alchemyURL = Meteor.settings.alchemyUrl;
var postData = {
'apikey': Meteor.settings.alchemyUrl,
'text': txt,
'outputMode': 'json'
};
var options = {
data: postData
};
var sentimentData = HTTP.call('POST', alchemyURL, options);
console.log(sentimentData);
I have discovered the answer, to this hence posting it in below.
So turns out, Meteor's HTTP package needs to be given the headers for implementing form url-encoding on the data. Also the data object needs to be passed in 'params' and not in 'data' The correct snippet to be used is given below.
var alchemyURL = Meteor.settings.alchemyUrl;
var postData = {
'apikey': Meteor.settings.alchemyUrl,
'text': txt,
'outputMode': 'json'
};
var options = {
params: postData,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
var sentimentData = HTTP.call('POST', alchemyURL, options);
console.log(sentimentData);
I was wondering how I might achieve the following using Ironrouter in meteorjs:
app.route("/api/tts").get(function(req,res){
res.type('audio/mpeg');
var text = req.query.q;
var request = require('request');
var url = "https://translate.google.pl/translate_tts?ie=UTF-8&q=" + text + "&tl=en&total=1&idx=0&client=t&prev=input";
request.get(url).pipe(res);
});
If you have iron:router installed already, then you already can. All you need to do is install request using meteorhacks:npm.
Then you simply write:
Router.route("/api/tts", function () {
// NodeJS request object
var req = this.request;
// NodeJS response object
var res = this.response;
res.type('audio/mpeg');
var text = req.query.q;
var request = Meteor.npmRequire('request');
var url = "https://translate.google.pl/translate_tts?ie=UTF-8&q=" + text + "&tl=en&total=1&idx=0&client=t&prev=input";
request.get(url).pipe(res);
}, { where: 'server' });
Let me know if that works.
You can't use IronRouter, Meteor routing is done on the client
(the answer from #rclai won't work because the request is still being sent from the client..)
This solution using the WebApp module shipped with Meteor to define server routes is exactly what you need.
e.g. something like this:
import { WebApp } from 'meteor/webapp';
WebApp.connectHandlers.use('/api/tts', (req, res, next) => {
var text = res.query.q;
var url = "https://translate.google.pl/translate_tts?ie=UTF-8&q=" + text + "&tl=en&total=1&idx=0&client=t&prev=input";
HTTP.call("GET", url, {}, function(err, response){
if(err){
res.writeHead(500);
res.end('Failed...');
}
else {
res.end(response.content);
}
});
});
What sets the return URL for the verification email. Not the link that gets generated and inserted in the email, but when you click the link, it ends up going to a page on your site after its verified. How can I set what page it goes to?
You can set the URL by specifying Accounts.emailTemplates.verifyEmail.text. Here's an example:
Accounts.emailTemplates.siteName = 'MyApp';
Accounts.emailTemplates.from = 'me#example.com';
Accounts.emailTemplates.verifyEmail.subject = function() {
return 'Verify your email address on MyApp';
};
Accounts.emailTemplates.verifyEmail.text = function(user, url) {
var token = url.split('/').pop();
var verifyEmailUrl = Meteor.absoluteUrl("verify-email/" + token);
return verifyEmailEmailBody(verifyEmailUrl);
};
The callback takes a url parameter which is the default URL generated by meteor. You can extract the verification token and then use it to build a custom URL. The function needs to return a body string, which you'll generate by implementing verifyEmailEmailBody.
On the client, you'll need to set up the corresponding route. When the route is run, you can call Accounts.verifyEmail.
You can change the verification url used in the email and then handle that route yourself. Here I'll use /verify and redirect to /wherever if successful.
client
var match = window.location.pathname.match(/^\/verify\/(.*)$/);
var token;
if (match) {
token = match[1];
}
Meteor.startup(function () {
if (token) {
Accounts.verifyEmail(token, function (error) {
if (!error) {
window.location.pathname = '/wherever';
}
});
}
});
server
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl('verify/' + token);
};