Skip to content
Open
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
2 changes: 2 additions & 0 deletions apps/ssr-test-app/.gitignore copy
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated certificates
certs/
70 changes: 68 additions & 2 deletions apps/ssr-test-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,75 @@ pnpm start-ssr-app

or if you have already built the Teams JavaScript client SDK and would like to build and run directly from the project directory ssr-test-app, simply `pnpm build` and `pnpm start` there.

### Note
## Running with HTTPS

Running the SSR Test App locally defaults to using an unsecure http connection. In order to run the SSR test app in the Orange app, a secure https connection is required. This can be achieved by generating an SSL certificate. Alternatively, ngrok can be used to generate a secure https connection without the need to generate an SSL certificate.
The SSR Test App uses HTTP by default. To run it in the Orange app, you'll need HTTPS.

**Two options:**

- **Option 1:** Generate local SSL certificates (recommended for development)
- **Option 2:** Use ngrok (no certificates needed, but URLs change between runs on free tier)

### Option 1: Using the Custom HTTPS Server

1. Generate SSL certificates:

**Automated (Recommended):**

```bash
# From the monorepo root
pnpm setup-ssr-app-cert
```

This script will:

- Check if mkcert is installed (prompts to install if missing)
- Install the local CA
- Generate certificates in `apps/ssr-test-app/certs/`

**Manual:**

```bash
# Install mkcert (if not already installed)
brew install mkcert # macOS
# or follow instructions at https://github.com/FiloSottile/mkcert

# Install the local CA
mkcert -install

# Generate certificates in the certificates directory
cd apps/ssr-test-app/certificates
mkcert localhost
# This creates localhost.pem and localhost-key.pem
```

2. Run the app with HTTPS:

```bash
# From monorepo root
pnpm start-ssr-app:https-dev # for development
# or
pnpm start-ssr-app:https # for production (requires pnpm build first)

# Or from the ssr-test-app directory
pnpm dev:https # for development
# or
pnpm start:https # for production (requires pnpm build first)
```

The app will be available at https://localhost:3000

### Option 2: Using ngrok

Alternatively, ngrok can be used to generate a secure https connection without the need to generate an SSL certificate:

```bash
# In one terminal, start the app normally
pnpm dev

# In another terminal, start ngrok
ngrok http 3000
```

# Troubleshooting

Expand Down
46 changes: 46 additions & 0 deletions apps/ssr-test-app/components/CommonComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

interface PageInfoProps {
renderString: string;
serverTime: string;
clientTime: string;
}

export function PageInfo({ renderString, serverTime, clientTime }: PageInfoProps): React.ReactElement {
return (
<>
<h1 id="id01">{renderString}</h1>
<h1 id="stime">The server render time is {serverTime.substring(12, 24)}</h1>
<h1 id="ctime">The client render time is {clientTime.substring(12, 24)}</h1>
</>
);
}

interface PostBodyDisplayProps {
postBody?: string;
}

export function PostBodyDisplay({ postBody }: PostBodyDisplayProps): React.ReactElement | null {
if (!postBody) {
return null;
}

return (
<pre>
<b>POST Body:</b> {postBody}
</pre>
);
}

interface ContextDisplayProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: any;
}

export function ContextDisplay({ context }: ContextDisplayProps): React.ReactElement {
return (
<pre>
<b>Context:</b> {JSON.stringify(context, null, 2)}
</pre>
);
}
4 changes: 3 additions & 1 deletion apps/ssr-test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"version": "0.0.1",
"scripts": {
"dev": "next",
"dev:https": "node server.js",
"build": "next build",
"start": "next start"
"start": "next start",
"start:https": "NODE_ENV=production node server.js"
},
"dependencies": {
"next": "^15.5.7",
Expand Down
89 changes: 89 additions & 0 deletions apps/ssr-test-app/pages/failureOnlyTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as microsoftTeams from '@microsoft/teams-js';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react';

import { ContextDisplay, PageInfo, PostBodyDisplay } from '../components/CommonComponents';
import { parseBody } from '../utils/serverUtils';

export interface FailureOnlyTestPageProps {
renderString: string;
time: string;
isPostRequest?: boolean;
postBody?: string;
withMessage?: boolean;
}

//Every POST request will trigger notifyFailure
export default function FailureOnlyTestPage(props: FailureOnlyTestPageProps): ReactElement {
const [teamsContext, setTeamsContext] = useState({});
const [clientTime, setClientTime] = useState('');

useEffect(() => {
microsoftTeams.app.initialize().then(() => {
microsoftTeams.app.getContext().then((ctx) => {
setTeamsContext(ctx);
});

// Always call notifyFailure on every POST request
if (props.isPostRequest) {
const message = props.withMessage
? 'Bearer realm="", authorization_uri="https://some_url/authorize", error="insufficient_claims", claims="Base65Encoded_claims_value"'
: '';
const request = {
reason: microsoftTeams.app.FailedReason.Unauthorized,
authHeader: message,
};
microsoftTeams.app.notifyFailure(request);
}
setClientTime(JSON.stringify(new Date()));
});
}, [props.isPostRequest, props.withMessage]);

return (
<div>
<Head>
<title>Failure Only Test Page</title>
</Head>
<div>
<PageInfo renderString={props.renderString} serverTime={props.time} clientTime={clientTime} />
<PostBodyDisplay postBody={props.postBody} />
<ContextDisplay context={teamsContext} />
</div>
</div>
);
}

/**
* @returns prop data
*/
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
const time = JSON.stringify(new Date());
const withMessage = query.withMessage === 'true';

if (req.method === 'POST') {
const postBody = await parseBody(req);

// Add delay for POST requests
await new Promise((resolve) => setTimeout(resolve, 1000));

return {
props: {
renderString: 'POST request received',
postBody,
time,
isPostRequest: true,
withMessage,
},
};
}

// Reject non-POST requests with 405 Method Not Allowed
res.setHeader('Allow', ['POST']);
res.statusCode = 405;
res.end('Method Not Allowed');

return {
props: {},
};
};
114 changes: 114 additions & 0 deletions apps/ssr-test-app/pages/failureSuccessTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as microsoftTeams from '@microsoft/teams-js';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react';

import { ContextDisplay, PageInfo, PostBodyDisplay } from '../components/CommonComponents';
import { parseBody } from '../utils/serverUtils';

export interface FailureSuccessTestPageProps {
renderString: string;
time: string;
postCount: number;
postBody?: string;
withMessage?: boolean;
}

// First POST request will trigger notifyFailure
// Add ?withMessage=true to the URL to include a message in notifyFailure
// Second POST request will trigger notifySuccess

// Track the number of POST requests received
let postRequestCount = 0;

export default function FailureSuccessTestPage(props: FailureSuccessTestPageProps): ReactElement {
const [teamsContext, setTeamsContext] = useState({});
const [clientTime, setClientTime] = useState('');
const [notificationStatus, setNotificationStatus] = useState('');

useEffect(() => {
microsoftTeams.app.initialize().then(() => {
microsoftTeams.app.getContext().then((ctx) => {
setTeamsContext(ctx);
});

// Call notifyFailure on first POST request
if (props.postCount === 0) {
const message = props.withMessage
? 'Bearer realm="", authorization_uri="https://some_url/authorize", error="insufficient_claims", claims="Base65Encoded_claims_value"'
: '';
const request = {
reason: microsoftTeams.app.FailedReason.Unauthorized,
authHeader: message,
};
microsoftTeams.app.notifyFailure(request);
setNotificationStatus(`notifyFailure called${props.withMessage ? ' with message' : ''} (first POST request)`);
}
// Call notifySuccess on second POST request
else {
microsoftTeams.app.notifySuccess();
setNotificationStatus('notifySuccess called (second POST request)');
}
setClientTime(JSON.stringify(new Date()));
});
}, [props.postCount, props.withMessage]);

return (
<div>
<Head>
<title>Failure & Success Test Page</title>
</Head>
<div>
<PageInfo renderString={props.renderString} serverTime={props.time} clientTime={clientTime} />
<h2 id="post-count">POST Request Count: {props.postCount}</h2>
{notificationStatus && (
<h2 id="notification-status" style={{ color: props.postCount === 0 ? 'red' : 'green' }}>
Status: {notificationStatus}
</h2>
)}
<PostBodyDisplay postBody={props.postBody} />
<ContextDisplay context={teamsContext} />
</div>
</div>
);
}

/**
* @returns prop data
*/
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
const time = JSON.stringify(new Date());
const withMessage = query.withMessage === 'true';

if (req.method === 'POST') {
const currentCount = postRequestCount;
postRequestCount++;
const postBody = await parseBody(req);

// Add delay for POST requests
await new Promise((resolve) => setTimeout(resolve, 1000));
// Reset counter after the second request
if (postRequestCount >= 2) {
postRequestCount = 0;
}

return {
props: {
renderString: `POST request #${currentCount} received`,
postBody,
time,
postCount: currentCount,
withMessage,
},
};
}

// Reject non-POST requests with 405 Method Not Allowed
res.setHeader('Allow', ['POST']);
res.statusCode = 405;
res.end('Method Not Allowed');

return {
props: {},
};
};
Loading
Loading