mirror of
https://github.com/tlsnotary/tlsn-plugin-boilerplate.git
synced 2026-01-09 19:57:53 -05:00
Example plugin for Spotify top artist
This commit is contained in:
BIN
examples/spotify/assets/icon.png
Normal file
BIN
examples/spotify/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
37
examples/spotify/config.json
Normal file
37
examples/spotify/config.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"title": "Spotify Top Artist",
|
||||
"description": "Notarize your favorite artist on Spotify",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Visit Spotify webplayer",
|
||||
"cta": "Go",
|
||||
"action": "start"
|
||||
},
|
||||
{
|
||||
"title": "Collect credentials",
|
||||
"description": "Login to your account if you haven't already",
|
||||
"cta": "Go",
|
||||
"action": "two"
|
||||
},
|
||||
{
|
||||
"title": "Notarize",
|
||||
"cta": "Notarize",
|
||||
"action": "three",
|
||||
"prover": true
|
||||
}
|
||||
],
|
||||
"hostFunctions": [
|
||||
"redirect",
|
||||
"notarize"
|
||||
],
|
||||
"cookies": [],
|
||||
"headers": [
|
||||
"api.spotify.com"
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"url": "https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=1",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
}
|
||||
36
examples/spotify/esbuild.js
Normal file
36
examples/spotify/esbuild.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const esbuild = require('esbuild');
|
||||
const path = require('path');
|
||||
const { name } = require('./package.json');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const outputDir = 'dist';
|
||||
const entryFile = 'src/index.ts';
|
||||
const outputFile = path.join(outputDir, 'index.js');
|
||||
const outputWasm = path.join(outputDir, `${name}.tlsn.wasm`);
|
||||
|
||||
async function build() {
|
||||
try {
|
||||
await esbuild.build({
|
||||
entryPoints: [entryFile],
|
||||
bundle: true,
|
||||
outdir: outputDir, // Use outdir for directory output
|
||||
sourcemap: true,
|
||||
minify: false, // might want to use true for production build
|
||||
format: 'cjs', // needs to be CJS for now
|
||||
target: ['es2020'], // don't go over es2020 because quickjs doesn't support it
|
||||
loader: {'.png': 'dataurl'}
|
||||
});
|
||||
|
||||
console.log('esbuild completed successfully.');
|
||||
|
||||
// Run extism-js to generate the wasm file
|
||||
const extismCommand = `extism-js ${outputFile} -i src/index.d.ts -o ${outputWasm}`;
|
||||
execSync(extismCommand, { stdio: 'inherit' });
|
||||
console.log('extism-js completed successfully.');
|
||||
} catch (error) {
|
||||
console.error('Build process failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
build();
|
||||
17
examples/spotify/package.json
Normal file
17
examples/spotify/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "spotify_top_artist",
|
||||
"version": "1.0.0",
|
||||
"description": "Demo TLSNotary plugin to notarize your top artist on Spotify",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "node esbuild.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "TLSNotary",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.0.1",
|
||||
"esbuild": "^0.19.6",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
14
examples/spotify/src/index.d.ts
vendored
Normal file
14
examples/spotify/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
declare module 'main' {
|
||||
// Extism exports take no params and return an I32
|
||||
export function start(): I32;
|
||||
export function two(): I32;
|
||||
export function three(): I32;
|
||||
export function config(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
redirect(ptr: I64): void;
|
||||
notarize(ptr: I64): I64;
|
||||
}
|
||||
}
|
||||
82
examples/spotify/src/index.ts
Normal file
82
examples/spotify/src/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import icon from '../assets/icon.png';
|
||||
import config_json from '../config.json';
|
||||
import { redirect, notarize, outputJSON, getCookiesByHost, getHeadersByHost } from './utils/hf.js';
|
||||
|
||||
/**
|
||||
* Plugin configuration
|
||||
* This configurations defines the plugin, most importantly:
|
||||
* * the different steps
|
||||
* * the user data (headers, cookies) it will access
|
||||
* * the web requests it will query (or notarize)
|
||||
*/
|
||||
export function config() {
|
||||
outputJSON({
|
||||
...config_json,
|
||||
icon: icon
|
||||
});
|
||||
}
|
||||
|
||||
function isValidHost(urlString: string) {
|
||||
const url = new URL(urlString);
|
||||
return url.hostname === 'open.spotify.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the first (start) plugin step
|
||||
*/
|
||||
export function start() {
|
||||
if (!isValidHost(Config.get('tabUrl'))) {
|
||||
redirect('https://open.spotify.com');
|
||||
outputJSON(false);
|
||||
return;
|
||||
}
|
||||
outputJSON(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of step "two".
|
||||
* This step collects and validates authentication cookies and headers for 'api.x.com'.
|
||||
* If all required information, it creates the request object.
|
||||
* Note that the url needs to be specified in the `config` too, otherwise the request will be refused.
|
||||
*/
|
||||
export function two() {
|
||||
const headers = getHeadersByHost('api.spotify.com');
|
||||
|
||||
if (
|
||||
!headers['authorization']
|
||||
) {
|
||||
outputJSON(false);
|
||||
return;
|
||||
}
|
||||
|
||||
outputJSON({
|
||||
url: 'https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=1',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-twitter-client-language': 'en',
|
||||
'x-csrf-token': headers['x-csrf-token'],
|
||||
Host: 'api.x.com',
|
||||
authorization: headers.authorization,
|
||||
Connection: 'close',
|
||||
},
|
||||
secretHeaders: [
|
||||
`authorization: ${headers.authorization}`,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: calls the `notarize` host function
|
||||
*/
|
||||
export function three() {
|
||||
const params = JSON.parse(Host.inputString());
|
||||
|
||||
if (!params) {
|
||||
outputJSON(false);
|
||||
} else {
|
||||
const id = notarize({
|
||||
...params,
|
||||
});
|
||||
outputJSON(id);
|
||||
}
|
||||
}
|
||||
39
examples/spotify/src/utils/hf.js
Normal file
39
examples/spotify/src/utils/hf.js
Normal file
@@ -0,0 +1,39 @@
|
||||
function redirect(url) {
|
||||
const { redirect } = Host.getFunctions();
|
||||
const mem = Memory.fromString(url);
|
||||
redirect(mem.offset);
|
||||
}
|
||||
|
||||
function notarize(options) {
|
||||
const { notarize } = Host.getFunctions();
|
||||
const mem = Memory.fromString(JSON.stringify(options));
|
||||
const idOffset = notarize(mem.offset);
|
||||
const id = Memory.find(idOffset).readString();
|
||||
return id;
|
||||
}
|
||||
|
||||
function outputJSON(json) {
|
||||
Host.outputString(
|
||||
JSON.stringify(json),
|
||||
);
|
||||
}
|
||||
|
||||
function getCookiesByHost(hostname) {
|
||||
const cookies = JSON.parse(Config.get('cookies'));
|
||||
if (!cookies[hostname]) throw new Error(`cannot find cookies for ${hostname}`);
|
||||
return cookies[hostname];
|
||||
}
|
||||
|
||||
function getHeadersByHost(hostname) {
|
||||
const headers = JSON.parse(Config.get('headers'));
|
||||
if (!headers[hostname]) throw new Error(`cannot find headers for ${hostname}`);
|
||||
return headers[hostname];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
redirect,
|
||||
notarize,
|
||||
outputJSON,
|
||||
getCookiesByHost,
|
||||
getHeadersByHost,
|
||||
};
|
||||
14
examples/spotify/tsconfig.json
Normal file
14
examples/spotify/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [],
|
||||
"types": [
|
||||
"@extism/js-pdk"
|
||||
],
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user