rebased embedr (#3511)
* skeleton of embedr service, based on bskyweb * embedr container setup * builds on this branch * actual routes * fix embedr go:embed * tweak embedr dockerfile * progress on embedr * fix path params * tweaks to build process * try to get embedr dockerfile to install embed deps * build this branch * updates to match sam's output HTML * try to unbreak embedr dockerfile * small embedr tweak * docker hack * get embed.js copied over to embedr * don't x-frame-options for embed.bsky.app * bskyembed: remove a console.log * use html/template for golang snippet generation * simplify embedr API fetches * missing file * Rm console.log fully --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>zio/stable
parent
196dd3a8ab
commit
58842d03a9
|
@ -0,0 +1,57 @@
|
||||||
|
name: build-and-push-embedr-aws
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- bnewbold/embedr
|
||||||
|
- bnewbold/embedr-rebase
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
|
||||||
|
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
|
||||||
|
PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }}
|
||||||
|
IMAGE_NAME: embed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
embedr-container-aws:
|
||||||
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ env.USERNAME}}
|
||||||
|
password: ${{ env.PASSWORD }}
|
||||||
|
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
file: ./Dockerfile.embedr
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
|
@ -0,0 +1,78 @@
|
||||||
|
FROM golang:1.21-bullseye AS build-env
|
||||||
|
|
||||||
|
WORKDIR /usr/src/social-app
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Node
|
||||||
|
ENV NODE_VERSION=18
|
||||||
|
ENV NVM_DIR=/usr/share/nvm
|
||||||
|
|
||||||
|
# Go
|
||||||
|
ENV GODEBUG="netdns=go"
|
||||||
|
ENV GOOS="linux"
|
||||||
|
ENV GOARCH="amd64"
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
ENV GOEXPERIMENT="loopvar"
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate the JavaScript webpack. NOTE: this will change
|
||||||
|
#
|
||||||
|
RUN mkdir --parents $NVM_DIR && \
|
||||||
|
wget \
|
||||||
|
--output-document=/tmp/nvm-install.sh \
|
||||||
|
https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh && \
|
||||||
|
bash /tmp/nvm-install.sh
|
||||||
|
|
||||||
|
RUN \. "$NVM_DIR/nvm.sh" && \
|
||||||
|
nvm install $NODE_VERSION && \
|
||||||
|
nvm use $NODE_VERSION && \
|
||||||
|
npm install --global yarn && \
|
||||||
|
yarn && \
|
||||||
|
cd bskyembed && yarn install --frozen-lockfile && cd .. && \
|
||||||
|
yarn intl:build && \
|
||||||
|
yarn build-embed
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bskyembed/dist
|
||||||
|
|
||||||
|
# hack around issue with empty directory and go:embed
|
||||||
|
RUN touch bskyweb/static/js/empty.txt
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate the embedr Go binary.
|
||||||
|
#
|
||||||
|
RUN cd bskyweb/ && \
|
||||||
|
go mod download && \
|
||||||
|
go mod verify
|
||||||
|
|
||||||
|
RUN cd bskyweb/ && \
|
||||||
|
go build \
|
||||||
|
-v \
|
||||||
|
-trimpath \
|
||||||
|
-tags timetzdata \
|
||||||
|
-o /embedr \
|
||||||
|
./cmd/embedr
|
||||||
|
|
||||||
|
FROM debian:bullseye-slim
|
||||||
|
|
||||||
|
ENV GODEBUG=netdns=go
|
||||||
|
ENV TZ=Etc/UTC
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install --yes \
|
||||||
|
dumb-init \
|
||||||
|
ca-certificates
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
|
||||||
|
WORKDIR /embedr
|
||||||
|
COPY --from=build-env /embedr /usr/bin/embedr
|
||||||
|
|
||||||
|
CMD ["/usr/bin/embedr"]
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app
|
||||||
|
LABEL org.opencontainers.image.description="embed.bsky.app Web App"
|
||||||
|
LABEL org.opencontainers.image.licenses=MIT
|
6
Makefile
6
Makefile
|
@ -13,6 +13,11 @@ build-web: ## Compile web bundle, copy to bskyweb directory
|
||||||
yarn intl:build
|
yarn intl:build
|
||||||
yarn build-web
|
yarn build-web
|
||||||
|
|
||||||
|
.PHONY: build-web-embed
|
||||||
|
build-web-embed: ## Compile web embed bundle, copy to bskyweb/embedr* directories
|
||||||
|
yarn intl:build
|
||||||
|
yarn build-embed
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run all tests
|
test: ## Run all tests
|
||||||
NODE_ENV=test EXPO_PUBLIC_ENV=test yarn test
|
NODE_ENV=test EXPO_PUBLIC_ENV=test yarn test
|
||||||
|
@ -28,6 +33,7 @@ lint: ## Run style checks and verify syntax
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps: ## Installs dependent libs using 'yarn install'
|
deps: ## Installs dependent libs using 'yarn install'
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
|
cd bskyembed && yarn install --frozen-lockfile
|
||||||
|
|
||||||
.PHONY: nvm-setup
|
.PHONY: nvm-setup
|
||||||
nvm-setup: ## Use NVM to install and activate node+yarn
|
nvm-setup: ## Use NVM to install and activate node+yarn
|
||||||
|
|
|
@ -17,9 +17,6 @@ const agent = new BskyAgent({
|
||||||
})
|
})
|
||||||
|
|
||||||
const uri = `at://${window.location.pathname.slice('/embed/'.length)}`
|
const uri = `at://${window.location.pathname.slice('/embed/'.length)}`
|
||||||
|
|
||||||
console.log(uri)
|
|
||||||
|
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
throw new Error('No uri in path')
|
throw new Error('No uri in path')
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,22 @@ test-coverage.out
|
||||||
|
|
||||||
# Don't check in the binary.
|
# Don't check in the binary.
|
||||||
/bskyweb
|
/bskyweb
|
||||||
|
/embedr
|
||||||
|
|
||||||
# Don't accidentally commit JS-generated code
|
# Don't accidentally commit JS-generated code
|
||||||
static/js/*.js
|
static/js/*.js
|
||||||
static/js/*.map
|
static/js/*.map
|
||||||
static/js/*.js.LICENSE.txt
|
static/js/*.js.LICENSE.txt
|
||||||
|
static/js/empty.txt
|
||||||
templates/scripts.html
|
templates/scripts.html
|
||||||
templates/*-embed.html
|
templates/*-embed.html
|
||||||
static/embed/*.html
|
static/embed/*.html
|
||||||
static/embed/assets/*.js
|
static/embed/assets/*.js
|
||||||
static/embed/assets/*.css
|
static/embed/assets/*.css
|
||||||
|
embedr-static/post-*.js
|
||||||
|
embedr-static/post-*.css
|
||||||
|
embedr-static/index-*.js
|
||||||
|
embedr-static/polyfills-*.js
|
||||||
|
|
||||||
# Don't ignore this file
|
# Don't ignore this file
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
|
|
@ -14,6 +14,7 @@ help: ## Print info about all commands
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: ## Build all executables
|
build: ## Build all executables
|
||||||
go build ./cmd/bskyweb
|
go build ./cmd/bskyweb
|
||||||
|
go build ./cmd/embedr
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run all tests
|
test: ## Run all tests
|
||||||
|
@ -43,3 +44,7 @@ check: ## Compile everything, checking syntax (does not output binaries)
|
||||||
.PHONY: run-dev-bskyweb
|
.PHONY: run-dev-bskyweb
|
||||||
run-dev-bskyweb: .env ## Runs 'bskyweb' for local dev
|
run-dev-bskyweb: .env ## Runs 'bskyweb' for local dev
|
||||||
GOLOG_LOG_LEVEL=info go run ./cmd/bskyweb serve
|
GOLOG_LOG_LEVEL=info go run ./cmd/bskyweb serve
|
||||||
|
|
||||||
|
.PHONY: run-dev-embedr
|
||||||
|
run-dev-embedr: .env ## Runs 'embedr' for local dev
|
||||||
|
GOLOG_LOG_LEVEL=info go run ./cmd/embedr serve
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
## oEmbed
|
||||||
|
|
||||||
|
<https://oembed.com/>
|
||||||
|
|
||||||
|
* URL scheme: `https://bsky.app/profile/*/post/*`
|
||||||
|
* API endpoint: `https://embed.bsky.app/oembed`
|
||||||
|
|
||||||
|
Request params:
|
||||||
|
|
||||||
|
- `url` (required): support both AT-URI and bsky.app URL
|
||||||
|
- `maxwidth` (optional): [220..550], 325 is default
|
||||||
|
- `maxheight` (not supported!)
|
||||||
|
- `format` (optional): only `json` supported
|
||||||
|
|
||||||
|
Response format:
|
||||||
|
|
||||||
|
- `type` (required): "rich"
|
||||||
|
- `version` (required): "1.0"
|
||||||
|
- `author_name` (optional): display name
|
||||||
|
- `author_url` (optional): profile URL
|
||||||
|
- `provider_name` (optional): "Bluesky Social"
|
||||||
|
- `provider_url` (optional): "https://bsky.app"
|
||||||
|
- `cache_age` (optional, integer seconds): 86400 (24 hours) (?)
|
||||||
|
- `width` (required): ?
|
||||||
|
- `height` (required): ?
|
||||||
|
|
||||||
|
Not used:
|
||||||
|
|
||||||
|
- title (optional): A text title, describing the resource.
|
||||||
|
- thumbnail_url (optional): A URL to a thumbnail image representing the resource. The thumbnail must respect any maxwidth and maxheight parameters. If this parameter is present, thumbnail_width and thumbnail_height must also be present.
|
||||||
|
- thumbnail_width (optional): The width of the optional thumbnail. If this parameter is present, thumbnail_url and thumbnail_height must also be present.
|
||||||
|
- thumbnail_height (optional): The height of the optional thumbnail. If this parameter is present, thumbnail_url and thumbnail_width must also be present.
|
||||||
|
|
||||||
|
Only `json` is supported; `xml` is a 501.
|
||||||
|
|
||||||
|
```
|
||||||
|
<link rel="alternate" type="application/json+oembed" href="https://embed.bsky.app/oembed?format=json&url=https://bsky.app/profile/bnewbold.net/post/abc123" />
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## iframe URL
|
||||||
|
|
||||||
|
`https://embed.bsky.app/embed/<did>/app.bsky.feed.post/<rkey>`
|
||||||
|
`https://embed.bsky.app/static/embed.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
<blockquote class="bluesky-post" data-lang="en" data-align="center">
|
||||||
|
<p lang="en" dir="ltr">{{ post-text }}</p>
|
||||||
|
— US Department of the Interior (@Interior) <a href="https://twitter.com/Interior/status/463440424141459456?ref_src=twsrc%5Etfw">May 5, 2014</a>
|
||||||
|
</blockquote>
|
||||||
|
```
|
|
@ -0,0 +1 @@
|
||||||
|
/bskyweb
|
|
@ -0,0 +1,207 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
appbsky "github.com/bluesky-social/indigo/api/bsky"
|
||||||
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrPostNotFound = errors.New("post not found")
|
||||||
|
var ErrPostNotPublic = errors.New("post is not publicly accessible")
|
||||||
|
|
||||||
|
func (srv *Server) getBlueskyPost(ctx context.Context, did syntax.DID, rkey syntax.RecordKey) (*appbsky.FeedDefs_PostView, error) {
|
||||||
|
|
||||||
|
// fetch the post post (with extra context)
|
||||||
|
uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey)
|
||||||
|
tpv, err := appbsky.FeedGetPostThread(ctx, srv.xrpcc, 1, 0, uri)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to fetch post: %s\t%v", uri, err)
|
||||||
|
// TODO: detect 404, specifically?
|
||||||
|
return nil, ErrPostNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if tpv.Thread.FeedDefs_BlockedPost != nil {
|
||||||
|
return nil, ErrPostNotPublic
|
||||||
|
} else if tpv.Thread.FeedDefs_ThreadViewPost.Post == nil {
|
||||||
|
return nil, ErrPostNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
postView := tpv.Thread.FeedDefs_ThreadViewPost.Post
|
||||||
|
for _, label := range postView.Author.Labels {
|
||||||
|
if label.Src == postView.Author.Did && label.Val == "!no-unauthenticated" {
|
||||||
|
return nil, ErrPostNotPublic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return postView, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) WebHome(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "home.html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OEmbedResponse struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
AuthorName string `json:"author_name,omitempty"`
|
||||||
|
AuthorURL string `json:"author_url,omitempty"`
|
||||||
|
ProviderName string `json:"provider_url,omitempty"`
|
||||||
|
CacheAge int `json:"cache_age,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
Height *int `json:"height,omitempty"`
|
||||||
|
HTML string `json:"html,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) parseBlueskyURL(ctx context.Context, raw string) (*syntax.ATURI, error) {
|
||||||
|
|
||||||
|
if raw == "" {
|
||||||
|
return nil, fmt.Errorf("empty url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// first try simple AT-URI
|
||||||
|
uri, err := syntax.ParseATURI(raw)
|
||||||
|
if nil == err {
|
||||||
|
return &uri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// then try bsky.app post URL
|
||||||
|
u, err := url.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.Hostname() != "bsky.app" {
|
||||||
|
return nil, fmt.Errorf("only bsky.app URLs currently supported")
|
||||||
|
}
|
||||||
|
pathParts := strings.Split(u.Path, "/") // NOTE: pathParts[0] will be empty string
|
||||||
|
if len(pathParts) != 5 || pathParts[1] != "profile" || pathParts[3] != "post" {
|
||||||
|
return nil, fmt.Errorf("only bsky.app post URLs currently supported")
|
||||||
|
}
|
||||||
|
atid, err := syntax.ParseAtIdentifier(pathParts[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rkey, err := syntax.ParseRecordKey(pathParts[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var did syntax.DID
|
||||||
|
if atid.IsHandle() {
|
||||||
|
ident, err := srv.dir.Lookup(ctx, *atid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
did = ident.DID
|
||||||
|
} else {
|
||||||
|
did, err = atid.AsDID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't really need to re-parse here, if we had test coverage
|
||||||
|
aturi, err := syntax.ParseATURI(fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &aturi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) WebOEmbed(c echo.Context) error {
|
||||||
|
formatParam := c.QueryParam("format")
|
||||||
|
if formatParam != "" && formatParam != "json" {
|
||||||
|
return c.String(http.StatusNotImplemented, "Unsupported oEmbed format: "+formatParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do we actually do something with width?
|
||||||
|
width := 550
|
||||||
|
maxWidthParam := c.QueryParam("maxwidth")
|
||||||
|
if maxWidthParam != "" {
|
||||||
|
maxWidthInt, err := strconv.Atoi(maxWidthParam)
|
||||||
|
if err != nil || maxWidthInt < 220 || maxWidthInt > 550 {
|
||||||
|
return c.String(http.StatusBadRequest, "Invalid maxwidth (expected integer between 220 and 550)")
|
||||||
|
}
|
||||||
|
width = maxWidthInt
|
||||||
|
}
|
||||||
|
// NOTE: maxheight ignored
|
||||||
|
|
||||||
|
aturi, err := srv.parseBlueskyURL(c.Request().Context(), c.QueryParam("url"))
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, fmt.Sprintf("Expected 'url' to be bsky.app URL or AT-URI: %v", err))
|
||||||
|
}
|
||||||
|
if aturi.Collection() != syntax.NSID("app.bsky.feed.post") {
|
||||||
|
return c.String(http.StatusNotImplemented, "Only posts (app.bsky.feed.post records) can be embedded currently")
|
||||||
|
}
|
||||||
|
did, err := aturi.Authority().AsDID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := srv.getBlueskyPost(c.Request().Context(), did, aturi.RecordKey())
|
||||||
|
if err == ErrPostNotFound {
|
||||||
|
return c.String(http.StatusNotFound, fmt.Sprintf("%v", err))
|
||||||
|
} else if err == ErrPostNotPublic {
|
||||||
|
return c.String(http.StatusForbidden, fmt.Sprintf("%v", err))
|
||||||
|
} else if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
html, err := srv.postEmbedHTML(post)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
data := OEmbedResponse{
|
||||||
|
Type: "rich",
|
||||||
|
Version: "1.0",
|
||||||
|
AuthorName: "@" + post.Author.Handle,
|
||||||
|
AuthorURL: fmt.Sprintf("https://bsky.app/profile/%s", post.Author.Handle),
|
||||||
|
ProviderName: "Bluesky Social",
|
||||||
|
CacheAge: 86400,
|
||||||
|
Width: width,
|
||||||
|
Height: nil,
|
||||||
|
HTML: html,
|
||||||
|
}
|
||||||
|
if post.Author.DisplayName != nil {
|
||||||
|
data.AuthorName = fmt.Sprintf("%s (@%s)", *post.Author.DisplayName, post.Author.Handle)
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) WebPostEmbed(c echo.Context) error {
|
||||||
|
|
||||||
|
// sanity check arguments. don't 4xx, just let app handle if not expected format
|
||||||
|
rkeyParam := c.Param("rkey")
|
||||||
|
rkey, err := syntax.ParseRecordKey(rkeyParam)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, fmt.Sprintf("Invalid RecordKey: %v", err))
|
||||||
|
}
|
||||||
|
didParam := c.Param("did")
|
||||||
|
did, err := syntax.ParseDID(didParam)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, fmt.Sprintf("Invalid DID: %v", err))
|
||||||
|
}
|
||||||
|
_ = rkey
|
||||||
|
_ = did
|
||||||
|
|
||||||
|
// NOTE: this request was't really necessary; the JS will do the same fetch
|
||||||
|
/*
|
||||||
|
postView, err := srv.getBlueskyPost(ctx, did, rkey)
|
||||||
|
if err == ErrPostNotFound {
|
||||||
|
return c.String(http.StatusNotFound, fmt.Sprintf("%v", err))
|
||||||
|
} else if err == ErrPostNotPublic {
|
||||||
|
return c.String(http.StatusForbidden, fmt.Sprintf("%v", err))
|
||||||
|
} else if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "postEmbed.html", nil)
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
|
||||||
|
logging "github.com/ipfs/go-log"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.Logger("embedr")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logging.SetAllLoggers(logging.LevelDebug)
|
||||||
|
//logging.SetAllLoggers(logging.LevelWarn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
run(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(args []string) {
|
||||||
|
|
||||||
|
app := cli.App{
|
||||||
|
Name: "embedr",
|
||||||
|
Usage: "web server for embed.bsky.app post embeds",
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []*cli.Command{
|
||||||
|
&cli.Command{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "run the server",
|
||||||
|
Action: serve,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "appview-host",
|
||||||
|
Usage: "method, hostname, and port of PDS instance",
|
||||||
|
Value: "https://public.api.bsky.app",
|
||||||
|
EnvVars: []string{"ATP_APPVIEW_HOST"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "http-address",
|
||||||
|
Usage: "Specify the local IP/port to bind to",
|
||||||
|
Required: false,
|
||||||
|
Value: ":8100",
|
||||||
|
EnvVars: []string{"HTTP_ADDRESS"},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
Usage: "Enable debug mode",
|
||||||
|
Value: false,
|
||||||
|
Required: false,
|
||||||
|
EnvVars: []string{"DEBUG"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.RunAndExitOnError()
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bluesky-social/indigo/atproto/identity"
|
||||||
|
"github.com/bluesky-social/indigo/util/cliutil"
|
||||||
|
"github.com/bluesky-social/indigo/xrpc"
|
||||||
|
"github.com/bluesky-social/social-app/bskyweb"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
|
"github.com/klauspost/compress/gzip"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
echo *echo.Echo
|
||||||
|
httpd *http.Server
|
||||||
|
xrpcc *xrpc.Client
|
||||||
|
dir identity.Directory
|
||||||
|
}
|
||||||
|
|
||||||
|
func serve(cctx *cli.Context) error {
|
||||||
|
debug := cctx.Bool("debug")
|
||||||
|
httpAddress := cctx.String("http-address")
|
||||||
|
appviewHost := cctx.String("appview-host")
|
||||||
|
|
||||||
|
// Echo
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// create a new session (no auth)
|
||||||
|
xrpcc := &xrpc.Client{
|
||||||
|
Client: cliutil.NewHttpClient(),
|
||||||
|
Host: appviewHost,
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpd
|
||||||
|
var (
|
||||||
|
httpTimeout = 2 * time.Minute
|
||||||
|
httpMaxHeaderBytes = 2 * (1024 * 1024)
|
||||||
|
gzipMinSizeBytes = 1024 * 2
|
||||||
|
gzipCompressionLevel = gzip.BestSpeed
|
||||||
|
gzipExceptMIMETypes = []string{"image/png"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wrap the server handler in a gzip handler to compress larger responses.
|
||||||
|
gzipHandler, err := gzhttp.NewWrapper(
|
||||||
|
gzhttp.MinSize(gzipMinSizeBytes),
|
||||||
|
gzhttp.CompressionLevel(gzipCompressionLevel),
|
||||||
|
gzhttp.ExceptContentTypes(gzipExceptMIMETypes),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// server
|
||||||
|
//
|
||||||
|
server := &Server{
|
||||||
|
echo: e,
|
||||||
|
xrpcc: xrpcc,
|
||||||
|
dir: identity.DefaultDirectory(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the HTTP server.
|
||||||
|
server.httpd = &http.Server{
|
||||||
|
Handler: gzipHandler(server),
|
||||||
|
Addr: httpAddress,
|
||||||
|
WriteTimeout: httpTimeout,
|
||||||
|
ReadTimeout: httpTimeout,
|
||||||
|
MaxHeaderBytes: httpMaxHeaderBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
e.HideBanner = true
|
||||||
|
|
||||||
|
tmpl := &Template{
|
||||||
|
templates: template.Must(template.ParseFS(bskyweb.EmbedrTemplateFS, "embedr-templates/*.html")),
|
||||||
|
}
|
||||||
|
e.Renderer = tmpl
|
||||||
|
e.HTTPErrorHandler = server.errorHandler
|
||||||
|
|
||||||
|
e.IPExtractor = echo.ExtractIPFromXFFHeader()
|
||||||
|
|
||||||
|
// SECURITY: Do not modify without due consideration.
|
||||||
|
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
|
||||||
|
ContentTypeNosniff: "nosniff",
|
||||||
|
// diable XFrameOptions; we're embedding here!
|
||||||
|
HSTSMaxAge: 31536000, // 365 days
|
||||||
|
// TODO:
|
||||||
|
// ContentSecurityPolicy
|
||||||
|
// XSSProtection
|
||||||
|
}))
|
||||||
|
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||||
|
// Don't log requests for static content.
|
||||||
|
Skipper: func(c echo.Context) bool {
|
||||||
|
return strings.HasPrefix(c.Request().URL.Path, "/static")
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
|
||||||
|
Skipper: middleware.DefaultSkipper,
|
||||||
|
Store: middleware.NewRateLimiterMemoryStoreWithConfig(
|
||||||
|
middleware.RateLimiterMemoryStoreConfig{
|
||||||
|
Rate: 10, // requests per second
|
||||||
|
Burst: 30, // allow bursts
|
||||||
|
ExpiresIn: 3 * time.Minute, // garbage collect entries older than 3 minutes
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IdentifierExtractor: func(ctx echo.Context) (string, error) {
|
||||||
|
id := ctx.RealIP()
|
||||||
|
return id, nil
|
||||||
|
},
|
||||||
|
DenyHandler: func(c echo.Context, identifier string, err error) error {
|
||||||
|
return c.String(http.StatusTooManyRequests, "Your request has been rate limited. Please try again later. Contact support@bsky.app if you believe this was a mistake.\n")
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// redirect trailing slash to non-trailing slash.
|
||||||
|
// all of our current endpoints have no trailing slash.
|
||||||
|
e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
|
||||||
|
RedirectCode: http.StatusFound,
|
||||||
|
}))
|
||||||
|
|
||||||
|
//
|
||||||
|
// configure routes
|
||||||
|
//
|
||||||
|
// static files
|
||||||
|
staticHandler := http.FileServer(func() http.FileSystem {
|
||||||
|
if debug {
|
||||||
|
log.Debugf("serving static file from the local file system")
|
||||||
|
return http.FS(os.DirFS("embedr-static"))
|
||||||
|
}
|
||||||
|
fsys, err := fs.Sub(bskyweb.EmbedrStaticFS, "embedr-static")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return http.FS(fsys)
|
||||||
|
}())
|
||||||
|
|
||||||
|
e.GET("/robots.txt", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/ips-v4", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/ips-v6", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/.well-known/*", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/security.txt", func(c echo.Context) error {
|
||||||
|
return c.Redirect(http.StatusMovedPermanently, "/.well-known/security.txt")
|
||||||
|
})
|
||||||
|
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler)), func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
path := c.Request().URL.Path
|
||||||
|
maxAge := 1 * (60 * 60) // default is 1 hour
|
||||||
|
|
||||||
|
// Cache javascript and images files for 1 week, which works because
|
||||||
|
// they're always versioned (e.g. /static/js/main.64c14927.js)
|
||||||
|
if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") {
|
||||||
|
maxAge = 7 * (60 * 60 * 24) // 1 week
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Response().Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// actual routes
|
||||||
|
e.GET("/", server.WebHome)
|
||||||
|
e.GET("/iframe-resize.js", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/embed.js", echo.WrapHandler(staticHandler))
|
||||||
|
e.GET("/oembed", server.WebOEmbed)
|
||||||
|
e.GET("/embed/:did/app.bsky.feed.post/:rkey", server.WebPostEmbed)
|
||||||
|
|
||||||
|
// Start the server.
|
||||||
|
log.Infof("starting server address=%s", httpAddress)
|
||||||
|
go func() {
|
||||||
|
if err := server.httpd.ListenAndServe(); err != nil {
|
||||||
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Errorf("HTTP server shutting down unexpectedly: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for a signal to exit.
|
||||||
|
log.Info("registering OS exit signal handler")
|
||||||
|
quit := make(chan struct{})
|
||||||
|
exitSignals := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(exitSignals, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
sig := <-exitSignals
|
||||||
|
log.Infof("received OS exit signal: %s", sig)
|
||||||
|
|
||||||
|
// Shut down the HTTP server.
|
||||||
|
if err := server.Shutdown(); err != nil {
|
||||||
|
log.Errorf("HTTP server shutdown error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the return that causes an exit.
|
||||||
|
close(quit)
|
||||||
|
}()
|
||||||
|
<-quit
|
||||||
|
log.Infof("graceful shutdown complete")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
srv.echo.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) Shutdown() error {
|
||||||
|
log.Info("shutting down")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return srv.httpd.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) errorHandler(err error, c echo.Context) {
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
if he, ok := err.(*echo.HTTPError); ok {
|
||||||
|
code = he.Code
|
||||||
|
}
|
||||||
|
c.Logger().Error(err)
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"statusCode": code,
|
||||||
|
}
|
||||||
|
c.Render(code, "error.html", data)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
appbsky "github.com/bluesky-social/indigo/api/bsky"
|
||||||
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (srv *Server) postEmbedHTML(postView *appbsky.FeedDefs_PostView) (string, error) {
|
||||||
|
// ensure that there isn't an injection from the URI
|
||||||
|
aturi, err := syntax.ParseATURI(postView.Uri)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("bad AT-URI in reponse", "aturi", aturi, "err", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
post, ok := postView.Record.Val.(*appbsky.FeedPost)
|
||||||
|
if !ok {
|
||||||
|
log.Error("bad post record value", "err", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
const tpl = `<blockquote class="bluesky-embed" data-bluesky-uri="{{ .PostURI }}" data-bluesky-cid="{{ .PostCID }}"><p{{ if .PostLang }} lang="{{ .PostLang }}"{{ end }}>{{ .PostText }}</p>— {{ .PostAuthor }} {{ .PostIndexedAt }}</blockquote><script async src="{{ .WidgetURL }}" charset="utf-8"></script>`
|
||||||
|
|
||||||
|
t, err := template.New("snippet").Parse(tpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("template parse error", "err", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lang string
|
||||||
|
if len(post.Langs) > 0 {
|
||||||
|
lang = post.Langs[0]
|
||||||
|
}
|
||||||
|
var authorName string
|
||||||
|
if postView.Author.DisplayName != nil {
|
||||||
|
authorName = fmt.Sprintf("%s (@%s)", *postView.Author.DisplayName, postView.Author.Handle)
|
||||||
|
} else {
|
||||||
|
authorName = fmt.Sprintf("@%s", postView.Author.Handle)
|
||||||
|
}
|
||||||
|
fmt.Println(postView.Uri)
|
||||||
|
fmt.Println(fmt.Sprintf("%s", postView.Uri))
|
||||||
|
data := struct {
|
||||||
|
PostURI template.URL
|
||||||
|
PostCID string
|
||||||
|
PostLang string
|
||||||
|
PostText string
|
||||||
|
PostAuthor string
|
||||||
|
PostIndexedAt string
|
||||||
|
WidgetURL template.URL
|
||||||
|
}{
|
||||||
|
PostURI: template.URL(postView.Uri),
|
||||||
|
PostCID: postView.Cid,
|
||||||
|
PostLang: lang,
|
||||||
|
PostText: post.Text,
|
||||||
|
PostAuthor: authorName,
|
||||||
|
PostIndexedAt: postView.IndexedAt, // TODO: createdAt?
|
||||||
|
WidgetURL: template.URL("https://embed.bsky.app/static/embed.js"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = t.Execute(&buf, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("template parse error", "err", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
Contact: mailto:security@bsky.app
|
||||||
|
Preferred-Languages: en
|
||||||
|
Canonical: https://bsky.app/.well-known/security.txt
|
||||||
|
Acknowledgements: https://github.com/bluesky-social/atproto/blob/main/CONTRIBUTORS.md
|
|
@ -0,0 +1 @@
|
||||||
|
/* embed javascript widget will go here */
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
/* script to resize embed ifame would go here? */
|
|
@ -0,0 +1,30 @@
|
||||||
|
13.59.225.103/32
|
||||||
|
3.18.47.21/32
|
||||||
|
18.191.104.94/32
|
||||||
|
3.129.134.255/32
|
||||||
|
3.129.237.113/32
|
||||||
|
3.138.56.230/32
|
||||||
|
44.218.10.163/32
|
||||||
|
54.89.116.251/32
|
||||||
|
44.217.166.202/32
|
||||||
|
54.208.221.149/32
|
||||||
|
54.166.110.54/32
|
||||||
|
54.208.146.65/32
|
||||||
|
3.129.234.15/32
|
||||||
|
3.138.168.48/32
|
||||||
|
3.23.53.192/32
|
||||||
|
52.14.89.53/32
|
||||||
|
3.18.126.246/32
|
||||||
|
3.136.69.4/32
|
||||||
|
3.22.137.152/32
|
||||||
|
3.132.247.113/32
|
||||||
|
3.141.186.104/32
|
||||||
|
18.222.43.214/32
|
||||||
|
3.14.35.197/32
|
||||||
|
3.23.182.70/32
|
||||||
|
18.224.144.69/32
|
||||||
|
3.129.98.29/32
|
||||||
|
3.130.134.20/32
|
||||||
|
3.17.197.213/32
|
||||||
|
18.223.234.21/32
|
||||||
|
3.20.248.177/32
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Hello Friends!
|
||||||
|
# If you are considering bulk or automated crawling, you may want to look in
|
||||||
|
# to our protocol (API), including a firehose of updates. See: https://atproto.com/
|
||||||
|
|
||||||
|
# By default, may crawl anything on this domain. HTTP 429 ("backoff") status
|
||||||
|
# codes are used for rate-limiting. Up to a handful concurrent requests should
|
||||||
|
# be ok.
|
||||||
|
User-Agent: *
|
||||||
|
Allow: /
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder!
|
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>embed.bsky.app homepage</h1>
|
||||||
|
<p>could redirect to bsky.app? or show a "create embed" widget?
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
oembed JSON response will go here
|
|
@ -0,0 +1 @@
|
||||||
|
embed post HTML will go here
|
|
@ -4,3 +4,6 @@ import "embed"
|
||||||
|
|
||||||
//go:embed static/*
|
//go:embed static/*
|
||||||
var StaticFS embed.FS
|
var StaticFS embed.FS
|
||||||
|
|
||||||
|
//go:embed embedr-static/*
|
||||||
|
var EmbedrStaticFS embed.FS
|
||||||
|
|
|
@ -4,3 +4,6 @@ import "embed"
|
||||||
|
|
||||||
//go:embed templates/*
|
//go:embed templates/*
|
||||||
var TemplateFS embed.FS
|
var TemplateFS embed.FS
|
||||||
|
|
||||||
|
//go:embed embedr-templates/*
|
||||||
|
var EmbedrTemplateFS embed.FS
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"build-ios": "yarn use-build-number-with-bump eas build -p ios",
|
"build-ios": "yarn use-build-number-with-bump eas build -p ios",
|
||||||
"build-android": "yarn use-build-number-with-bump eas build -p android",
|
"build-android": "yarn use-build-number-with-bump eas build -p android",
|
||||||
"build": "yarn use-build-number-with-bump eas build",
|
"build": "yarn use-build-number-with-bump eas build",
|
||||||
"build-embed": "cd bskyembed && yarn build && cd .. && node ./scripts/post-embed-build.js",
|
"build-embed": "cd bskyembed && yarn build && yarn build-snippet && cd .. && node ./scripts/post-embed-build.js",
|
||||||
"start": "expo start --dev-client",
|
"start": "expo start --dev-client",
|
||||||
"start:prod": "expo start --dev-client --no-dev --minify",
|
"start:prod": "expo start --dev-client --no-dev --minify",
|
||||||
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
|
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
|
||||||
|
|
|
@ -1,49 +1,65 @@
|
||||||
// const path = require('node:path')
|
const path = require('node:path')
|
||||||
// const fs = require('node:fs')
|
const fs = require('node:fs')
|
||||||
|
|
||||||
// const projectRoot = path.join(__dirname, '..')
|
const projectRoot = path.join(__dirname, '..')
|
||||||
|
|
||||||
// // copy embed assets to web-build
|
// copy embed assets to embedr
|
||||||
|
|
||||||
// const embedAssetSource = path.join(
|
const embedAssetSource = path.join(projectRoot, 'bskyembed', 'dist', 'static')
|
||||||
// projectRoot,
|
|
||||||
// 'bskyembed',
|
|
||||||
// 'dist',
|
|
||||||
// 'static',
|
|
||||||
// 'embed',
|
|
||||||
// 'assets',
|
|
||||||
// )
|
|
||||||
|
|
||||||
// const embedAssetDest = path.join(
|
const embedAssetDest = path.join(projectRoot, 'bskyweb', 'embedr-static')
|
||||||
// projectRoot,
|
|
||||||
// 'web-build',
|
|
||||||
// 'static',
|
|
||||||
// 'embed',
|
|
||||||
// 'assets',
|
|
||||||
// )
|
|
||||||
|
|
||||||
// fs.cpSync(embedAssetSource, embedAssetDest, {recursive: true})
|
fs.cpSync(embedAssetSource, embedAssetDest, {recursive: true})
|
||||||
|
|
||||||
// // copy entrypoint(s) to web-build
|
const embedEmbedJSSource = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'bskyembed',
|
||||||
|
'dist',
|
||||||
|
'embed.js',
|
||||||
|
)
|
||||||
|
|
||||||
// // additional entrypoints will need more work, but this'll do for now
|
const embedEmbedJSDest = path.join(
|
||||||
// const embedHtmlSource = path.join(
|
projectRoot,
|
||||||
// projectRoot,
|
'bskyweb',
|
||||||
// 'bskyembed',
|
'embedr-static',
|
||||||
// 'dist',
|
'embed.js',
|
||||||
// 'index.html',
|
)
|
||||||
// )
|
|
||||||
|
|
||||||
// const embedHtmlDest = path.join(
|
fs.cpSync(embedEmbedJSSource, embedEmbedJSDest)
|
||||||
// projectRoot,
|
|
||||||
// 'web-build',
|
|
||||||
// 'static',
|
|
||||||
// 'embed',
|
|
||||||
// 'post.html',
|
|
||||||
// )
|
|
||||||
|
|
||||||
// fs.copyFileSync(embedHtmlSource, embedHtmlDest)
|
// copy entrypoint(s) to embedr
|
||||||
|
|
||||||
// console.log(`Copied embed assets to web-build`)
|
// additional entrypoints will need more work, but this'll do for now
|
||||||
|
const embedHomeHtmlSource = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'bskyembed',
|
||||||
|
'dist',
|
||||||
|
'index.html',
|
||||||
|
)
|
||||||
|
|
||||||
console.log('post-embed-build.js - waiting for embedr!')
|
const embedHomeHtmlDest = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'bskyweb',
|
||||||
|
'embedr-templates',
|
||||||
|
'home.html',
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.copyFileSync(embedHomeHtmlSource, embedHomeHtmlDest)
|
||||||
|
|
||||||
|
const embedPostHtmlSource = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'bskyembed',
|
||||||
|
'dist',
|
||||||
|
'post.html',
|
||||||
|
)
|
||||||
|
|
||||||
|
const embedPostHtmlDest = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'bskyweb',
|
||||||
|
'embedr-templates',
|
||||||
|
'postEmbed.html',
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.copyFileSync(embedPostHtmlSource, embedPostHtmlDest)
|
||||||
|
|
||||||
|
console.log(`Copied embed assets to embedr`)
|
||||||
|
|
Loading…
Reference in New Issue