Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions docs/capabilities/analytics/devvit-journeys.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Devvit Journeys

Devvit Journeys adds a telemetry stream to your app that tracks the entire lifecycle of a user session. With journeys, you can:
Expand Down Expand Up @@ -85,7 +88,7 @@ By instrumenting these moments, you can track session boundaries, understand pla
| :---: | ------------------------------------- | ----------------------- | ----------------------------- |
| **1** | Game loads | **App.Ready** | Game is fully interactive |
| **2** | Player clicks “Start Game” | **Journey.Start** | Begins a new session |
| **3** | Player completes Level 1 (of 5 levels) | **Journey.Progress** | `progress: 0.2` |
| **3** | Player completes Level 1 (of 5 levels) | **Journey.Progress** | `progress: 0.2` |
| **4** | Player opens inventory | **Journey.Interaction** | `action: "menu_opened"` |
| **5** | Player completes Level 2 | **Journey.Progress** | `progress: 0.4` |
| **6** | Player reaches final level | **Journey.Progress** | `progress: 0.9` |
Expand All @@ -98,7 +101,7 @@ By instrumenting these moments, you can track session boundaries, understand pla
| **1** | Game loads | **App.Ready** | Game is fully interactive |
| **2** | Player clicks “Start Game” | **Journey.Start** | Begins a new session |
| **3** | Player completes early level | **Journey.Progress** | `progress: 0.3` |
| **4** | Player dies | **Journey.End** | `complete: false`, `win: false` |
| **4** | Player dies | **Journey.End** | `complete: false`, `win: false` |

### Scenario 3: early exit / abandonment

Expand Down Expand Up @@ -149,7 +152,7 @@ Here's how to implement journey tracking in your app.

Set Journeys permissions to `true` in `devvit.json`.

```
```json title="devvit.json"
"permissions": {
"journeys": true
},
Expand All @@ -159,7 +162,7 @@ Set Journeys permissions to `true` in `devvit.json`.

You can send events solely on the backend and use the front‑end only to establish and pass along the journeyId. To do this, thread the active `journey ID` from your front‑end to your backend routes.

```
```ts title="client/index.ts"
import { telemetry } from '@devvit/analytics/client/reddit';

export async function submitScore(score: number): Promise<void> {
Expand All @@ -184,7 +187,50 @@ export async function submitScore(score: number): Promise<void> {

On the server, read the incoming `journeyId` and use it for correlation in your own route.

<Tabs
variant="pill"
groupId="http-server-framework"
defaultValue="hono"
values={[
{ label: 'Hono', value: 'hono' },
{ label: 'Express', value: 'express' },
]}>
<TabItem value="hono">

```ts title="server/index.ts"
import { telemetry } from '@devvit/analytics/server/reddit';
import { Hono } from 'hono';

const app = new Hono();

app.post('/api/score', async (c) => {
const journeyId = c.req.header('x-devvit-journey-id') ?? '';
const { score } = await c.req.json<{ score: number }>();

console.log('score event', {
journeyId,
score,
});

await telemetry.endJourney({
journeyId,
complete: true,
game: {
win: true,
score,
},
});

return c.json({ ok: true });
});

export default app;
```

</TabItem>
<TabItem value="express">

```ts title="server/index.ts"
import express from 'express';
import { telemetry } from '@devvit/analytics/server/reddit';

Expand All @@ -209,16 +255,18 @@ app.post('/api/score', async (req, res) => {

res.json({ ok: true });
});

```

</TabItem>
</Tabs>

### Client events

If you don’t want to manually send server-events, you can use the generic client side events. In this case, the `JourneyId` is handled. In this case, you won’t need to pass a `JourneyId` when calling progress and so forth. You also won’t need `telemetry.getActiveJourneyId()` unless you’re curious about that data.

Note: This also requires using the route adapters provided in `@devvit/analytics/server/reddit`

```
```ts title="client/index.ts"
// client
import { telemetry } from '@devvit/analytics/client/reddit';

Expand All @@ -231,7 +279,7 @@ await telemetry.progress({

```

```
```ts title="server/index.ts"
// server
import { createTelemetryRouter } from '@devvit/analytics/server/reddit';
app.use(createTelemetryRouter());
Expand Down
63 changes: 27 additions & 36 deletions docs/capabilities/http-fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ Your Devvit app can make network requests to access allow-listed external domain

## Enabling HTTP fetch calls

devvit.json

```

```json title="devvit.json"
{
...
"permissions": {
"http": {
"enable": true,
"domains": ["my-site.com", "another-domain.net"]
}
}
...
"permissions": {
"http": {
"enable": true,
"domains": ["my-site.com", "another-domain.net"]
}
}
}
```

Expand Down Expand Up @@ -56,15 +53,12 @@ Devvit Web applications have two different contexts for using fetch:

Server-side fetch allows your app to make HTTP requests to allowlisted external domains from your server-side code (e.g., API routes, server actions):

server/index.ts

```

```ts title="server/index.ts"
const response = await fetch('https://example.com/api/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const data = await response.json();
Expand All @@ -78,22 +72,20 @@ Client-side fetch has different restrictions:
- **Domain limitation**: Can only make requests to your own webview domain
- **Endpoint requirement**: All requests must target endpoints that start with /api/
- **Authentication**: Handled automatically \- no need to manage auth tokens
- **No external domains**: Cannot make requests to external domains from client-side code
client/index.ts

```
- **No external domains**: Cannot make requests to external domains from client-side code

```ts title="client/index.ts"
const handleFetchData = async () => {
// ✅ Correct: Fetching your own webview's API endpoint
const response = await fetch("/api/user-data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});

const data = await response.json();
console.log("API response:", data);
// ✅ Correct: Fetching your own webview's API endpoint
const response = await fetch("/api/user-data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});

const data = await response.json();
console.log("API response:", data);
};

// ❌ Incorrect: Cannot fetch external domains from client-side
Expand All @@ -107,7 +99,7 @@ const handleFetchData = async () => {

The following error means HTTP Fetch requests are hitting the internal timeout limits.

```
```text
HTTP request to domain: <domain> timed out with error: context deadline exceeded.
```

Expand Down Expand Up @@ -191,8 +183,7 @@ If your app uses fetch domains, add this context to your app's [README](../devvi

Example Fetch Domains section:

```

```md title="README.md"
## Fetch Domains

The following domains are requested for this app:
Expand Down
13 changes: 7 additions & 6 deletions docs/capabilities/notifications/pn-best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ To learn more about creating deeper engagement loops, check out the best practic

In your terminal, navigate to your project directory and run this command to update the push notification to the latest release.

```
```bash
npm install @devvit/notifications
```

### Step 2: Import the push notification module

```
```ts
import { notifications } from '@devvit/notifications';
```

Expand All @@ -144,7 +144,7 @@ import { notifications } from '@devvit/notifications';

To send a push notification to a group of users, you can use the double curly brackets ( { { } } ) to reference variables in a Mustache template.

```
```ts
await notifications.enqueue({
title: 'Hello {{name}}!',
body: 'You have {{score}} new points.',
Expand Down Expand Up @@ -179,7 +179,7 @@ await notifications.enqueue({

**Note:** Mustache templating is optional. Here's a simplified example without it:

```
```ts
await notifications.enqueue({
title: 'Winner!',
body: 'Congrats on your win',
Expand All @@ -189,6 +189,7 @@ await notifications.enqueue({
link: 't3_xyz987',
},
],
});
```

**Note**: If the app hasn’t been published, you can only send push notifications to yourself for testing. **Pre-release apps in testing are not subject to the rate-limits below**.
Expand All @@ -204,14 +205,14 @@ If you need higher limits, let us know.

Users will be able to opt in or out of receiving notifications triggered by a button in your UI:

```
```ts
await notifications.optInCurrentUser();
await notifications.optOutCurrentUser();
```

You will also be able to retrieve a list of users who have opted in (if not managing it manually):

```
```ts
//This will just return the first 1000 users
const recipients = await notifications.listOptedInUsers();

Expand Down
4 changes: 2 additions & 2 deletions docs/capabilities/server/http-fetch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const response = await fetch('https://example.com/api/data', {

const data = await response.json();
console.log('External API response:', data);
````
```

### Client-side fetch

Expand Down Expand Up @@ -106,7 +106,7 @@ If you see the following error, it means HTTP Fetch requests are hitting the int
- Use a queue or kick off an async request in your back end. You can use [Scheduler](./scheduler.mdx) to monitor the result.
- Optimize the overall HTTP request latency if you have a self-hosted server.

```ts
```text
HTTP request to domain: <domain> timed out with error: context deadline exceeded.
```

Expand Down
Loading
Loading