Closed jeffchuber closed 5 years ago
@jeffchuber wouldn't you need to add this to the static folder? https://www.gatsbyjs.org/docs/static-folder/
This will be copied as-is to the public folder, which can then be deployed to e.g. S3 very easily.
Also: this seems like this could be a good idea for a plugin to create this automatically!
Going to close as answered, but please feel free to re-open/reply if we can help further!
@DSchau thanks for the quick reply!
The issue is not getting a file into the static folder, the issue is accessing that file by the route/url apple demands https://domain.com/.well-known/apple-app-site-association
.
Desired scenario:
http://localhost:8000/.well-known/apple-app-site-association
{
"webcredentials": {
"apps": [
"KAW43335BG.com.companyName.App1",
"KAW43335BG.com.companyName.App2"
]
}
}
Looks like this is not an uncommon issue: https://github.com/gatsbyjs/gatsby/issues/4144
@jeffchuber you need to set up a redirect for your provider. For example, with S3: https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html
I've set up an example with Netlify with this repo, and you can see it working:
https://gatsby-apple-app-site-association.netlify.com/.well-known/apple-app-site-association
@DSchau I really appreciate the reply! I do think this is something a lot of devs will encounter or I wouldn't spend the time.
Unfortunately it needs to be solved at the routing level because Apple rejects redirects:
My understanding is that enabling a dev to add a route to gatsby-node.js
that can point directly at a file (instead of a React component) would be ideal. Something like this perhaps.
createPage({
path: '/.well-known/apple-app-site-association',
file: path.resolve('./public/.well-known/apple-app-site-association.json'),
})
Ah! Come on Apple, not making it easy on us!
Let's re-open this and find a more robust solution.
@DSchau what do you think about the idea above of setting up a route to serve a static file?
Hi @jeffchuber,
Why do you need to route to the apple-app-site-association
file? Safari and your iOS app look for if the file exists on your server for credential sharing.
It may be that your server is restricting access to dotfiles which are hidden to some OSs.
I use express to serve the gatsby files and express.static to allow access to the .well-known
folder.
app.use(
express.static(serverSettings.root, {
dotfiles: "allow"
})
);
app.get("/.well-known/change-password", (req, res) => {
res.redirect(301, "/change-password");
});
@rafcontreras thanks for the idea!
currently im using Cloudfront -> S3 -> gatsby static site.
I found a workaround which is hosting on a subdomain, so I'm going to close this for now out of respect for the community.
Hi @jeffchuber,
Why do you need to route to the
apple-app-site-association
file? Safari and your iOS app look for if the file exists on your server for credential sharing.It may be that your server is restricting access to dotfiles which are hidden to some OSs.
I use express to serve the gatsby files and express.static to allow access to the
.well-known
folder.app.use( express.static(serverSettings.root, { dotfiles: "allow" }) ); app.get("/.well-known/change-password", (req, res) => { res.redirect(301, "/change-password"); });
Where do you use express exactly ? here are the 2 ressource I found to serve this .well-know folder https://www.gatsbyjs.org/docs/api-proxy/ https://www.gatsbyjs.org/packages/gatsby-plugin-express/
second one also dont give info of where the express app should be called
Hey @adberard
I used Gatsby to create a hybrid app, so I need a server in between the user and the different APIs as some have dumb CORS settings.
I'm using expressJS to proxy API requests and I'm hosting it in Azure.
Here's my very small express server server.js
:
const url = require("url");
const express = require("express");
const gatsyExpress = require("gatsby-plugin-express");
const proxy = require("express-http-proxy");
const bodyParser = require("body-parser");
const expressStaticGzip = require("express-static-gzip");
// NodeJS Express server
const app = express();
// In case the server is behind a proxy
app.enable("trust proxy");
/* Removes the X-Powered-By header
to make it slightly harder for attackers
to see what potentially-vulnerable
technology powers your site*/
app.disable("x-powered-by");
// Parse API queries
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Redirect .well-known requests .well-known/change-password
app.get("/.well-known/change-password", (request, response) => {
response.redirect(301, "/change-password");
});
// Proxy API
app.use(
"/api",
proxy("https://api.domain.tld", {
https: true,
proxyReqPathResolver(request) {
const reqPath = url.parse(request.url).path;
return [api, reqPath].join("/");
},
proxyErrorHandler(error, next) {
console.log(error)
next(error);
}
})
);
// Send pre-compressed static files
app.use(
"/",
expressStaticGzip("/public", {
enableBrotli: true,
orderPreference: ["br", "gz"]
})
);
// Routes
app.use(
gatsyExpress("gatsby-express.json", {
publicDir: "/public",
template: "public/404/index.html",
redirectSlashes: true
})
);
// Allow dotfiles
app.use(
express.static(serverSettings.root, {
dotfiles: "allow"
})
);
// Serve 404 page on 404
app.use((req, res, next) => {
const err = new Error("Not Found");
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.sendFile("404/index.html", {
root: "/public"
});
});
// HTTP server
app
.listen(3000, () => {})
.on("error", e => {
console.log(e);
});
And here is my web.config
in case someone need it
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<urlCompression doStaticCompression="false" doDynamicCompression="true" />
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By" />
<add name="Access-Control-Allow-Origin" value="*" />
<add name="X-Frame-Options" value="DENY" />
<add name="Cache-Control" value="public, max-age=0, must-revalidate" />
<add name="X-XSS-Protection" value="1; mode=block" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />
<add name="Referrer-Policy" value="no-referrer" />
<add name="Content-Security-Policy" value="default-src 'self' https://*.google-analytics.com https://*.google.com; img-src * blob: data:; media-src *; object-src *; script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline';" />
<add name="X-Permitted-Cross-Domain-Policies" value="none" />
<add name="Feature-Policy" value="accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'" />
<add name="X-DNS-Prefetch-Control" value="on"/>
</customHeaders>
</httpProtocol>
<staticContent>
<clientCache cacheControlMode="DisableCache" />
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
<mimeMap fileExtension=".json" mimeType="application/json" />
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
<mimeMap fileExtension=".*" mimeType="text/plain" />
<mimeMap fileExtension=".mp4" mimeType="video/mp4" />
<mimeMap fileExtension=".m4v" mimeType="video/m4v" />
<mimeMap fileExtension=".aac" mimeType="audio/aac" />
<mimeMap fileExtension=".oga" mimeType="audio/ogg" />
<mimeMap fileExtension=".webm" mimeType="video/webm" />
<mimeMap fileExtension=".ogv" mimeType="video/ogv" />
<mimeMap fileExtension=".ogg" mimeType="video/ogg" />
<mimeMap fileExtension=".m4a" mimeType="video/mp4" />
</staticContent>
<modules runAllManagedModulesForAllRequests="false" />
<iisnode watchedFiles="web.config;*.js;"/>
<handlers>
<add name="iisnode" path="serve.js" verb="*" modules="iisnode" />
</handlers>
<security>
<requestFiltering removeServerHeader="true">
<hiddenSegments>
<remove segment="bin" />
</hiddenSegments>
</requestFiltering>
</security>
<rewrite>
<rules>
<clear />
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^serve.js\/debug[\/]?" />
</rule>
<rule name="app" enabled="true" patternSyntax="ECMAScript" stopProcessing="true">
<match url="iisnode.+" negate="true" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="Rewrite" url="serve.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Has anyone found a solid approach for this?
Is creating the file on postBuild
a good solution?
I also need a solution to this... @jeffchuber how did you host it on a subdomain? Does your links then all have to be written as subdomain.example.com
or is it possible to still have the universal link applicable to example.com
?
@PolGuixe did you find a good solution?
Hi all, I have successfully linked my app with a gatsby-js powered website recently. It's not complex, and you don't actually need to use any server-rendered route, just some little gotchas.
My website is hosted on Netlify
, but I believe the same idea applies to all hosting platforms.
Here're the steps.
apple-app-site-association
in static
folder, as mentioned by @DSchau
{"applinks":{"apps":[],"details":[{"appID":"HE7MCCDHL4.com.revteltech.nfcopenrewriter","paths":["*"]}]}}
Please notice the
apps
prop should be an empty array. (Apple has some updates regarding this format, but the above snippet works correctly for me)
By Apple's doc, you can serve it under
/
or/.well-known
, we use the former one here.
content-type
, it should be application/json
. More info check official Apple doc. For netlify
users, you can put a _headers
file in your static
directory, with following content:
/apple-app-site-association
Content-Type: application/json
By default
netlify
serve it asplain/text
, and I guess that's the reason why some people cannot make it work
You can use official tool. After you run the program, check the Link to Application
field.
The following screenshot means your association file is NOT recognized by Apple, you should check the format or content-type mentioned above:
On the other hand, the following screenshot means the association
file is correctly served. Apple might need some time to process the link between your app and website. If there's still an issue, then it should be in your iOS app rather than your website. (in my case, the validator still show the same kind of messages but the universal link
already works correctly):
Finally, open your website in iOS safari browser, see if it hints you about your app on the top area:
Hope that helps!
I'm interested in strategies to add
apple-app-site-association
to a Gatsby site. more hereApple requires that it be at
https://domain.com/.well-known/apple-app-site-association
and return flatjson
. (no html!)In m production website, I'm using
Cloudfront
in front ofs3
, so linking directly to it does not work. The Gatsby router picks up the route and gives me a 404.Thank you!